データ可視化Webアプリケーションをplotly-Dashで作る

# はじめに

Pythonで手軽にデータ可視化用Webアプリケーションを作れる[plotly](https://plotly.com/python/)と[Dash](https://dash.plotly.com/introduction)というライブラリを紹介して、ちょっと使ってみようと思います。

ざっくり言うと、plotlyで描いたグラフをWebアプリケーションフレームワークのDashで作ったものに載せることで完成するという使い方になっています。

# plotlyとは

plotlyはデータをさまざまなグラフで可視化できるPythonライブラリです。作成したプロットはインタラクティブな操作が可能で、ズームインしてさらに分析するなどもできます。

pipで簡単にインストールできます。（2022/12/16時点でバージョン5.11.0が最新版でした）
```
$ pip install plotly
```

## サブモジュールについて

* `plotly.express`
    * データを可視化するための関数が定義されています。
    * 高レベルなAPIを提供しているので、細かい設定は抜きにしてサッとグラフを作ることができます。express(＝急行）様様です。
* `plotly.graph_object`
    * データを可視化するための低レベルな機能を提供しています。
    * プロットオブジェクトを作成したり、レイアウトのカスタマイズができます。
* `plotly.subplots`
    * 一つの描画領域に複数のプロットを並べるための関数を定義しています。
* `plotly.figure_factory`
    * plotlyが裏で使っているplotly.jsにはない図表を描画するためのラッパー関数が定義されています。
    * 例えば、等高線図や矢印プロット、バレットグラフがありそうです。
* `plotly.io`
    * plotlyで描いたプロットをPDFやpngで保存するための低レベル関数などを定義しています。
        * サッと保存するなら`plotly.graph_objects.Figure`にメソッドが用意されているので、plotly.ioは低レベルということになります。
* `plotly.colors`
    * plotlyで使うパレットを指定する際などの色に関するお役立ち関数が定義されています。
* `plotly.data`
    * データセットがあらかじめいくつか組み込まれています。ちょっと遊んでみるだけの時に、データを用意する手間が省けます。

# Dashとは

Pythonで構築されたWebアプリケーションフレームワークです。plotlyで可視化したインタラクティブなプロットをWebアプリケーションに組み込めます。Dashは内側でFlaskが使われていて、HTTPリクエストを処理できます。

pipで簡単にインストールできます。（2022/12/16時点でバージョン2.7.1が最新版でした）
```
$ pip install Dash
```

## よく使われるサブモジュール

* `dash.Dash`
    * リクエストを待ち受けるWebアプリのクラスを定義しています。
* `dash.dcc`
    * ドロップダウンやチェックリストといったインタラクティブなコンポーネントのクラスを定義しています。
* `dash.html`
    * HTMLを書いたりせずに、PythonでWebページを構成するHTMLコンポーネントができるようにしています。
* `dash.Input`,`dash.Output`
    * インタラクティブな操作をした際の入力とコールバックを完了した後の出力を受け取るためのクラスです。

# 具体例について

plotlyとDashを開発しているPlotly社のページに素晴らしい具体例がplotlyにもDashにもそろっています。休みの日にゴロゴロしているくらいなら、このgalleryに赴いて美的感動を受けてください。

# 実践してみる

株価の情報を表示する画面を作ってみます。ローソク足を作る関数はすでに`plotly`に用意されているので、そこに5日間移動平均線と20日間移動平均線を付け加えてみます。

## 株価情報の取得

Yahoo! FinanceのAPIを使って株式情報をダウンロードしましょう。yfinanceモジュールはYahooの一般公開されているAPIなので、研究や教育を目的に個人の使用にとどめましょう。
```
$ pip install yfinance
```

yfinanceでダウンロードするには、企業名のティッカーシンボルかコードが必要になります。ティッカーシンボルがどこにまとめられているかは分からなかったので、コードがまとまっているエクセルシートを[日本取引所グループ](https://www.jpx.co.jp/markets/statistics-equities/misc/01.html)から持ってきました。

そこから、適当な企業を見つけます。下は日鉄ソリューションズの株価をダウンロードしてきます。
```
>>> import yfinance
>>> from datetime import datetime as dt
>>> df = yfinance.download('2327.T', start=dt(2021,9,27), end=dt(2022,9,27))
[*********************100%***********************]  1 of 1 completed
>>> df.to_csv('日鉄ソリューションズ_20210927-20220927.csv')
```

## plotlyとDashを使う

Webアプリケーションの画面を作るコードです。`sma.py`は移動平均線を計算するコードです。

```sma.py
def short_SMA(df):
    averages = []
    for i in range(len(df)):
        if i >= 4:
            averages.append(df.loc[i-4:i,"終値"].sum()/5)
        else:
            averages.append(None)
    return averages

def middle_SMA(df):
    averages = []
    for i in range(len(df)):
        if i >= 19:
            averages.append(df.loc[i-19:i,"終値"].sum()/20)
        else:
            averages.append(None)
    return averages
```

```app.py
from dash import Dash, dcc, html, Input, Output
import plotly.graph_objects as go
import pandas as pd
from sma import short_SMA, middle_SMA

app = Dash(__name__) # リクエストを待ち受けるWebアプリのクラス

# Webアプリの画面レイアウト
app.layout = html.Div([
    html.H4('ロウソク足'),
    dcc.Checklist(
        id='toggle-rangeslider',
        options=[{'label': 'Include Rangeslider', 
                  'value': 'slider'}],
        value=['slider']
    ),
    dcc.Graph(id="graph"),
])

@app.callback(
    Output("graph", "figure"), # 関数の出力がgraphコンポネントのfigureプロパティに設定される
    Input("toggle-rangeslider", "value") # Checklistのvalueプロパティを受ける
)
def display_candlestick(value):
    df = pd.read_csv(
        "./日鉄ソリューションズ_20210927-20220927.csv",
        parse_dates=True, usecols=[0,1,2,3,4,6]
    )
    candlestick = go.Figure(go.Candlestick(
        x=df['Date'],
        open=df['始値'],
        high=df['高値'],
        low=df['安値'],
        close=df['終値'],
        name="ローソク足"
    ))
    df["short_avg"] = short_SMA(df)
    df["middle_avg"] = middle_SMA(df)
    short_avg = go.Scatter(
        x=df["Date"], y=df["short_avg"],
        name="短期移動平均線",
        mode="lines",
        marker=dict(color="#a0a0a0")
    )
    middle_avg = go.Scatter(
        x=df["Date"], y=df["middle_avg"],
        name="中期移動平均線",
        mode="lines",
        marker=dict(color="#808080")
    )
    fig = go.Figure(candlestick) # ローソク足を描画する
    fig.add_traces(short_avg) # 短期移動平均線を追加する
    fig.add_traces(middle_avg) # 中期移動平均線を追加する
    fig.update_layout(
        xaxis_rangeslider_visible='slider' in value
    )
    return fig

app.run_server(debug=True)
```

以下で実行できる。
```
>>> python app.py
Dash is running on http://127.0.0.1:8050/

 * Serving Flask app 'app'
 * Debug mode: on
```
ブラウザで上のURLにアクセスすれば次のページが表示されます。
* 図の下のスライドバーで表示する時間範囲を決定できます。
* 左上のチェックリストからチェックを外すとコールバック関数が呼ばれてスライドバーが消えます。

![app.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960001/5ad1f5ec-6f74-1917-7343-61467a9df5b8.png)

# おわりに

簡単にブラウザでデータを可視化できるアプリケーションが作れました。もっと作り込んで、移動平均線以外の指標を計算して表示するとか別の銘柄に切り替えられるとかできるようにしたいと思います。