1
1

Dashの細かい使いどころ

Posted at

DashはPythonでデータアプリケーションを作成するためのフレームワークです。フロントエンドに関する知識はほぼ不要で、Pythonのみでアプリケーションを作成することができるので、特にPython以外の言語に馴染の薄いデータサイエンティストが、サクッとプロトタイプを作成するのに重宝します。類似のフレームワークにstreamlitがありますが、Dashの方がコード量は多くなるもののカスタマイズ性に優れ、より見栄えの良いアプリケーションを作成することができます。

今回はDashでアプリケーションを作成してみて学んだ細かい使いどころを備忘録としてまとめました。コールバックなどの基本的な使い方は他に優れた記事がたくさんにあるのでそちらをご覧ください。

レイアウト関連

マルチページ

Dashはマルチページに対応しています。作り方はとても簡単で以下の設定を行うだけです。

  • appの宣言時にuse_pages=Trueと設定する
  • 各ページに対応するスクリプトをpages/に配置する
    • dash.register_page(__name__)でページとして登録する
    • layoutに各ページのレイアウトを設定する
# app.py
app = Dash(__name__, use_pages=True)  # use_pages=Trueと設定
# pages/page1.py
dash.register_page(__name__)  # ページとして登録
layout = html.H1("Hello")  # 変数layoutにレイアウトを設定

CSS

各コンポーネントのスタイルはstyle属性を通して個別にカスタマイズできますが、全体的なスタイルを整えたいときはCSSを利用するのが便利です。Dashではassets/にcssファイルを配置すると自動的に読み込まれ、スタイルに反映されます。

Dash Bootstrap Components

Dash Bootstrap Components (dbc) はBootstrapフレームワークを利用して、優れたデザインのコンポーネントを提供してくれる3rd-party製ツールです。dbcを利用するだけで、簡単に見栄えの良いアプリケーションに仕立て上げることができます。

テーマ

dbcでは様々なデザインのスタイルシートが提供されており、これを読み込ませることで全体のテーマを自分好みにカスタマイズできます。利用できるテーマの一覧は上記リンクのAvailable themesから確認できます。

app = Dash(__name__, external_stylesheets=[dbc.themes.FLATLY])  # スタイルシートを読み込む

レイアウト

dbcを利用することで、各コンポーネントを美しく整列することができます。dbc.Containerの下にdbc.Row、さらにその下にdbc.Colという階層で記述します。dbc.Containerの引数としてfluid=Trueを与えることで、不要な余白を除去して表示します。

layout = dbc.Container(
    [
        dbc.Row(
            [
                dbc.Col(html.Div("Hello"), width=2),
                dbc.Col(html.Div("World"), width=10),
            ],
        ),
    ],
    fluid=True,
)

コンポーネント

その他、dbcではさまざまなコンポーネントが用意されており、これらを組み合わせることでよりリッチなレイアウトを作成できます。アクションアイテムはDash組み込みのコンポーネント(Dash Core Component: dbc)と同様にコールバック利用も可能です。

例えば、以下のようなコンポーネントが用意されています。

ナビゲーションバー

dbc_navbar.png

フォーム

dbc_form.png

グラフの状態の保持

デフォルトの振る舞いでは、ユーザーがグラフの表示範囲を変更したのち、適当なタイミングでコールバックが呼ばれると、表示範囲がデフォルトの状態にリセットされてしまいます。これは、場合によってはユーザー体験を損ねる原因になります(例えば、地図上である範囲にフォーカスしてインタラクティブな操作を行いたい場合など)。

figureのlayout属性でuirevision=Trueと設定すればコールバックが呼ばれても状態を保持することができます(詳細は上記リンク参照)。

コールバック関連

コールバックへの複数のインプット

コールバックに複数のInputを与えることも可能です。これにより、Inputに紐づけられた複数のコンポーネントのいずれかが操作されたタイミングで発火し、Outputに紐づけられたコンポーネントの状態が変化します。

@callback(
    Output(component_id="figure", component_property="figure"),
    Input(component_id="dropdown", component_property="value"),
    Input(component_id="checklist", component_property="value"),
    State(component_id="state", component_property="data"),
)
def update_figure(files: List[str], checklist: List[str], state: str):
    ....

コールバック間のデータのやり取り

Dashでは、複数のコールバック間でデータのやり取りをするための機能としてdcc.Storeが提供されています。例えば、アップロードしたデータを重い処理にかけたのち、その結果を複数の図で表示させたい場合、コールバックごとに行うのではなく、dcc.Storeを介してデータを共有することで効率的に実行することができます。

app.layout = html.Div([
    dcc.Graph(id='graph'),
    dcc.Dropdown(id='dropdown'),
    dcc.Store(id='intermediate-value')
])

@callback(
    Output('intermediate-value', 'data'),
    Input('dropdown', 'value')
)
def convert_data(value):
     df = processing(value)
     return df.to_json(date_format='iso', orient='split')

@callback(
    Output('graph', 'figure'),
    Input('intermediate-value', 'data')
)
def update_graph(data):
    df = pd.read_json(data, orient='split')
    figure = create_figure(df)
    return figure

ただし、dcc.Storeはデータをユーザーのブラウザセッション上に保存するため、重いデータを保存するのには向いていません(ネットワークコストが高くつく)。また、データをやり取りするためにjson形式にフォーマットする必要があり、やや手間がかかります。

そのため、場合によってはサーバーのファイルシステムなどに処理結果を保存し、dcc.Storeには処理が完了したことを知らせるシグナルとしての役割を任せる、などの使い方の方が望ましいかもしれません(詳細は上記のリンクを参照ください)。

その他

ファイルのアップロード

Dash組み込みのアップローダーとしてはdcc.uploaderが提供されていますが、ファイルサイズに制限がある、ファイルの種類にかかわらずbase64形式の文字列でエンコーディングされるためパースが必要、などの点でややクセがあります。そこで、代替として3rd-party製のdash-uploaderがオススメです。dash-uploaderの方が作りがシンプルで、かつ

  • プログレスバーの表示機能
  • 指定したディレクトリ(/tmp/など)へのファイルの保存機能

などの機能をデフォルトでサポートしており使い勝手がよいです。

import dash_uploader as du

app = dash.Dash(__name__)
du.configure_upload(app, "/tmp/uploads")  # ファイルをアップロードするディレクトリを設定

app.layout = html.Div([
    du.Upload(),  # コンポーネントを設定
])

dash-uploaderは依存関係によってはマルチページだとうまく動作しない問題があるようです。対処法は下のリンクを参照ください。

認証

Dashではdash-authという簡易的な認証の仕組みもサポートされています。

VALID_USERNAME_PASSWORD_PAIRS = {'hello': 'world'}  # ユーザー名とパスワードの組

app = Dash(__name__)
auth = dash_auth.BasicAuth(app, VALID_USERNAME_PASSWORD_PAIRS)  # 認証を設定

デプロイ

デプロイ時には、gunicornをインストールしエントリーポイントとしてgunicorn app:serverを実行します。

# app.py
app = Dash(__name__)
server = app.server  # これが必要
app.layout = html.H1('Hello World')

if __name__ == '__main__':
    app.run(debug=True)
# Procfile
web: gunicorn app:server
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1