PyCon JP 2024発表の詳細版です.
Python製のLow-codeであるDashについて話をさせてもらいました(自分のレポート).
トーク本編では話さなかったor話せなかった件を中心に紹介します.
なお, 本エントリーのサンプルコードは以下のGitHub Repositoryにて公開しています.
本エントリーの目的
「とりあえずDashでHello worldをやりながら構成を理解, 作りたいものを作れるぐらいの知識を提供」
すること目的としています.
FlaskでPythonのアプリを作ったことあるかつ, pandasやplotlyを使えるといい感じにお楽しみ頂けるかと思います.
TL;DR
ひとまずデータの可視化をするアプリをPoC的にエイッと作るなら役立ちます&案外大きいアプリケーションも作れます.
認証認可などちょっと厄介(痒いところに手が届かない)欠点はありますが使えると思います(個人的な感想).
Dashとは何か
Dashはデータの可視化(ビジュアライゼーション)に全振りした, Low-code(≒いい感じに楽に作れる)Web アプリのフレームワークです.
敢えて特徴を3つに絞るならば,
- Low-codeなWeb Frameworkで, データの可視化に全振りしている.
- Python版(注: 他の言語版*1もある)はFlaskがベース. Flaskで使えるものはだいたい使える.
- OSS版(このエントリーで紹介しているもの)とEnterprise版(有償*2)が存在.
といった所でしょうか.
なおPythonのLibraryなので導入はこれで行けます.
pip install dash pandas # どのみちpandas使うので最初から入れると幸せ(なはず)
ちなみにEnterprise版は使ったこと無いです, あしからず🙏
Hello world
とある野球データ(Sean LahmanのMLBデータ, 打撃版)のCSVを可視化するサンプル(元のコード).
from dash import Dash, html, dcc, callback, Output, Input import plotly.express as px import pandas as pd df = pd.read_csv('Batting.csv') # applicationの base object app = Dash() server = app.server # HTMLタグおよび組み込みのコンポーネントでページを作る app.layout = [ html.H1(children='Dash App for Baseball', style={'textAlign':'center'}), dcc.Dropdown(df.playerID.unique(), 'ohtansh01', id='dropdown-selection'), # default value is Ohtani,Shohei('ohtansh01') dcc.Graph(id='graph-content'), html.P('Sample data is "Lahman Baseball Database"', style={'textAlign':'center'}) ] # Drop downの選択でcallback発火 -> グラフを更新 @callback( Output('graph-content', 'figure'), Input('dropdown-selection', 'value') ) def update_graph(value): dff = df[df.playerID==value] return px.bar(dff, x='yearID', y='HR') if __name__ == '__main__': app.run(debug=True)
こちらをapp.py
というファイル名で保存,
python app.py
を実行するとこのようなページが表示されます.
コードブロック毎にざっくり紹介します.
基本的な構成
from dash import Dash, html, dcc, callback, Output, Input import plotly.express as px import pandas as pd df = pd.read_csv('Batting.csv') # applicationの base object app = Dash() server = app.server
pandasのDataframe(df
)をGlobal領域にて宣言・読み込み.
app = Dash()
はFlaskで言う所のapp = Flask(__name__)
みたいなもので, Application本体の基本的なobject.
server = app.server
の部分はgunicornなどのWebサーバーでホストする際のエントリーポイントになる部分となります(参考).
コンポーネント(html, dcc)
# HTMLタグおよび組み込みのコンポーネントでページを作る app.layout = [ html.H1(children='Dash App for Baseball', style={'textAlign':'center'}), dcc.Dropdown(df.playerID.unique(), 'ohtansh01', id='dropdown-selection'), # default value is Ohtani,Shohei('ohtansh01') dcc.Graph(id='graph-content'), html.P('Sample data is "Lahman Baseball Database"', style={'textAlign':'center'}) ]
上記はコメントの通り, ページのレイアウトを複数のコンポーネントで構成しています.
H1
とかP
とかはもうお察しだと思います(それぞれHTMLのタグと同等).
dcc.Dropdown
など, dcc
の部分は「Dash Core Components」という基本的な組み込みコンポーネントです.
入力(この例だとDropdown), 出力(この例だとGraphの描画)といったアプリのベースとなる部分は基本的*3にこのコンポーネントから部品を選んで構築することになります.
コールバック
アプリケーションで動的な動きをする部分, このサンプルだと「プルダウンを選択したら値とグラフが変わります」的な実装はすべてcallbackで実装します.
# Drop downの選択でcallback発火 -> グラフを更新 @callback( Output('graph-content', 'figure'), Input('dropdown-selection', 'value') ) def update_graph(value): dff = df[df.playerID==value] return px.bar(dff, x='yearID', y='HR')
- 関数に
@callback
デコレータを付けて実装. 引数としてInput/Outputのオブジェクト(書き換え先のIDおよび種別・引数など)をもたせる - callbackのInput/Outputに合わせて関数を実装
これでいい感じにcallbackイベントが実装可能です.
Dashの実装で最も時間がかかるのはこのcallbackかもしれません(個人的な感想).
ちなみにcallback関数のテストはこんな感じでいけます(公式のサンプルから引用).
# 別途dash[testing]のインストールが必要 from contextvars import copy_context from dash._callback_context import context_value from dash._utils import AttributeDict # Import the names of callback functions you want to test from app import display, update def test_update_callback(): output = update(1, 0) assert output == 'button 1: 1 & button 2: 0' def test_display_callback(): def run_callback(): context_value.set(AttributeDict(**{"triggered_inputs": [{"prop_id": "btn-1-ctx-example.n_clicks"}]})) return display(1, 0, 0) ctx = copy_context() output = ctx.run(run_callback) assert output == f'You last clicked button with ID btn-1-ctx-example'
個人的にはcallbackそのもののテストは結構大変なので, データ取得やグラフ描画を関数として切り出してそれに対するテストにしちゃったほうが良いかなと思います*4.
認証認可
Basic認証で良ければ, 上記のコードにちょこっと追加することで実現可能です(サンプルコード).
# クラスをimport from dash_auth import BasicAuth # 使い方 app = Dash() server = app.server # Basic認証を組み込む auth = BasicAuth(app, {'basic_auth_user':'basic_auth_password'})
これでid: basic_auth_user password: basic_auth_password
でbasic認証がかかります.
なお, PyCon JP 2024のトークでは認証認可について以下の通り紹介しました.
大切なポイント*5として,
ことです.
最初の解説で, 「DashはFlaskベース」であると解説したとおり, 無理をすればFlaskの知見がある方がいい感じにやれば自分で組むこともできそうですが, (個人的には)セキュリティやライセンスなどのリスクも大いにありそう(特に前者)なので, よほどの上級者じゃない限りはやらないことを強くオススメします(特に業務目的ならなおのこと*7).
Basic認証が嫌かつ, Enterpriseを使わない場合であれば,
- Auth0やFirebase Authenticationなどのクラウドサービスの認証局を準備し, その支配下で認証認可を入れる.
- IP制限などのネットワーク層で参照できる範囲を限定する
といった, 「アプリケーションの外側で解決する」アプローチ*8を強く推奨します.
マルチページ
ここまでの説明はすべてSPA(Single Page Application)の解説でした.
DashはSPAだけでなく, MPA(Multi Page Application), すなわち複数のURL(動的含む)を持ったApplicationの構築が可能です.
例えばここまで紹介してきた⚾️Applicationを「このあと投手成績のページも作らなきゃ」と思い立ち, とりあえず,
/
: Topページ/batting
: 打者成績のページ
の様に再構築したくなったとします(サンプルはこれ).
となった場合は以下の構成で実現可能です.
app.py
ベースとなるApplication構成.
import dash from dash import Dash, html, dcc app = Dash(__name__, use_pages=True) server = app.server app.layout = html.Div([ html.H1('Multi Page'), html.Div([ html.Div( dcc.Link(f"{page['name']} - {page['path']}", href=page["relative_path"]) ) for page in dash.page_registry.values() ]), dash.page_container ]) if __name__ == '__main__': app.run(debug=True)
以下のコンポーネントがヘッダー(タイトルと各ページのリンク)
html.H1('Multi Page'), html.Div([ html.Div( dcc.Link(f"{page['name']} - {page['path']}", href=page["relative_path"]) ) for page in dash.page_registry.values() ]),
各ページの内容はdash.page_container
に書き込まれる.
pages
pages/top.py
にtopページ
from dash import html, register_page register_page(__name__, path='/') # HTMLタグおよび組み込みのコンポーネントでページを作る layout = [ html.H1(children='Dash App for Baseball(Top)', style={'textAlign':'center'}), html.P('こちらはダミーのページ', style={'textAlign':'center'}) ]
page固有のコンテンツはlayout
に引き渡し. ちなみに関数で書いてもOK.
register_page(__name__, path='/')
の部分がURLの登録.
同様に打者成績のページ(pages/batting.py
)も以下の通り実装.
from dash import html, dcc, callback, register_page, Output, Input import plotly.express as px import pandas as pd register_page(__name__, path='/batting') df = pd.read_csv('Batting.csv') # HTMLタグおよび組み込みのコンポーネントでページを作る layout = [ html.H1(children='Dash App for Baseball', style={'textAlign':'center'}), dcc.Dropdown(df.playerID.unique(), 'ohtansh01', id='dropdown-selection'), # default value is Ohtani,Shohei('ohtansh01') dcc.Graph(id='graph-content'), html.P('Sample data is "Lahman Baseball Database"', style={'textAlign':'center'}) ] # Drop downの選択でcallback発火 -> グラフを更新 @callback( Output('graph-content', 'figure'), Input('dropdown-selection', 'value') ) def update_graph(value): dff = df[df.playerID==value] return px.bar(dff, x='yearID', y='HR')
これでMPAとして動くようになります.
世の中に公開する
FlaskやDjango, Fast APIといった他のFramework同様(というよりWebアプリ全般のテンプレとして), Container化してしまえばよいです.
具体的には,
- gunicornなどのWebサーバーで動かす準備をする(公式手順). ちなみに
app.run
はあくまでもDebug用のサーバーなのでproduction利用はやめましょう. - Dockerfileを書く. これはFlaskやDjangoと大差ないと思います*9.
- お好きな環境にデプロイする. 私の発表ではCloud Run(Google Cloud)を使いましたが, 別にk8sでもAmazon ECSでも何でも良いです*10.
といった(PythonでWebを使う人なら)お馴染みの方法でDeployが可能です.
Streamlitとの比較
Python製のLow-codeといえば, 最近流行ってると言えそうなStreamlitもあります*11.
私もチャットアプリを作るなどの場面でStreamlitを多用しています.
Dash同様Streamlitも便利なLow-codeですが, どちらも特性が異なるので両方使う(長所に合わせて使い分ける)のがベストだと考えます*12.
ざっくり書くと,
- かっこよく, いい感じのレイアウトでデータ可視化のアプリを作るならDash
- 入出力が多い, チャットなどのインタラクティブなアプリならStreamlit
この使い分けがベストだと私は考えます.
結び
これで,
「とりあえずDashでHello worldをやりながら構成を理解, 作りたいものを作れるぐらいの知識を提供」
は出来た気がします.
コメントやご質問等あれば, コメントなり何なり頂けると嬉しいです.
最後に自分の考えをちょこっと述べますが,
(Dashが果たす目的から察するに)TableauやLooker Studioなどのサービスで実装せずに済むならそれに越したことは無い*13です, 実務上の意思決定という意味では特に.
DashもStreamlitも組んで運用するといざ大変(PoCみたいに使い捨てできればいいのですが)なので, これでガチでアプリ作って運用する人はそれなりに覚悟を以てやると良いでしょう.
お後がよろしいようで&最後までお読み頂きありがとうございました.
Appendix
ちょっと前の書籍ですがこちらが参考になるかと.
*1:Julia, R, F# の3つ(公式サイトより)
*2:サブスク的なプランは無いっぽく, 問い合わせたうえで決まるらしいです.
*3:「基本的に」と書いたのはBootstrapのコンポーネント(Dash Bootstrap Components, 略してdbc)など他のサードパーティのコンポーネントを使うケースもあるからです.
*4:Dashと関係ない話ですが, ここまで頑張るならWebアプリとしてのEnd to End(E2E)テストを組むほうがよほど賢い解決策なように思えます, 急がば回れみたいな発想にはなりますが.
*5:この認証認可周りはPyCon JP 2024でも複数の質問がありました.というよりこの件しか質問がありませんでした.
*6:Enterprise版によると色々サポートしてるみたいです, 私は触ったことありませんが.
*7:業務目的ならそれなりのコストをかけて(変にケチって情報流出や漏洩などのリスクを抱えることなく), 外部サービスで認証認可入れるでいいと思います. 下手なハックをすると大変です.
*8:つまりサービス全体, システム全体で俯瞰して考えましょうって事です.
*9:つまり同じようなテクニックで解決します(のでサンプルコードは省略).
*10:ここは本当に好みで良いです, 無理にベンダーロックイン(クラウド縛り)する必要は無いです.
*11:私が使い始めたのは2020年, あのAIサービスの開発がデビュー戦でしたが. 4年近く経った今, ここまで流行るとは思いませんでした...超便利な道具として重宝してるので当然か.
*12:言葉にすると普通の事を言ってますが, 「比較」だの何だの言う以前に両方使って強みを知っとけというのが持論だったりします.
*13:私が野球のアプリをDashで組んだのは, Looker Studioなどではどうしても表現できない可視化があったからです. 言うたら「しょうがなく」使っています.