データサイエンティスト/新規事業担当者に贈るDashの勧め

[VISITS Technologies Advent Calendar 2019](https://qiita.com/advent-calendar/2019/visits) 8日目は@woods0918が担当します。（初のQiita投稿です!）
業務では、蓄積されたデータを使った新機能や新プロダクトの企画/PoCや、社内外のデータ利活用支援を担当しています。
今回は、インタラクティブなWebアプリケーションを高速で作ることのできる[Dash](https://plot.ly/dash/)というフレームワークを、実際のプロトタイプ作成を通して紹介していきます。

## Dashとは
Dashとは、PythonだけでインタラクティブなWebアプリケーションを作ることのできるOSSのフレームワークで、可視化ライブラリで有名なPlotly社により開発されています。
[ソースコード](https://github.com/plotly/dash)を見ると、FlaskとReactとPlotlyを使って実装されているようです。
Dashの特徴を3つ紹介いたします。

### インタラクティブなWebアプリケーションを素早く作ることができる
Dash user guideの[イントロダクション](https://dash.plot.ly/introduction)には、以下のような説明があります。
> Through a couple of simple patterns, Dash abstracts away all of the technologies and protocols that are required to build an interactive web-based application. Dash is simple enough that you can bind a user interface around your Python code in an afternoon.

要点として、
・ インタラクティブなWebアプリケーションを実装するために必要なテクノロジーやプロトコルが全て抽象化されてシンプルなパターンに落とし込まれている。
・ そのシンプルなパターンを使えば、ある日の午後の時間だけでUIを実装することができる。

実際に使ってみた感想としても、Jinja2のようなテンプレートエンジンでは実装が難しいような複雑でインタラクティブなUIを短時間で実装できるため、非常に重宝しています。

### Pythonだけで作ることができる
インタラクティブなWebアプリケーションを作ろうと思うと、フロントエンドを実装する際にどうしてもJavaScriptを書かないといけない場合が多いと思います。
データサイエンティストであればPythonに馴染みのある方が多く、最近のブームから非エンジニアでもPythonなら分かるという人が増えているように思います。
そのような方たちがインタラクティブなWebアプリケーションを作ろうと思ったときに、JavaScriptがハードルとなるケースが多くあると感じていました。
Dashを使えば、そのようなハードルを取り払うことができます。

### Plotly.pyで作成したグラフとスムーズに連携できる
Plotly.pyは、Pythonでインタラクティブなグラフを作成できる可視化ライブラリです。Dashは、Plotly社が作っているフレームワークなだけあって、Plotlyとの連携がスムーズです。
例えば、散布図のある点をクリックしたときに、その点の位置やデータを取得できたり、クリックを起点に画面を遷移させるような処理が簡単に実装できます。

## 本記事で作成するプロトタイプ
今回は、Excel analysisという仮想のアプリケーションのプロトタイプ作成を通して、DashによるWebアプリケーションの実装方法を紹介していきます。
Excel analysisは、ユーザがアップロードしたexcel/csvファイルをブラウザ上でインタラクティブに可視化し、可視化した結果をHTMLファイルでダウンロードできるアプリケーションです。
以下のよりアクセスすることも可能です。
[Excel analysisにアクセス](https://excel-analysis.herokuapp.com/)

### Excel analysisの操作サンプル
Kaggle tutorialで有名なTitanicのデータを例にしたサンプルです。
1. まずcsvファイルをアップロードし、分析を開始します。
2. 次にX軸やY軸を選択し、散布図を表示します。
3. 最後に、表示した散布図をHTML形式でダウンロードします。
![demo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270069/6348acf6-f041-5519-d284-4efb0cb86856.gif)


## 設計
### ページ
ユーザがexcel/csvをアップロードするページと、アップロードされたファイルをもとに分析する画面の2つを実装する必要があります。
それぞれの画面は以下のようにURLと紐付けます。

| 画面名            | URL |
|:-----------------|-------------------|
| アップロード画面      | / |
| 分析画面           | /analysis |

### 共通コンポーネント
HeaderやFooterといった共有する要素は、各ページごとに実装するのではなく、共通のコンポーネントとして実装して使いまわせるような方針で実装します。

### 機能
主に、以下のような機能を開発する必要があります。
・アップロードするファイルパスをユーザから受け取る機能
・指定されたファイルパスからファイルをアップロードする機能
・アップロードされたファイルからカラムの値を読み取り、グラフの設定をするフォームを生成する機能
・グラフ設定フォームに入力された値をもとにグラフを描画する機能
・現在描画されているグラフをダウンロードする機能

### その他
ログイン機能の設計やデータ永続化のためのDBのテーブル設計は今回は行いません。

### ディレクトリ設計
上記設計をもとに、Dashの[ドキュメント](https://dash.plot.ly/urls)を参考にしつつ、以下のようなディレクトリ構造としました。
> app.py     (アプリケーションの設定)
  index.py   (アプリケーションの設定)
  apps       (アプリケーションの設定)
  ├─ top.py
  ├─ analysis.py
  components (アプリケーションの設定)
  ├ header.py
  ├ footer.py
  utils
  ├ 各機能の実装
  assets     (アプリケーションの設定)
  ├ (.cssファイル)
  ├ (画像ファイル)
  resources  (アプリケーションの設定)
  ├ (ダウンロードするファイルの一時保管場所)

以下では、上記の設計に基いた実装のうち、Dash特有の実装をいくつか紹介していきます。なお、全コードは以下を参照してください。
https://github.com/woods0918/excel-analysis

## 必要なライブラリ
``` requirements.txt
dash==1.5.1
dash-bootstrap-components==0.7.1
dash-core-components==1.4.0
dash-html-components==1.0.1
dash-table==4.5.0
```

## アプリケーションの設定

``` app.py
import dash, flask, os
import dash_bootstrap_components as dbc

# Dashアプリケーションの生成、external_stylesheetsでbootstrapを読み込む
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])

# Dashアプリケーションの設定
app.config['suppress_callback_exceptions'] = True # callbackに関する設定
app.title = APP_TITLE # アプリタイトルに関する説明
server = app.server
server.route('/')
def favicon():
    return flask.send_from_directory(os.path.join(server.root_path, 'assets'),'favicon.ico')

```

## Routingの設定
``` index.py
app.layout = html.Div([
    # ~略~
    dcc.Location(id='url', refresh=False), # 現在表示されているページのURLを表す
    html.Div(id='page-component'), # URLの遷移に合わせて変化する部分
    # ~略~
], className="root")

# Callbackについては後ほど解説
@app.callback(
    Output('page-component', 'children'),
    [
        Input('url', 'pathname')
    ])
def display_app(pathname):
    if pathname == '/': # "/"へアクセスがあった場合、topページが返却される
        return top.generate(class_name="top-page")
    elif pathname == "/analysis":　# "/analysis"へアクセスがあった場合、analysisページが返却される
        return analysis.generate(class_name="analysis-page")
    else: # 上記以外のアクセスは、topページが返却される
        return top.generate(class_name="top-page")
```

## 画面の実装
・　HTMLの構造は、dash-html-componentsを使用して実装していきます。
・　インタラクティブなコンポーネント（例: Dropdown）はdash-core-componentsを使用して実装していきます。
・　Bootstrapのコンポーネント（例: Jumbotron）はdash-bootstrap-componentsを使用して実装していきます。


``` header.py
# 全体をDivタグで設定
html.Div([
    # トップ見出しを生成
    dbc.Jumbotron([
        dbc.Container([ 
            html.H1(APP_TITLE, className="display-3"),
            html.P(
                "誰でもExcelを簡単に分析できるサービスです"
            )
        ])
    ], className="top-message-area"),
    html.Div([
        # アップロード機能をせいせ
        dcc.Upload(
    # ~略~
], className=class_name)
```

## Callbackの実装
・  Callbackを使うと、アプリケーション上の何かしらの変化を受け取って、アプリケーション上の何かしらを更新する処理を実装することができます。
・ Outputでは、更新先のコンポーネントのIDとプロパティを指定します。
・ Inputでは、変化を検知したいコンポーネントのIDとプロパティを指定します。
・ Stateでは、処理に必要となる情報を含むコンポーネントのIDとプロパティを指定します。

``` bar_conditions.py
html.Div([
    html.Div([
        dbc.FormGroup([
            dbc.Label("X軸(必須)", html_for="dropdown"),
            dcc.Dropdown(
                id="bar-x-dropdown",
                options=[{"label": col, "value": col} for col in cols],
                placeholder="選択してください"
            )
        ]),
    #  ~略~
        html.Div([
            dbc.Button("表示する", id="bar-button", className="mr-1", color="success")
        ], className="bar-button-area")
    ], className="bar-conditions"),
    dcc.Graph(id="bar-plot", figure=fig),
    #  ~略~
], className="bar-area")
```

``` analysis.py
@app.callback(
    Output("bar-plot", "figure"), # 更新先はplotlyで作成したグラフ(figure)を指定
    [
        Input("bar-button", "n_clicks") # 処理のきっかけとして、表示ボタンのクリックを指定
    ],
    [
        # Dropdownに入力されている値を活用できるように指定
        State("bar-x-dropdown", "value"),
        State("bar-y-dropdown", "value"),
        State("bar-tooltip-dropdown", "value"),
        State("bar-color-dropdown", "value"),
        State("upload-data-store", "data")
    ]
)
def plot_bar(n_clicks, x, y, tooltip, color, data):
    # ~略~
        
    return bar.plot(data, x, y, tooltip, color)
```
・ 上記の実装では、「表示ボタン」を押すと、その時点で入力されているDropdownの値をもとにグラフが更新される、という動きが作れます。
・Inputをdropdownにすると、Dropdownの値が変更される度にグラフが更新される、という動きになります。

## まとめ
データサイエンティストであれば分析結果やアルゴリズムをユーザに試してもらうとき、新規事業開発担当者であればアイデアを形にしてユーザに試して欲しいとき、どうしてもGUIが必要となることが多いです。

そのような時に、フロントエンドもバックエンドの両方を学び、実装するのではPDCAのスピードが落ちてしまいます。
かといって、まだPDCAの段階であるにも関わらず、エンジニアのリソースを突っ込むのは憚れるケースが多いと思います。

Dashを学べば低い学習コストでリッチなUIを簡単に作れるようになり、自分の取り組みを検証する機会を多く持つことができます。
検証機会の増加はそのまま取り組みの成功確率の増加に繋がるはずです。

非エンジニアこそDashを学んでいただき、自らの取り組みを簡単に形にする術を身につけていただければと思います。

内容に誤りや不明点があればコメントいただけると幸いです🙇‍♂️