Jupyter上でDashを使えるjupyter_dash

Dashは可視化をインタラクティブに行えるウェブフレームワークです。そしてDashにはJupyter上でアプリケーションを動作させるjupyter_dashというパッケージが存在しています。そして最近、Google Colab上でjupyter_dashが動作するようになりました。

今回はjupyter_dashの使い方に加えて、Jupyter上でDashが使えるメリットを紹介したいと思います。

今回はグーグルコラボ上で全ての作業を行います。サンプルのノートは次のリンク先にあります。

https://colab.research.google.com/drive/1eUlcEKeHLzN7zBYPutFeU9yRPOwnWqKi?usp=sharing

## 準備

jupyter_dashはコラボにインストールされていません。あと可視化に使うplotlyのバージョンが古いものになっているので、更新します。

```
!pip install jupyter_dash
!pip install --upgrade plotly
```

次に今回利用するライブラリをインポートします。

```
import dash 
from jupyter_dash import JupyterDash 
import dash_core_components as dcc 
import dash_html_components as html 
import plotly.express as px
from dash.dependencies import Input, Output
```

jupyter_dashはJupyter上でDashを使うためのパッケージです。dash_core_componentsは様々なツール、dash_html_componentsはHTMLコンポーネントを提供するパッケージです。plotly.expressはグラフ作成のパッケージです。

## 実践

ここからはPlotly ExpressのGapminderデータを使って進めます。Gapminderデータは1952年から2007年までの世界の国々の平均寿命、人口、1人当たりGDPを持つデータセットです。

```
gapminder = px.data.gapminder()
gapminder.head()
```

<a href="https://gyazo.com/5b8bd30d4f2aeb2bd7ddd99fde1922b0"><img src="https://i.gyazo.com/5b8bd30d4f2aeb2bd7ddd99fde1922b0.png" alt="Image from Gyazo" width="1015"/></a>

### 平均寿命を線グラフで可視化（1つの国）

まずは、国ごとの平均寿命を観察したいとします。Plotly Expressのグラフの書き方は、次のようになります。

- 描きたいグラフ種類の関数を使う
- グラフに描画したいデータフレームを渡す
- 各要素に利用する要素を引数に渡す

まずは日本の平均寿命の推移を可視化します。

```
# コード1
jp_gapminder = gapminder[gapminder["country"] == "Japan"] # Japanのデータフレーム作成
px.line(jp_gapminder, x='year', y="lifeExp") # グラフ描画
```

<a href="https://gyazo.com/8253cc1ae3b338d50f00812e2e7733d3"><img src="https://i.gyazo.com/8253cc1ae3b338d50f00812e2e7733d3.png" alt="Image from Gyazo" width="1140"/></a>

次に中国の平均寿命を観察したい場合だと、たいていの場合中国のデータフレームを作成し、同じように関数の引数にデータを渡し、シフト＋エンターという感じで実行すると思います。

しかし、jupyter_dashを使うとドロップダウンを選択するだけで、グラフを切り替えられ、良い感じにシフトとエンターの摩耗を防げます。

```
# コード2
# JupyterDashインスタンスの作成
app = JupyterDash(__name__)

# layout属性にレイアウトを渡す（ドロップダウンとグラフ）
app.layout = html.Div([
                       dcc.Dropdown(id="my_dropdown",
                                    options=[{"value": cnt, "label": cnt} for cnt in gapminder.country.unique()],
                                    value="Japan"
                                    ),
                       dcc.Graph(id="my_graph")
])

# ドロップダウンの選択値をグラフに反映するためのコールバック関数
@app.callback(Output("my_graph", "figure"), Input("my_dropdown", "value"))
def update_graph(selected_country):
  selected_gapminder = gapminder[gapminder["country"] == selected_country]
  return px.line(selected_gapminder, x="year", y="lifeExp")

# ノート上で実行
app.run_server(mode="inline")
```

上のようなコードでドロップダウンの選択が反映されたグラフが描画されます。

<a href="https://gyazo.com/8e1ac2f1624fcc40c063b9d04bce6e78"><img src="https://i.gyazo.com/8e1ac2f1624fcc40c063b9d04bce6e78.gif" alt="Image from Gyazo" width="1497.5"/></a>

### 平均寿命を線グラフで作成（複数国）

複数国を描画する場合、Plotly Expressでは色（引数color）で国を分けます。

```
# コード3
country_list = ["China", "Korea, Rep.", "Japan"]
selected_gapminder = gapminder[gapminder["country"].isin(country_list)]
px.line(selected_gapminder, x='year', y="lifeExp", color="country")
```

<a href="https://gyazo.com/243fc0fdac72015c09bcaea2af6ad6eb"><img src="https://i.gyazo.com/243fc0fdac72015c09bcaea2af6ad6eb.png" alt="Image from Gyazo" width="1186"/></a>

これを国を入れ替えながら詳細に見るのは結構手間です。しかし、jupyter_dashを使うと先ほどのコードを少し変えるだけで、次のような感じで簡単に複数国の表示を切り替えられるアプリケーションが作成できます。

```
# コード4
app = JupyterDash(__name__)

app.layout = html.Div([
                       dcc.Dropdown(id="my_dropdown",
                                    options=[{"value": cnt, "label": cnt} for cnt in gapminder.country.unique()],
                                    value=["Japan", "China", "Korea, Rep."],　# ➊
                                    multi=True　# ➋
                                    ),　
                       dcc.Graph(id="my_graph")
])

@app.callback(Output("my_graph", "figure"), Input("my_dropdown", "value"))
def update_graph(selected_country):
  selected_gapminder = gapminder[gapminder["country"].isin(selected_country)] # ➌
  return px.line(selected_gapminder, x="year", y="lifeExp", color="country")　# ➍

app.run_server(mode="inline")
```

変更点は番号を振ったところです。➊ではドロップダウンで複数国が最初から選ばれるように、リストに入れて国名を渡します。➋では引数multiにTrueを渡しドロップダウンで複数国を選択できるようにします。➌では複数国が選択されたデータフレームが作成されます。➍では引数colorに"country"を渡し、線の色が国名ごとに変更されるよう指定します。

作成したアプリケーションは次のように動作します。

<a href="https://gyazo.com/24f40eee2fad14a8acede8e4b755cecb"><img src="https://i.gyazo.com/24f40eee2fad14a8acede8e4b755cecb.gif" alt="Image from Gyazo" width="1492.5"/></a>

### ツリーマップを使って可視化

最後にツリーマップを使った可視化を作成します。ツリーマップを文字で解説する文章力がないので、まず作成してみます。

```
# コード5
gapminder['board'] = 'world' # "board"列を追加し、'world'という文字列を追加する
px.treemap(gapminder, path=['board', 'year', 'country'], values='pop')
```

<a href="https://gyazo.com/6529228bd05055f8a8dc485f0e5afceb"><img src="https://i.gyazo.com/6529228bd05055f8a8dc485f0e5afceb.gif" alt="Image from Gyazo" width="1455"/></a>

今回は人口のみを可視化してみました。ツリーマップは次のように動的に数値を確認できます。ツリーマップはデータを入れ子にしてみることができるため、その順番を変えるだけでもデータとして新たな発見があります。

次に、ラジオボタンで人口、1人当たりGDPの表示を切り替えながら、ドロップダウンでツリーマップの表示順を切り替えられるツールを作成します。


```
# コード6
app = JupyterDash(__name__)

app.layout = html.Div([
                       html.H1(id="title"), # ラジオボタンの選択を表示する
                       dcc.RadioItems(
                           id="my_radio",
                           options=[{"label": i, "value": i} for i in ["pop", "gdpPercap"]],
                           value = "pop"
                       ),
                       dcc.Dropdown(
                           id="my_drop",
                           options=[{"label": i, "value": i} for i in ['board', 'year', 'continent', 'country']],
                           value = ['board', 'year', 'continent', 'country'],
                           multi=True
                       ),
                       dcc.Graph(id="my_graph")
])

@app.callback([Output('title', 'children'),Output('my_graph', "figure")], 
              [Input("my_radio", "value"), Input("my_drop", "value")])
def update_tree(radio_select, drop_select):
# ドロップダウンで3つ以上の要素が選択されている場合のみグラフを描画
  if len(drop_select) >= 3: 
    return radio_select, px.treemap(gapminder, path=drop_select, values=radio_select)
  else:
    return dash.no_update
app.run_server(mode="inline")
```

30行に満たないコードですが、次のように結構複雑な動作をしてくれます。

<a href="https://gyazo.com/f52c5c9c769cab622c92b3768d3117c0"><img src="https://i.gyazo.com/f52c5c9c769cab622c92b3768d3117c0.gif" alt="Image from Gyazo" width="1510"/></a>

## まとめ

以上のようにjupyter_dashを使うことにより、グラフ描画でのちょっとした面倒を削減することができます（そして多分キーボードも長持ちします）。

もうちょっとDashを詳しく知りたいと思われた方は、過去の記事をご参照ください。

https://qiita.com/OgawaHideyuki/items/6df65fbbc688f52eb82c

もうちょっと実際のデータを使った事例を知りたいという方は、WEB+DB PRESS VOL118に記事を書かせていただいたので、手に取っていただけますと幸いです。

https://gihyo.jp/magazine/wdpress

また、PyConJP2020で行ったTutorialの資料を公開しているので、これも参考になるかと思います。こちらはデータの前処理、可視化、機械学習との流れとなっています。スターをつけてもらえると嬉しいです。

https://github.com/hannari-python/tutorial

もっと詳しく知りたいという方は、[本家のドキュメント](https://dash.plotly.com/)を参照していただくか、11月ごろに出る本を購入していただけますと幸いです。宣伝です。

http://www.asakura.co.jp/books/isbn/978-4-254-12258-9/

あと、PyCon mini Hiroshima2020で話す機会をいただいたので、アイデアを練っています。イベントに参加いただき、当日のトークを聞いていただけると嬉しいです！

https://pycon-hiroshima.connpass.com/event/186849/