DashHandsOn04
4回目
#dashhandson
https://gyazo.com/c72e2d181734c5cecd593bec0e5d5473
1回目はDashの全体像、2回目はレイアウトの作成、3回目はコールバックに触れた
資料
DashHandsOn 01
DashHandsOn 02
DashHandsOn03
github
今回はアプリケーションにインタラクティブさを提供するコールバックに触れる
資料 Github , html
課題
Gitの解説が必要だった
自己紹介
小川 英幸 @ogawahideyuki hideyan.icon
はんなりPythonの会 オーガナイザ
PyConJP / PyCon China Beijing 2019 スピーカ
最近作っているもの
COVID-19可視化 https://chomoku.herokuapp.com/covid-19
京都のCOVID-19アプリ https://chomoku.herokuapp.com/kyoto-covid 
Qiita 記事: COVID-19のデータでネットワーク図を作成した
Github: https://github.com/mazarimono/chomoku
今日やること
復習
レイアウト
コールバック
新しいこと
コールバック
アプリケーションのリアルタイム更新
新機能:: パターンマッチングコールバック https://dash.plotly.com/pattern-matching-callbacks
アプリのデプロイ
ヘロクへのデプロイ
復習
まずは環境作り(参考: Python Japan https://www.python.jp/install/install.html
code:shell
$ python -m venv venv # 仮想環境の作成
$ venv/Scripts/activate.ps1 # 仮想環境に入る(ウィンドウズ Poweshell)
$ . ./venv/bin/activate # 仮想環境に入る(Mac / Linux)
$ pip install -U pip
$ pip install dash plotly pandas gunicorn
Dashのレイアウトを作成する
code: first.py
import dash
import dash_html_components as html
import dash_core_components as dcc
import plotly.express as px
gapminder = px.data.gapminder() # サンプルデータの読み込み
app = dash.Dash(__name__) # Dashのインスタンスを作成する
app.layout = html.Div(
[
# タイトルの作成
html.H1("Hello Dash"),
# グラフの作成
dcc.Graph(
figure=px.scatter(
gapminder,
x="gdpPercap",
y="lifeExp",
size="pop",
color="continent",
log_x=True,
)
),
]
)
app.run_server(debug=True)
Dashのインスタンスを作成し
レイアウト属性にコンポーネントを組み合わせて作成したレイアウトを渡す
コンポーネントは7つ提供されている。自作も可能(次回、西田さんに話して頂きます!!)
グラフはplotlyを用いる。ここではplotlyのラッパのplotly.expressを使ってグラフを作成
グラフはplotly.expressを用いて作成したfigureを、dash_core_componentsのGraphコンポーネントのfigure属性に渡して表示する
コードの実行
code: shell
$ python first.py
コールバックを持つアプリケーション
コールバック ユーザの何らかのアクションを利用して、アプリケーションをインタラクティブに動作させる
ここではgapminderデータの日本のデータを用いて、ドロップダウンで選択した要素を線グラフで描画するアプリケーションを作成します。
code: app3.py
import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.express as px
from dash.dependencies import Input, Output, State
# gapminderデータから日本のものだけ選択する
gapminder = px.data.gapminder()
gap_jp = gapminder[gapminder"country" == "Japan"]
app = dash.Dash(__name__)
app.layout = html.Div(
[
# 人口、平均余命、1人当たりGDPを選択できる
# ドロップダウンの作成
dcc.Dropdown(
id="drop",
options=[{"label": i, "value": i} for i in gapminder.columns3:6],
value="lifeExp",
),
# コールバックで作成したグラフを描画する
# コンポーネント
dcc.Graph(id="my_graph"),
]
)
# コールバックの作成
@app.callback(Output("my_graph", "figure"), Input("drop", "value"))
# コールバック関数
def update_graph(selected_value):
return px.line(gap_jp, x="year", y=selected_value, title=f"日本の{selected_value}")
if __name__ == "__main__":
app.run_server(debug=True)
コールバックでは、コンポーネントの属性に与えるデータを変更してアプリケーションを動作させます。
コールバックはInputクラスに渡したコンポーネントの属性が更新されると呼び出されます。
ここではInputコンポーネントにドロップダウンのvalue属性を指定します。
コンポーネントの指定にはid名を用います。
Inputクラスの引数はコンポーネントのID名、コンポーネントの属性名となります。
コールバックの出力先はOutputクラスにコンポーネントの属性を指定します。
ここでは、Graphコンポーネントのfigure属性を指定します。
Outputクラスの引数はInputクラスと同様です。
Inputインスタンスは単数でもリストに格納します。(Outputインスタンスは単数だとリストに格納しない。複数だとリストに格納する)
ここでは用いてませんがコンポーネントの属性値の状態を活用するStateクラスも存在します。
StateインスタンスはInpurインスタンス同様、リストに格納し、components_id、components_propertyの順で指定
そのコールバック関数ではデータの戻り値を作成
関数名、引数名は任意
引数にはInputインスタンス、Stateインスタンスで指定された属性が割り当てられ、その数と同様の引数名が必要
復習おしまい
リアルタイム更新
リアルタイムでアプリケーションを更新します。
データは今日のリポジトリのdata/df_realtime_update.csvを用います。
このデータは1秒ごとの値を持ちます。
リアルタイム更新にはIntervalクラスを用います
Interval属性で更新間隔をms
n_intervalsで更新回数を数える
code: realtime.py
from datetime import datetime, timedelta
import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import plotly.express as px
from dash.dependencies import Input, Output
# データの読み込み
df = pd.read_csv("./data/df_realtime_update.csv", index_col=0)
app = dash.Dash(__name__)
app.layout = html.Div([
html.H1("リアルタイム更新"),
# インターバルコンポーネントの設置(アプリケーションからは見えないが、レイアウト内に設置、初期設定でn_intervalsを1000ミリ秒ごとに更新)
dcc.Interval(
id="update_tool",
),
dcc.Graph(
id="realtime_graph"
)
])
@app.callback(Output("realtime_graph", "figure"),
Input("update_tool", "n_intervals")
)
def update_graph(n_intervals):
now_time = datetime.now()
m_time = now_time - timedelta(seconds=120)
now_time = now_time.time().isoformat(timespec="seconds")
m_time = m_time.time().isoformat(timespec="seconds")
dff = df.locm_time: now_time
return px.line(x=dff.index, y=dff"price")
app.run_server(debug=True)
データが1秒ごとに存在するので、時間をインデックスにしてCSVファイルを読み込む
現在の時間を取得し、その120秒前からのデータを線グラフで表示する
そのグラフをコールバックの返り値とする
新機能 パターンマッチングコールバック
4/10のバージョンアップにて追加された
Documents
コンポーネントのIDが辞書で作成でき、dash.dependenciesから ALL / MATCH / ALLSMALLERのワイルドカードが使えるようになった。==>コンポーネントを作成できる機能
Change log https://github.com/plotly/dash/blob/master/CHANGELOG.md
ALL
code: new_callback.py
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State, MATCH, ALL
app = dash.Dash(__name__, suppress_callback_exceptions=True)
# レイアウト
app.layout = html.Div([
html.Button("Add Filter", id="add-filter", n_clicks=0),
html.Div(id='dropdown-container', children=[]),
html.Div(id='dropdown-container-output')
])
# コールバック
# dropdown-containerにドロップダウンを返す
# childrenにドロップダウンをどんどん加える
# ドロップダウンのidを辞書で作成。"index"の値にボタンのクリック数を渡す
@app.callback(
Output('dropdown-container', 'children'),
Input('add-filter', 'n_clicks'),
State('dropdown-container', 'children'))
def display_dropdowns(n_clicks, children):
new_dropdown = dcc.Dropdown(
id={
'type': 'filter-dropdown',
'index': n_clicks
},
options=[{'label': i, 'value': i} for i in 'NYC', 'MTL', 'LA', 'TOKYO']
)
children.append(new_dropdown)
return children
# インプットにドロップダウン
# クリックして作られる全てのドロップダウンをALLを用いて指定する
# 全てのドロップダウンの値を使う
# ドロップダウンの全ての値を返すコールバック
@app.callback(
Output('dropdown-container-output', 'children'),
Input({'type': 'filter-dropdown', 'index': ALL}, 'value')
# "type"のキーバリューは実際は必要ない
)
def display_output(values):
return html.Div([
html.Div('Dropdown {} = {}'.format(i + 1, value))
for (i, value) in enumerate(values)
])
if __name__ == "__main__":
app.run_server(debug=True)
1つめのコールバック: ドロップダウンを追加する。ID名は辞書で作成。引数n_clicksを渡す。
レイアウトでchildrenにリストを渡し、それにドロップダウンを加える
2つ目のコールバック
追加されたすべてのドロップダウンの値を個別に表示する
どんどん増えるコンポーネントをALLを用いて活用する
ALL
選択されたコンポーネントのデータが全てリストに格納して返される
全てのデータを活用してコールバックを作成
===> これまではID名がないとコールバックを作れなかったが、今後は存在しないコンポーネントも活用できる。
===> 注意 属性の指定は必要!
============
dash.callback_contextのくだりが理解できていないのでもう一度読む
MATCH
IDが合致したものにコールバックを適用
code: new_callback2.py
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State, MATCH, ALL
app = dash.Dash(__name__, suppress_callback_exceptions=True)
app.layout = html.Div([
html.Button("Add Filter", id="dynamic-add-filter", n_clicks=0),
html.Div(id="dynamic-dropdown-container", children=[])
])
@app.callback(Output("dynamic-dropdown-container", "children"),
Input("dynamic-add-filter", "n_clicks"),
State("dynamic-dropdown-container", "children")
)
def display_dropdown(n_clicks, children):
new_element = html.Div([
dcc.Dropdown(
id={"type": "dynamic-dropdown", "index": n_clicks},
options=[{"label": i, "value": i} for i in "京都", "東京", "大阪"]
),
html.Div(
id={"type":"dynamic-output", "index": n_clicks}
)
])
children.append(new_element)
return children
@app.callback(
Output({"type": "dynamic-output", "index": MATCH}, "children"),
Input({"type": "dynamic-dropdown", "index": MATCH}, "value"),
State({"type": "dynamic-dropdown", "index": MATCH}, "id")
)
def display_value(value, id):
return html.Div(f"Dropdown {id'index'}= {value}")
app.run_server(debug=True)
ひとつ目のコールバック ドロップダウンと文字の表示の組み合わせの作成 idのキー"index"にn_clicks属性を渡す
ふたつ目のコールバック MATCHセレクタを用いてidが合致するコンポーネントに対してコールバックを返す
indexが一致するものに文字列を返す
ALLSMALLER
ALLSMALLERセレクタ
InputとStateにのみ使える
OutputはMATCHセレクタを使うと良い
ALLSMALLERは常に必要ではないが、ロジックをシンプルにしてくれる。
code: new_callback3.py
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State, MATCH, ALL, ALLSMALLER
import pandas as pd
import plotly.express as px
gapminder = px.data.gapminder()
df = gapmindergapminder.year == 2007
app = dash.Dash(__name__, suppress_callback_exceptions=True)
app.layout = html.Div([
html.Button('Add Filter', id='add-filter-ex3', n_clicks=0),
html.Div(id='container-ex3', children=[]),
])
# コールバック1
# ボタンクリックでドロップダウンをcontainder-ex3に追加する
# ドロップダウンの下にコールバック2からのデータを得る
# ドロップダウン、Divともにidの"index"にボタンのn_clicks属性を取る
@app.callback(
Output('container-ex3', 'children'),
Input('add-filter-ex3', 'n_clicks'),
State('container-ex3', 'children'))
def display_dropdowns(n_clicks, existing_children):
existing_children.append(html.Div([
dcc.Dropdown(
id={
'type': 'filter-dropdown-ex3',
'index': n_clicks
},
options=[{'label': i, 'value': i} for i in df'country'.unique()],
# ドロップダウンの初期値をクリック回数で選択
value=df'country'.unique()n_clicks
),
html.Div(id={
'type': 'output-ex3',
'index': n_clicks
})
]))
return existing_children
# コールバック2
# ドロップダウンの選択値をMATCHとALLSMALLERセレクタの両方を利用
# 出力先はid名output-ex3のindexがマッチしているコンポーネント
#
@app.callback(
Output({'type': 'output-ex3', 'index': MATCH}, 'children'),
[Input({'type': 'filter-dropdown-ex3', 'index': MATCH}, 'value'),
Input({'type': 'filter-dropdown-ex3', 'index': ALLSMALLER}, 'value')],
)
def display_output(matching_value, previous_values):
# 選択された
all_values = matching_value + previous_values::-1
dff = df[df"country".isin(all_values)]
avg_lifexp = dff'lifeExp'.mean()
# Return a slightly different string depending on number of values
if len(all_values) == 1:
return html.Div(f'{avg_lifexp:.2f} is the life expectancy of {matching_value}')
elif len(all_values) == 2:
return html.Div(f'{avg_lifexp:.2f} is the average life expectancy of {" and ".join(all_values)}')
else:
return html.Div(f'{avg_lifexp:.2f} is the average life expectancy of {" and ".join(all_values:-1)}, and {all_values-1}')
if __name__ == '__main__':
app.run_server(debug=True)
デプロイ
クラウドへのデプロイを行う
クラウドは基本簡単
アカウントの作成は各ベンダのウェブページを参照ください
ヘロク documents dash documents
Azure documents
GCP documents
ヘロク
dashの公式にやり方ある
個人的にシンプルで好きで使っているが全然わかっていない https://chomoku.herokuapp.com/kyoto-covid
デプロイするファイル
アカウント作成: https://signup.heroku.com/
必要な欄を埋める
メールが送ってくるのでリンクをクリックし、パスワードを設定する
https://gyazo.com/d889b31ab5ce25910dac568f2625a8cd
CLI: https://devcenter.heroku.com/articles/heroku-cli
mac / brewを使って
code: brew
$ brew tap heroku/brew && brew install heroku
Windows: ダウンロードしましょう
CLIがダウンロードできたらログインします。
https://devcenter.heroku.com/articles/heroku-cli#getting-started
準備完了
一連の流れの分かりやすい記事
http://vdeep.net/rubyonrails-heroku
herokuのCLIを使いながらdeploy
code: heroku
$ git init
$ heroku create heroku自体で唯一の値
$ git add .
$ git commit -m " make it better "
$ git push heroku master
$ heroku destroy --app
Githubを使ったHerokuへのデプロイ
githubにプッシュして、GUIから手続き memo: test-env/hanpy
Deployを選択
https://gyazo.com/fe59487c9b705fd72b43f36a242ae874
Githubを選択
リポジトリを選択
manual deploy
enable automatic deploys
デプロイに選択したブランチが更新されるとアプリ自体も更新される
SourceTree https://www.sourcetreeapp.com/ gitのGUIツール
やってみましょう!