Blow Up by Black Swan

PythonーWEBアプリケーションフレームワークDashの使い方(plotly.py関連)①

今回は、plotly.js,React.jsを基礎とするplotly.pyを利用したPythonのWebアプリケーションフレームワークDashのチュートリアルをまとめました。Pythonでのグラフ表示ではmatplotlibが使われることが多いようですが、plotly.pyやDashもそのデザイン性や使い勝手から、最近注目を集めて始めているようです。

まだ勉強段階ですので、基本的には公式ドキュメントのチュートリアルをまとめた内容になっています。文章を訳して載せるのもありだと思いますが、私の勉強スタイルである箇条書き方式を取っています。

また、WEBアプリ系の構築はまだまだ不慣れなため、随所で私個人のまとめメモも入れたままになっています。首を傾げるところがありましたら、公式ドキュメントを確認して頂ければと思います。なお、Dashは2018年11月22日時点で最新のバージョンである0.30.0を使用し、公式ドキュメントもこの時点のものを参照しています。

また分量の関係から3部構成になっていますが、目次は公式ドキュメンに沿うようにしています。

  • 記事①(本記事): イントロ、Part.1〜Part.2
  • 記事②: Part.3〜Part.5
  • 記事③: Part.6、7、FAQ

他の記事については以下になります。

Introduction to Dash

  • Dashは、生産性の高いwebアプリケーションを構築を可能とするPythonのWebフレームワーク
  • Flask、Plotly.js、React.jsを基礎とし、幅広くカスタマイズでき、Pythonのみで実装できるユーザインターフェイスを備えているため、データ可視化アプリの開発には最適
  • Pythonでデータを処理する人にとっては特に適している
  • シンプルな記法によって、Dashはインタラクティブなウェブベースアプリケーションを構築するために必要とされる、あらゆる複雑な技術やプロトコルを気にしないで済むようになっている
  • Dashは、午後のひとときの時間だけで、ユーザインタフェースを持つアプリケーションを作成できるくらいシンプルに作りとなっている
  • Dashアプリは、webブラウザで読み取られる
  • 利用者は、サーバにアプリをデプロイし、URLを通してアプリをシェアすることができる
  • Dashアプリは、webブラウザで視聴されるので、もともとクロスプラットフォームと可搬性という特徴を持つ

Part 1. インストール

  • 以下のパッケージはPythonの2系、3系ともにサポート
pip install dash==0.21.1  # The core dash backend
pip install dash-renderer==0.13.0  # The dash front-end
pip install dash-html-components==0.11.0  # HTML components
pip install dash-core-components==0.24.1  # Supercharged components
pip install plotly --upgrade  # Plotly graphing library used in examples
  • dash-html-components…htmlタグとリンクするパッケージ
  • dash-core-components…UI用に提供されるコンポーネントの中でコアとなるコンポーネントを提供しているパッケージ

Part 2. The Dash Layout

  • layoutは、アプリケーションの構造を記述するものであり、 明示的なDashコンポーネントを組み合わせることで構成される
  • この章では、6つの自己充足的なアプリケーションを通して、Dashアプリケーションの基本的なパート、Dashlayoutを学ぶ
  • Dashアプリケーションは、2つの要素から構成される
    • 1つが、この章で扱う、アプリケーションの構造を記述するlayout
    • もう1つが次の章で扱うコールバック
  • Dashでは、アプリケーションの可視化に関わる、あらゆるコンポーネントに対し、Pythonのクラスを提供している
    • htmlタグに関わるdash-html-components, UI用のコアコンポーネントを提供するdash-core-components
  • 開発チームが、dash_core_componentsdash_html_componentsにある一連のコンポーネントをメンテナンスしているが、利用者自身でもJavaScriptとReact.jsを利用してオリジナルコンポーネントを構築することもできる
  • *コンポーネントとは…部品やモジュールのようなもの。事前に定義しておくことで使い回しができる。
#app1.py
# -*- coding: utf-8 -*-
import dash
import dash_core_components as dcc
import dash_html_components as html

app = dash.Dash()  #インスタンスの作成

#レイアウトでアプリケーションの構造を記述
#html.Divコンポーネント
#   -->子コンポーネント[html.H1, html.Div, dcc.Graph]
app.layout = html.Div(
    children=[
        html.H1(children='Hello Dash'),
        html.Div(
            children='''
            Dash: A web application framework for Python.
            '''),
        dcc.Graph(  #Graphメソッドは引数にplotlyのFigureオブジェクトをとる(Figureオブジェクトの記法はplotlyに従う)
            id='example-graph',
            figure={
                'data': [
                    {'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'bar', 'name': 'SF'},
                    {'x': [1, 2, 3], 'y': [2, 4, 5], 'type': 'bar', 'name': u'Montréal'},],
                'layout': {
                    'title': 'Dash Data Visualization'
                }
            }
        )
    ]
)
  1. layoutは、コンポーネントのツリーから構成される。html.Divdcc.Graph
  2. dash_html_componentsライブラリは、あらゆるHTMLタグに対応するコンポーネントを持つ
    • html.H1(children='Hello Dash')は、Webアプリ上で<h1>Hello Dash</h1>HTMLを生成する
  3. 全てのコンポーネントが純粋なHTML記法ではない。dash_core_componentsは、React.jsライブラリを通じて、インタラクティブで、HTMLやCSS、JSによって生み出される高次のコンポーネントを記述する
  4. それぞれのコンポーネントは、もっぱらキーワード属性を通して記述される。Dashは宣言型であり、利用者は最初に、これらの属性を通じて、アプリケーションを記述する(style=children=など)
  5. childrenプロパティは特別なもの。慣例的に第一引数になるが、これはchildren=の記載を省略できることを意味する。
    • childrenは子要素のことを指す
    • html.H1(children='Hello Dash')html.H1('Hello Dash')と同義。
    • childrenは、文字以外に数字、シングルコンポーネント、コンポネーントのリストをその要素に取ることができる。
  6. チュートリアルとこのコードとのフォントが少し異なっているのは、カスタムスタイルシートを利用しているため。
    CSSをいじるかapp.css.append_css({"external_url": "https://codepen.io/chriddyp/pen/bWLwgP.css"})を挿入することで、同じフォントにすることができる
    ->実際にフォントが変わったのを確認済み
    • 参考リンク: more about CSS
    • ※Graphメソッドは引数にplotlyのFigureオブジェクトをとる(Figureオブジェクトの記法はplotlyに従う)

Making your first change

  • Dashは、’hot-reloading’機能を持っている
    • この機能は、app.run_server(debug=True)でアプリを走らせるときに、デフォルトでアクティベイトされる
    • これは、コードに変更があった場合に、自動でブラウザをリフレッシュする機能。–>> FLASKと同じ!!
  • ‘hot-reloading’機能をオフにしたい場合は、app.run_server(dev_tools_hot_reload=False)と指定する

More about HTML

  • dash_html_components…htmlのあらゆるタグと属性に対応している
#app2.py
# -*- coding: utf-8 -*-
import dash
import dash_core_components as dcc
import dash_html_components as html

app = dash.Dash()

colors = {
    'background': '#111111',
    'text': '#7FDBFF'
}

app.layout = html.Div(
    style={'backgroundColor': colors['background']},
    children=[
        html.H1(
            children='Hello Dash',  # Hello Dashと動議
            style={  #htmlではセミコロンで繋ぐところをDashでは辞書型で繋いでいる
                'textAlign': 'center',
                'color': colors['text']}),
        html.Div(
            children='Dash: A web application framework for Python.',
            style={
                'textAlign': 'center',
                'color': colors['text']}),
        dcc.Graph(
            id='example-graph-2',
            figure={
                'data': [
                    {'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'bar', 'name': 'SF'},
                    {'x': [1, 2, 3], 'y': [2, 4, 5], 'type': 'bar', 'name': u'Montréal'},],
                'layout': {
                    'plot_bgcolor': colors['background'],
                    'paper_bgcolor': colors['background'],
                    'font': {
                        'color': colors['text']}
                }
            }
        )
    ]
)

if __name__ == '__main__':
    app.run_server(debug=True)  #'hot-reloading'機能が有効化

上記コードはstyle属性をもつhtml.Divhtml.H1によって、以前のものとデザインが変わっている
dash_html_componentsとhtmlの属性値には重要となる違いがいくつかあるが、htmlで利用できる全ての属性やタグはDashでも利用できる

  1. htmlでのstyle属性はセミコロン(;)表記 –> Dashではdict型で表記
  2. Dashのstyle属性の表記はキャメルケース(html:text-align –> Dash:textAligh)
    • キャメルケース…複合語を表記する場合に大文字を利用すること。ラクダ(キャメル)のこぶからきた言葉
  3. htmlのクラス属性: class="" –> Dash:className=""
  4. htmlの子要素は、Dashではchildren引数によって指定される。慣習によって常に第一属性になるが、たいてい省略される。

再利用可能なコンポーネント

  • pythonでDashで定められた記法(マークアップ)を記述することによって、言語の切り替えが不要なテーブルのように、複雑で再利用可能なコンポーネントを作り出すことができる
#app3.py
import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
df = pd.read_csv(
    'https://gist.githubusercontent.com/chriddyp/'
    'c78bf172206ce24f77d6363a2d754b59/raw/'
    'c353e8ef842413cae56ae3920b8fd78468aa4cb2/'
    'usa-agricultural-exports-2011.csv')
#再利用可能なコンポーネント
def generate_table(dataframe, max_rows=10):
    return html.Table(
        # Header(thead)
        [html.Tr([html.Th(col) for col in dataframe.columns])] +
        
        # Body(tbody)
        [html.Tr([
            html.Td(dataframe.iloc[i][col]) for col in dataframe.columns
        ]) for i in range(min(len(dataframe), max_rows))]
    )
app = dash.Dash()
app.layout = html.Div(
    children=[
        html.H4(children='US Agriculture Exports (2011)'),
        generate_table(df)
    ]
)
if __name__ == '__main__':
    app.run_server(debug=True)

可視化についてもう少し

  • dash_core_componentライブラリーはGraphと名付けられたコンポーネント持つ
  • Graphは、JavaScriptのグラフライブラリでオープンソースのplotly.jsを利用し、インタラクティブなデータの可視化を実現する
  • plotly.jsは35のチャートタイプをサポートし、クオリティの高いSVGとハイパフォーマンスなWebGLの両方で読み取る
    • SVG…一種の画像フォーマット。画像と点と線による演算によって再現し、拡大・縮小しても画質が損なわれない
    • WebGL…三次元CGを表示するための標準仕様
  • dash_core_component.Graphfigure引数は、pythonのオープンソースグラフライブラリであるplotly.pyで利用されるfigure引数と同じ
    • ->plotlyのfigureについては末尾に記載
  • 参考サイト: plotly.py公式ドキュメント
#app4.py
import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import plotly.graph_objs as go  #plotlyをインポートしている

app = dash.Dash()

df = pd.read_csv(
    'https://gist.githubusercontent.com/chriddyp/' +
    '5d1ea79569ed194d432e56108a04d188/raw/' +
    'a9f9e8076b837d541398e999dcbac2b2826a81f8/'+
    'gdp-life-exp-2007.csv')


app.layout = html.Div([
    dcc.Graph(
        id='life-exp-vs-gdp',
        figure={  #plotlyのfigureオブジェクト
            'data': [
                go.Scatter(  #plotlyの記法と同じ
                    x=df[df['continent'] == i]['gdp per capita'],
                    y=df[df['continent'] == i]['life expectancy'],
                    text=df[df['continent'] == i]['country'],
                    mode='markers',
                    opacity=0.7,
                    marker={
                        'size': 15,
                        'line': {'width': 0.5, 'color': 'white'}
                    },
                    name=i
                ) for i in df.continent.unique()],
            'layout': go.Layout(
                xaxis={'type': 'log', 'title': 'GDP Per Capita'},
                yaxis={'title': 'Life Expectancy'},
                margin={'l': 40, 'b': 40, 't': 10, 'r': 10},
                legend={'x': 0, 'y': 1},
                hovermode='closest'
            )
        }
    )
])

if __name__ == '__main__':
    app.run_server()
  • ダブルクリックなどで対話的な操作が可能

マークダウン

  • dash_html_conponenetsを利用してhtmlを書くことができるが、この労力がかかる作業をする代わりに、dash_core_componentの中のmarkdownコンポーネントを利用することもできる
  • plotly.py
  • 構造
    • マークダウンで書かれた文章をdcc.Markdownメソッドに渡す
#app4.py
import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import plotly.graph_objs as go  #plotlyをインポートしている

app = dash.Dash()

df = pd.read_csv(
    'https://gist.githubusercontent.com/chriddyp/' +
    '5d1ea79569ed194d432e56108a04d188/raw/' +
    'a9f9e8076b837d541398e999dcbac2b2826a81f8/'+
    'gdp-life-exp-2007.csv')


app.layout = html.Div([
    dcc.Graph(
        id='life-exp-vs-gdp',
        figure={  #plotlyのfigureオブジェクト
            'data': [
                go.Scatter(  #plotlyの記法と同じ
                    x=df[df['continent'] == i]['gdp per capita'],
                    y=df[df['continent'] == i]['life expectancy'],
                    text=df[df['continent'] == i]['country'],
                    mode='markers',
                    opacity=0.7,
                    marker={
                        'size': 15,
                        'line': {'width': 0.5, 'color': 'white'}
                    },
                    name=i
                ) for i in df.continent.unique()],
            'layout': go.Layout(
                xaxis={'type': 'log', 'title': 'GDP Per Capita'},
                yaxis={'title': 'Life Expectancy'},
                margin={'l': 40, 'b': 40, 't': 10, 'r': 10},
                legend={'x': 0, 'y': 1},
                hovermode='closest'
            )
        }
    )
])

if __name__ == '__main__':
    app.run_server()
dashイメージ2-5

コアコンポーネント

  • dash_core_componentは、ドロップダウンやグラフ、マークダウン、ブロックなど多くの高次元コンポーネントをもつ
  • Dashコンポーネントのように、これらのコンポーネントはもっぱら明示的に記述される。設定可能なあらゆるオプションは、コンポーネントのキーワード引数として利用できる
  • 次のページで利用可能なコンポーネントをすべて見ることができる: Dash Core Components Gallery
#app6.py
# -*- coding: utf-8 -*-
import dash
import dash_core_components as dcc
import dash_html_components as html

app = dash.Dash()

app.layout = html.Div([
  #(1)ドロップダウン
    html.Label('Dropdown'),
    dcc.Dropdown(  #children(ここでは省略されている)の引数として指定されている
        options=[
            {'label': 'New York City', 'value': 'NYC'},
            {'label': u'Montréal', 'value': 'MTL'},
            {'label': 'San Francisco', 'value': 'SF'}],
        value='MTL'),

    #(2)マルチセレクトドロップダウン
    html.Label('Multi-Select Dropdown'),
    dcc.Dropdown(    #children(ここでは省略されている)の引数として指定されている
        options=[
            {'label': 'New York City', 'value': 'NYC'},
            {'label': u'Montréal', 'value': 'MTL'},
            {'label': 'San Francisco', 'value': 'SF'}],
        value=['MTL', 'SF'],
        multi=True),

    #(3)Radio Items
    html.Label('Radio Items'),
    dcc.RadioItems(  #children(ここでは省略されている)の引数として指定されている
        options=[
            {'label': 'New York City', 'value': 'NYC'},
            {'label': u'Montréal', 'value': 'MTL'},
            {'label': 'San Francisco', 'value': 'SF'}],
        value='MTL'),

    #(4)チェックボックス
    html.Label('Checkboxes'),
    dcc.Checklist(  #children(ここでは省略されている)の引数として指定されている
        options=[
            {'label': 'New York City', 'value': 'NYC'},
            {'label': u'Montréal', 'value': 'MTL'},
            {'label': 'San Francisco', 'value': 'SF'}],
        values=['MTL', 'SF']),

    #(5)テキストインプット
    html.Label('Text Input'),
    dcc.Input(value='MTL', type='text'),  #children(ここでは省略されている)の引数として指定されている

    #(6)スライダー
    html.Label('Slider'),
    dcc.Slider(  #children(ここでは省略されている)の引数として指定されている
        min=0,
        max=9,
        marks={i: 'Label {}'.format(i) if i == 1 else str(i) for i in range(1, 6)},
        value=5,),
],   #children属性の終了
    style={'columnCount': 2}
)  #columnCount->CSS要素


if __name__ == '__main__':
    app.run_server(debug=True)
dashイメージ2-6

Calling ヘルプ

  • Dashコンポーネントは宣言的なもの
    • これらコンポーネントの設定部分全ては、インスタンス化されるときにキーワード引数としてセットされる
  • コンポーネントとその設定について学ぶために、Pythonコンソールでhelpを呼び出すと良い
#helpを呼び出した場合
import dash_core_components as dcc
help(dcc.Dropdown)

戻り値

Help on class Dropdown in module builtins:

class Dropdown(dash.development.base_component.Component)
 |  Dropdown(id=undefined, options=undefined, value=undefined, className=undefined, clearable=undefined, disabled=undefined, multi=undefined, placeholder=undefined, searchable=undefined, **kwargs)
 |  
 |  A Dropdown component.
 |  Dropdown is an interactive dropdown element for selecting one or more
 |  items.
 |  The values and labels of the dropdown items are specified in the 'options'
 |  property and the selected item(s) are specified with the 'value' property.
 |  
 |  Use a dropdown when you have many options (more than 5) or when you are
 |  constrained for space. Otherwise, you can use RadioItems or a Checklist,
 |  which have the benefit of showing the users all of the items at once.
  
(省略)
  
 |  __subclasshook__(C) from abc.ABCMeta
 |      Abstract classes can override this to customize issubclass().
 |      
 |      This is invoked early on by abc.ABCMeta.__subclasscheck__().
 |      It should return True, False or NotImplemented.  If it returns
 |      NotImplemented, the normal algorithm is used.  Otherwise, it
 |      overrides the normal algorithm (and the outcome is cached).

Part2のサマリー

  • Dashアプリのlayoutは、アプリがどのようなものかを記述する
  • layoutは、コンポーネントの階層的なツリー
  • dash_html_componentsライブラリはすべてのHTMLタグに対しクラスを提供し、キーワード引数はstyleclassNameidのようなHTML属性を記述する
  • dash_core_componentsライブラリはコントロールやグラフのような高次元のコンポーネントを記述する
  • 参考サイト1: dash_core_components gallery
  • 参考サイト2: dash_html_components
  • layoutの記法イメージ
app.layout = html.Div(
    children=[
        html.Div()],
    style={}
    ]
)