見出し画像

Plotly+Djangoでインタラクティブなグラフを描く

この記事について

pythonのグラフ描画ライブラリPlotly の使い方をまとめます。
Djangoは使う必要ないのですが、アプリを作っていくという記事の一部なので、Djangoで表示します。

コードはgithubにあるので必要なら使ってください。

Plotlyとは

Plotlyは、javascript やpythonで利用できるオープンソースのグラフ描画ライブラリです。テキストやコミュニティが充実していて、英語に抵抗が無ければ知りたいことは大体解決できます。

Plotlyの特徴は、何といっても描かれたグラフを感覚的に操作してほしい情報を得られるという点です。
グラフに色々な線を描いた時に、クリック一つで線を消したり出現させたりは勿論、詳しく見たい部分を拡大したり、ブラウザ上で画像を保存したり出来ます。

Plotlyのインストール

基本的に公式ページに書いてある通りです。

https://plotly.com/python/getting-started/#jupyter-notebook-support

Djangoで使う前提なので、関係する部分だけ書いていきます。

plotlyはpipやcondaでインストール出来ます。

pip install plotly

取りあえず動かすだけなら、以下のようにコードを書いて、pythonで実行すれば良いです。

import plotly.graph_objects as go
fig = go.Figure(data=go.Bar(y=[2, 3, 1]))
fig.write_html('first_figure.html', auto_open=True)

このコードをfirust_figure.py とかで保存して、pythonで実行します。

python firust_figure.py 

すると、first_figure.htmlというファイルが生成され、ブラウザでグラフが表示されます。

画像1

htmlファイルを見ると、javascriptのスクリプトが書かれているのが確認できると思います。

jupyter notebook やgoogle colabを使えば、htmlファイルを生成することなくグラフを表示できます。やり方は公式ページを見てください。

Django側での準備

Djangoでは、プロジェクトを立ち上げて、htmlページを格納する為のtemplateフォルダや、urls.pyを準備します。基本的にはそれだけです。

プロジェクトの立ち上げ方などは、Djangoのドキュメントだったり、私が書いた解説など、至る所に情報があります。

この記事では、以下のようにプロジェクトとアプリを生成しました。

django-admin startproject plotly_ex
cd plotly_ex
python manage.py startapp graphs

グラフの描き方

Plotlyでは、グラフを描くためのインスタンスを生成し、そこに属性を追加する事で、グラフの種類やレイアウトを設定します。

設定が完了したら、htmlへの出力をして、ブラウザで表示します。先ほどはhtmlを生成していましたが、既に存在しているhtmlへ張り付ける事も出来ます。
plotlyが配布しているデータで、キャンドルスティックを描いてみます。データの詳細を載せておきます。

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 506 entries, 0 to 505
Data columns (total 11 columns):
#   Column         Non-Null Count  Dtype
---  ------         --------------  -----
0   Date           506 non-null    object
1   AAPL.Open      506 non-null    float64
2   AAPL.High      506 non-null    float64
3   AAPL.Low       506 non-null    float64
4   AAPL.Close     506 non-null    float64
5   AAPL.Volume    506 non-null    int64
6   AAPL.Adjusted  506 non-null    float64
7   dn             506 non-null    float64
8   mavg           506 non-null    float64
9   up             506 non-null    float64
10  direction      506 non-null    object
dtypes: float64(8), int64(1), object(2)
memory usage: 43.6+ KB

views.pyとhtmlを次のように書きます。

views.py

from django.shortcuts import render

import plotly.graph_objects as go

import pandas as pd

# Create your views here.


def index(request):
   df = pd.read_csv(
       'https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv')

   fig = go.Figure(data=[go.Candlestick(x=df['Date'],
                                        open=df['AAPL.Open'],
                                        high=df['AAPL.High'],
                                        low=df['AAPL.Low'],
                                        close=df['AAPL.Close'])])

   plot_fig = fig.to_html(fig, include_plotlyjs=False)
   return render(request, "graphs/index.html", {
       "graph": plot_fig
   })

index.html

<!DOCTYPE html>
<html lang="ja">

<head>
   <meta charset="utf-8" />
   <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
   <!--  plotly JS Files  -->
   <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
   <meta content='width=device-width, initial-scale=1.0, shrink-to-fit=no' name='viewport' />
</head>

<body>
   {% autoescape off %}
   {{ graph }} 
   <!-- HTMLコードの文字列が格納されたコンテキスト -->
   {% endautoescape %}
</body>

</html>

Django側でurls.pyを作ったり、settings.pyにアプリを追加したりして、アプリを起動すると、以下のようなグラフが得られます。

画像2

ブラウザ上でグラフの下のスライドバーを操作したり、グラフ上で範囲を選択する事で、拡大したり出来ます。

画像3

views.pyをのグラフを描いている部分を少し見てみます。

   fig = go.Figure(data=[go.Candlestick(x=df['Date'],
                                        open=df['AAPL.Open'],
                                        high=df['AAPL.High'],
                                        low=df['AAPL.Low'],
                                        close=df['AAPL.Close'])])

   plot_fig = fig.to_html(include_plotlyjs=False)

fig=go.Figure()でグラフオブジェクトを生成しています。

go.Figure()でグラフオブジェクトを生成できます。date=[]に書きたいグラフを渡していくことが出来ます。
キャンドルスティックを描くには時間を表すx軸、open,high, low,closeが必要で、それぞれをpandas.Seriesの形で渡しています。データの個数が一致している必要があるので、複数のDataFrameから切り出してくるのでなく、一つのDataFrameに結合して使った方が楽です。

次に、plot_fig でhtmlに埋め込むためのグラフを生成しています。
include_plotlyjs=False とした時は、htmlの方でploty.jsを読み込んでおく必要があります。.to_htmlは、色々な引数を取る事が出来ます。弄ってみたい方はドキュメントを読んでください。
また、htmlにグラフを描く時は、次のようにautoescapeをoffにしなくてはいけません。

index.html

{% autoescape off %}
{{ graph }} 
<!-- HTMLコードの文字列が格納されたコンテキスト -->
{% endautoescape %}

取りあえずは、go.Figure()でグラフオブジェクトを生成し、to_html()でhtmlで表示する為の文字列を生成すると覚えておけば良いと思います。

複数枚のグラフを一枚に描く

大体の作業では、複数のグラフを一枚にまとめたいものです。
グラフオブジェクトに、.add_trace()とする事で、グラフを何枚でも1枚にまとめる事が出来ます。例えば、先ほどのグラフに、移動平均線2本を足すには以下のようにします。

views.py

from django.shortcuts import render

import plotly.graph_objects as go

import pandas as pd


# Create your views here.


def index(request):
   df = pd.read_csv(
       'https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv')
   close = df['AAPL.Close']

   fig = go.Figure(data=[go.Candlestick(x=df['Date'],
                                        open=df['AAPL.Open'],
                                        high=df['AAPL.High'],
                                        low=df['AAPL.Low'],
                                        close=df['AAPL.Close'])])
   df["sma1"] = close.rolling(window=7).mean()
   df["sma2"] = close.rolling(window=14).mean()
   fig.add_trace(
       go.Scatter(
           x=df['Date'],
           y=df["sma1"],
           name="sma(7)",
           line=dict(
               color='darkorange',
               width=1)))
   fig.add_trace(
       go.Scatter(
           x=df['Date'],
           y=df["sma2"],
           name="sma(14)",
           line=dict(
               color='tomato',
               width=1)))
   plot_fig = fig.to_html(include_plotlyjs=False)

   return render(request, "graphs/index.html", {
       "graph": plot_fig
   })

views.pyを書き換えて、runserverしたアドレスにアクセスすると、次のようなグラフが表示されるはずです。(少し拡大しています。)

画像4

plotlyで曲線を描くにはgo.scatter()を使います。少しだけ詳細を眺めます。

 go.Scatter(x=df['Date'],
           y=df["sma1"],
           name="sma(7)",
           line=dict(
               color='darkorange',
               width=1)))


go.Scaterを使う時は、最低限xとyを指定します。
name= で何か指定すると、グラフの横に表示される名前を指定できます。lime=dict()と書くと、曲線を描いてくれます。colorは、cssで使える色の名前を使えます。

line=を指定しないと、点をプロットする、matplotlibのscatter plotと同じようなグラフを描けます。
他にも引数があります。例えば、グラフ全体が表示されたときに、sma(7)は表示してほしくない時は、visible='legendonly' と指定します。

 go.Scatter(x=df['Date'],
           y=df["sma1"],
           name="sma(7)",
           line=dict(
               color='darkorange',
               width=1)), visible='legendonly')

他にも引数がありますが。詳細はドキュメントを読んでください。

グラフに目印を付ける

例えば、キャンドルスティックに自分で株を売買した情報を表示したいとします。そのような要望はgo.scatterで叶えることが出来ます。

views.py

from django.shortcuts import render

import plotly.graph_objects as go

import pandas as pd


# Create your views here.


def index(request):
   df = pd.read_csv(
       'https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv')

   close = df['AAPL.Close']

   fig = go.Figure(data=[go.Candlestick(x=df['Date'],
                                        open=df['AAPL.Open'],
                                        high=df['AAPL.High'],
                                        low=df['AAPL.Low'],
                                        close=df['AAPL.Close'])])
   df["sma1"] = close.rolling(window=7).mean()
   df["sma2"] = close.rolling(window=14).mean()
   fig.add_trace(
       go.Scatter(
           x=df['Date'],
           y=df["sma1"],
           name="sma(7)",
           line=dict(
               color='darkorange',
               width=1)))
   fig.add_trace(
       go.Scatter(
           x=df['Date'],
           y=df["sma2"],
           name="sma(14)",
           line=dict(
               color='tomato',
               width=1)))

   event = [{"side": "BUY",
             "price": df['AAPL.Close'][0],
             "size":1000},
            {"side": "SELL",
             "price": df['AAPL.Close'][100],
             "size":1000},
            {"side": "BUY",
             "price": df['AAPL.Close'][300],
             "size":1000}]
   fig.add_trace(go.Scatter(x=df["Date"][[0, 100, 300]], y=df["AAPL.Close"][[0, 100, 300]], name="orders", mode="markers",
                            text=event, textposition="bottom left", textfont=dict(
       family="sans serif",
       size=20,
       color="black"),
       marker=dict(
       color='maroon',
       size=6,)
   ))
   plot_fig = fig.to_html(include_plotlyjs=False)

   return render(request, "graphs/index.html", {
       "graph": plot_fig
   })

追加したのはevent=[]という部分とその次のfig.add.trace()部分です。このように書くと、グラフ上に・(丸印)を表示できます。

画像5

マーカーにマウスポインタを置くと、指定した文字が表示されます。
どのようにグラフを描いたのか少し詳しく見てみます。

event = [{"side": "BUY",
             "price": df['AAPL.Close'][0],
             "size":1000},
            {"side": "SELL",
             "price": df['AAPL.Close'][100],
             "size":1000},
            {"side": "BUY",
             "price": df['AAPL.Close'][300],
             "size":1000}]

event = の部分には、マーカーにマウスポインタを置いた時に表示してほしい情報を書いています。リストで渡して、個数や順番に注意します。

次に、fig.add部分を見ます。

   fig.add_trace(go.Scatter(x=df["Date"][[0, 100, 300]], 
       y=df["AAPL.Close"][[0, 100, 300]], 
       name="orders", mode="markers",
       text=event, textposition="bottom left", textfont=dict(
       family="sans serif",
       size=20,
       color="black"),
       marker=dict(
       color='maroon',
       size=6,)
   ))

go.scatter()で、scatter plotを作るのですが、点を打つ場所の指定には、前に使ったデータフレームを使っています。
mode="markers"と指定する事で、グラフ上に丸印を打つことが出来ます。
マーカーの書式については、textfont で指定します。
意味のあるグラフを描きたい時は、マーカーを付ける場所と、データフレームのインデックスなどを同期させると上手く出来ます。
また、mode=""には、lineやline+text, markers+textがあり、+textをすると、text=で指定したデータが、直接グラフ上に表示されます。
何処に文字を表示するかをtextposition=""で指定します。
詳しくは公式のドキュメントを読んでください。

複数のy軸を使用する

1枚のグラフで、y軸を二つ用意したい事があります。例えば、キャンドルスティックでは、出来高を同時に表示させたいと思ったりします。
そんな時は、グラフオブジェクトに対して、secondary_y =True を指定します。
例えば、次のように書きます。

views.py

from django.shortcuts import render

import plotly.graph_objects as go
from plotly.subplots import make_subplots


import pandas as pd


# Create your views here.


def index(request):
   df = pd.read_csv(
       'https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv')

   close = df['AAPL.Close']

   fig = make_subplots(specs=[[{"secondary_y": True}]])

   fig.add_trace(go.Bar(x=df['Date'],
                        y=df['AAPL.Volume'],
                        name="Volume"), secondary_y=False)

   fig.add_trace(go.Candlestick(x=df['Date'],
                                open=df['AAPL.Open'],
                                high=df['AAPL.High'],
                                low=df['AAPL.Low'],
                                close=df['AAPL.Close']),
                 secondary_y=True)
   df["sma1"] = close.rolling(window=7).mean()
   df["sma2"] = close.rolling(window=14).mean()
   fig.add_trace(
       go.Scatter(
           x=df['Date'],
           y=df["sma1"],
           name="sma(7)",
           line=dict(
               color='darkorange',
               width=1)))
   fig.add_trace(
       go.Scatter(
           x=df['Date'],
           y=df["sma2"],
           name="sma(14)",
           line=dict(
               color='tomato',
               width=1)),
       secondary_y=True)

   event = [{"side": "BUY",
             "price": df['AAPL.Close'][0],
             "size":1000},
            {"side": "SELL",
             "price": df['AAPL.Close'][100],
             "size":1000},
            {"side": "BUY",
             "price": df['AAPL.Close'][300],
             "size":1000}]
   fig.add_trace(
       go.Scatter(x=df["Date"][[0, 100, 300]], y=df["AAPL.Close"][[0, 100, 300]],
                  name="orders", mode="markers",
                  text=event, textposition="bottom left", textfont=dict(
           family="sans serif",
           size=20,
           color="black"),
           marker=dict(
           color='maroon',
           size=6,)
       ),
       secondary_y=True)
   plot_fig = fig.to_html(include_plotlyjs=False)

   return render(request, "graphs/index.html", {
       "graph": plot_fig
   })

変更したのは、前半のimport文、fig=make_subplots(), fig.add_trace(go.Bar(), secondar_y=False)の部分等です。
これでグラフは次のようになります。

画像6

左側にもy軸が表れたのが分かると思います。コードを少し詳しく見ます。

fig = make_subplots(specs=[[{"secondary_y": True}]])

   fig.add_trace(go.Bar(x=df['Date'],
                        y=df['AAPL.Volume'],
                        name="Volume"), secondary_y=False)

   fig.add_trace(go.Candlestick(x=df['Date'],
                                open=df['AAPL.Open'],
                                high=df['AAPL.High'],
                                low=df['AAPL.Low'],
                                close=df['AAPL.Close']),
                 secondary_y=True)

先ほどまでのグラフは、fig=go.Figure()と始まっていましたが、今回はfig= make_subplots()で始まります。
描かれるグラフに何か細工したい時は、make_subplots()で始める事が多いです。
今回は

fig = make_subplots(specs=[[{"secondary_y": True}]])

としています。これでy軸を二つ使うと宣言しています。
この後の行で、二つグラフオブジェクトを作っていますが、

fig.add_trace(go.Bar(), secondary_y=False)
fig.add_trace(go.CandleStick(), secondary_y=Trace)

の形になっています。
add_trace()で、secondary_y=Trueとすると、右側のy軸を基準にしたグラフだと認識されます。
secondary_y =False だと左側のy軸基準になります。
もっと詳しい話は、公式ドキュメントを読んでください。

グラフを縦(横)に重ねる

グラフを縦や横に重ねてセットにしたい事があると思います。plotlyでそうすると、スライダーを共通に出来たりして、欲しい情報に素早くアクセスできるようになります。

これは、make_subplotsに書き加える事で実現できます。matplotlibと同じ感じなのでとっつきやすいかもしれません。
縦にグラフを2枚置きたい時は、例えば次のようにします。

views.py

from django.shortcuts import render

import plotly.graph_objects as go
from plotly.subplots import make_subplots


import pandas as pd


# Create your views here.


def index(request):
   df = pd.read_csv(
       'https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv')

   close = df['AAPL.Close']

   fig = make_subplots(rows=2, shared_xaxes=True, row_heights=[0.6, 0.4],
                       specs=[[{"secondary_y": True}],
                              [{}]
                              ]
                       )

   fig.add_trace(go.Scatter(x=df['Date'], y=df["AAPL.Adjusted"], name="Adjusted"),
                 row=2, col=1,
                 )

   fig.add_trace(go.Bar(x=df['Date'],
                        y=df['AAPL.Volume'],
                        name="Volume"), row=1, col=1, secondary_y=False)

   fig.add_trace(go.Candlestick(x=df['Date'],
                                open=df['AAPL.Open'],
                                high=df['AAPL.High'],
                                low=df['AAPL.Low'],
                                close=df['AAPL.Close']
                                ),
                 row=1, col=1,
                 secondary_y=True)
   df["sma1"] = close.rolling(window=7).mean()
   df["sma2"] = close.rolling(window=14).mean()
   fig.add_trace(
       go.Scatter(
           x=df['Date'],
           y=df["sma1"],
           name="sma(7)",
           line=dict(
               color='darkorange',
               width=1)
       ),
       row=1, col=1, secondary_y=True)
   fig.add_trace(
       go.Scatter(
           x=df['Date'],
           y=df["sma2"],
           name="sma(14)",
           line=dict(
               color='tomato',
               width=1)
       ),
       row=1, col=1, secondary_y=True)

   event = [{"side": "BUY",
             "price": df['AAPL.Close'][0],
             "size":1000},
            {"side": "SELL",
             "price": df['AAPL.Close'][100],
             "size":1000},
            {"side": "BUY",
             "price": df['AAPL.Close'][300],
             "size":1000}]
   fig.add_trace(
       go.Scatter(x=df["Date"][[0, 100, 300]], y=df["AAPL.Close"][[0, 100, 300]],
                  name="orders", mode="markers",
                  text=event, textposition="bottom left", textfont=dict(
           family="sans serif",
           size=20,
           color="black"),
           marker=dict(
           color='maroon',
           size=6,)
       ),
       row=1, col=1, secondary_y=True)
   plot_fig = fig.to_html(include_plotlyjs=False)

   return render(request, "graphs/index.html", {
       "graph": plot_fig
   })

こうして書いておくと、グラフは次のようになります。

画像7

グラフが2枚になりました。スライダーを動かすと、下のグラフと上のグラフが連動するようにしています。

画像8

コードを見ましょう。変更したのは、make_subplots()や、add_trace()のオプションに行や列を識別する為のcol=, row=を入れた部分です。

   fig = make_subplots(rows=2, shared_xaxes=True, row_heights=[0.6, 0.4],
                       specs=[[{"secondary_y": True}],
                              [{}]
                              ]
                       )

   fig.add_trace(go.Scatter(x=df['Date'], y=df["AAPL.Adjusted"], name="Adjusted"),
                 row=2, col=1,
                 )

   fig.add_trace(go.Bar(x=df['Date'],
                        y=df['AAPL.Volume'],
                        name="Volume"), row=1, col=1, secondary_y=False)

make_subplotsの中に設定を書き込んでいきます。

rows=2 は縦に二枚、横に一枚グラフを描く事を示しています。

shared_xaxes=Trueは、グラフでx軸を共有するという事です。

 row_heights=[0.6, 0.4]で、それぞれのグラフの高さを比率で決めています。[0.9,0.1]とかでも良いです。
rows=nだったら、len(row_heights)=nとしないとエラーになります。

specs=[[{"secondary_y": True}],
                              [{}]
                              ]

の部分は、どのグラフに設定を適用するか、というやつです。
rows=2,cols=2とかにすると、2×2行列のように設定を書かなくてはいけません。

make_subpliotsで、rowsやcolsで2以上を指定した時には、add_trace()で、それが何処のグラフか明示しなくてはなりません。
実際、上で抜き出したコードも次のような形で書かれていると思います。

   fig.add_trace(go.Scatter(),row=2, col=1)
   fig.add_trace(go.Bar(), row=1, col=1, secondary_y=False)

この時、(row,col)の場所のグラフの設定とmake_subplotsで指定した設定が合わないとエラーが起こります。
今回で言うと、(rows,col)=(1,1)ではsecondary_y を使うといっているので、その設定を書いています。

一方で、

fig.add_trace(go.Scatter(),row=2, col=1,secondary_y=False )

とか書くとエラーが出ます。
他にも色々設定がありますが、詳しくは公式ドキュメントを読んでください。

まとめ

・plotlyの紹介をした
・djangoのプロジェクト上で色々なグラフを描いた。
・plotlyの機能を紹介した。主に、go.add_trace()とmake_subplots()



いいなと思ったら応援しよう!