読者です 読者をやめる 読者になる 読者になる

StatsFragments

Python, R, Rust, 統計, 機械学習とか

Python spyre によるデータ分析結果のWebアプリ化

R を使っている方はご存知だと思うが、R には {Shiny} というパッケージがあり、データ分析の結果を インタラクティブな Web アプリとして共有することができる。{Shiny} って何?という方には こちらの説明がわかりやすい。

qiita.com

Python でも {Shiny} のようなお手軽可視化フレームワークがあるといいよね、とたびたび言われていたのだが、spyre という なんかそれっぽいパッケージがあったので触ってみたい。

github.com

インストール

pip で。

pip install dataspyre

使い方

現時点で ドキュメンテーションはない ので、README と examples ディレクトリを見る。サンプルとして株価を取得してプロットするWebアプリを作ってみたい。spyre で Webアプリを作る手順は以下の3つ。

  1. spyre.server.App を継承したクラスを作る。
  2. 描画をコントロール/指示するクラス変数を指定する。
  3. 描画を行うメソッド getData, getPlot を書く。メソッド名は描画内容 (output_type) ごとに固定。

最初は examples のものを書き換えながら作るのが楽。各クラス変数/メソッドは相互に関連するため、プログラム全体を示した上で必要と思われる箇所にコメントを入れた。

補足 株価の取得には以下のパッケージを使う。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from spyre import server

import pandas as pd
pd.options.display.mpl_style = 'default'

# あらかじめデータを取得しておく
# 終値のみを取得し、一つのDataFrameに結合
import japandas as jpd
toyota = jpd.DataReader(7203, 'yahoojp', start='2015-01-01')[[u'終値']]
toyota.columns = [u'トヨタ']
honda = jpd.DataReader(7267, 'yahoojp', start='2015-01-01')[[u'終値']]
honda.columns = [u'ホンダ']
df = toyota.join(honda)


# spyre.server.App を継承したクラスを作る
class StockExample(server.App):
    title = u"株価のプロット"

    # 左側のペインに表示する UI 要素を辞書のリストで指定
    # ここではドロップダウン一つだけを表示
    inputs = [{"input_type":'dropdown', 
               # ドロップダウン自体の表示ラベル
               "label": 'Frequency',
               # ドロップダウンの選択項目を指定
               # label はドロップダウン項目の表示ラベル
               # value は各項目が選択された時にプログラム中で利用される値
               "options" : [ {"label": "月次", "value":"M"},
                             {"label": "週次", "value":"W"},
                             {"label": "日次", "value":"B"}],
               # 各 UI 要素の入力は各描画メソッド (getData, getPlot) に
               # 辞書型の引数 params として渡される
               # その辞書から値を取り出す際のキー名
               "variable_name": 'freq',
               "action_id": "update_data" }]

    # 画面を更新する設定
    controls = [{"control_type" : "hidden",
                 "label" : "update",
                 "control_id" : "update_data"}]

    # 描画するタブの表示ラベルを文字列のリストで指定
    tabs = [u"トヨタ", u"ホンダ", u"データ"]

    # tabs で指定したそれぞれのタブに描画する内容を辞書のリストで指定
    outputs = [{"output_type" : "plot", # matplotlib のプロットを描画する
                "output_id" : "toyota", # 描画するタブに固有の id
                "control_id" : "update_data",
                "tab" : u"トヨタ",       # 描画するタブの表示ラベル (tabs に含まれるもの)
                "on_page_load" : True },

               {"output_type" : "plot",
                "output_id" : "honda",
                "control_id" : "update_data",
                "tab" : u"ホンダ",
                "on_page_load" : True },

               {"output_type" : "table", # DataFrameを描画する
                "output_id" : "table_id",
                "control_id" : "update_data",
                "tab" : u"データ",
                "on_page_load" : True }]

    def getData(self, params):
        """
        output_type="table" を指定したタブを描画する際に呼ばれるメソッド
        DataFrameを返すこと

        params は UI 要素の入力 + いくつかのメタデータを含む辞書
        UI 要素の入力は inputs で指定した variable_name をキーとして行う
        """
        # ドロップダウンの値を取得
        # 値にはユーザの選択によって、options -> value で指定された M, W, B いずれかが入る
        freq = params['freq']

        # freq でグループ化し平均をとる
        tmp = df.groupby(pd.TimeGrouper(freq)).mean()
        return tmp

    def getPlot(self, params):
        """
        output_type="plot" を指定したタブを描画する際に呼ばれるメソッド
        matplotlib.Figureを返すこと
        """
        tmp = self.getData(params)

        # 同じ output_type で複数のタブを描画したい場合は、 params に含まれる
        # output_id で分岐させる
        # output_id は タブの表示ラベルではなく、outputs 中で指定した output_id
        if params['output_id'] == 'toyota':
            ax = tmp[[u'トヨタ']].plot(legend=False)
            return ax.get_figure()
        elif params['output_id'] == 'honda':
            ax = tmp[[u'ホンダ']].plot(legend=False)
            return ax.get_figure()
        else:
            raise ValueError


app = StockExample()
# port 9093 で Webサーバ + アプリを起動
app.launch(port=9093)

実行後、ブラウザで http://127.0.0.1:9093 を開くと以下のような画面が表示される。inputs で指定した項目が左側のメニューに、 tabs で指定した項目がタブとして表示されている。

f:id:sinhrks:20150613001354p:plain

ドロップダウンやタブの選択を切り替えると、動的にグラフやデータが更新されることがわかる。

f:id:sinhrks:20150613001400p:plain

DataFrame の描画はちょっとおかしく、index である日時自体が描画されずにその名前 ("日付") だけが表示されている。これは後日 issue あげて直そうかと思う。

f:id:sinhrks:20150613001406p:plain

06/13追記 index を描画しないのは spyre の仕様で、そのとき名前が残ってしまうのは pandas のバグです。v0.17.0で修正予定。

まとめ

お手軽可視化フレームワーク spyre を試してみた。現行の {Shiny} と比べると 機能/UI とも十分ではないが、いくつかのデータ / プロットをインタラクティブに共有する用途であれば十分使えそうだ。

使いごこちの印象として、はじめて {Shiny} を触ったあの日の気持ちを思い出したような気がする、、。