Python pandas のデータを Highcharts/Highstock + Jupyter でプロットしたい
R を使っている方はご存知だと思うが、R には {htmlwidgets}
というパッケージがあり、R 上のデータを任意の Javascript ライブラリを使ってプロットすることが比較的カンタンにできる。{htmlwidgets}
って何?という方には こちらの説明がわかりやすい。
同じことを Python + pandas
を使ってやりたい。サンプルとして利用する Javascript ライブラリは 上の資料と同じく Highcharts
、Highstock
にする。
補足 pandas-highcharts
という Python パッケージもあるが、このエントリでは任意の Javascript ライブラリで使えるであろう方法を記載する。
Highcharts
でのプロット
以降の操作は Jupyter Notebook
上で行う。まずは必要パッケージをロードする。
import numpy as np import pandas as pd from IPython.display import HTML
続けて、Highcharts
、Highstock
を読み込む。これは %%html
magic を使うのが楽。
%%html <script src="http://code.highcharts.com/highcharts.js"></script> <script src="http://code.highcharts.com/stock/highstock.js"></script> <script src="http://code.highcharts.com/modules/exporting.js"></script>
Highcharts
でプロットする準備ができたため、%%html
magic を使って サンプル Your first chart に記載のサンプルをプロットしてみる。これでプロット時のスクリプト / データ構造が確認できる。
%%html <div id="container" style="width:100%; height:400px;"></div> <script> plot = function () { $('#container').highcharts({ chart: { type: 'bar' }, title: { text: 'Fruit Consumption' }, xAxis: { categories: ['Apples', 'Bananas', 'Oranges'] }, yAxis: { title: { text: 'Fruit eaten' } }, series: [{ name: 'Jane', data: [1, 0, 4] }, { name: 'John', data: [5, 7, 3] }] }); }; plot(); </script>
補足 Jupyter
から実行すればアニメーションする。
pandas
のデータをプロットしたい場合は、上のスクリプトと同じように .highcharts()
の引数にあわせた形式でデータを渡し、HTML としてレンダリングしてやればうまくいきそうだ。pandas
で元データとなる DataFrame
を定義して、上のスクリプトの形式に変換していく。
df = pd.DataFrame({'Jane': [1, 0, 4], 'John': [5, 7, 3]}, index=['Apples', 'Bananas', 'Oranges']) df # Jane John # Apples 1 5 # Bananas 0 7 # Oranges 4 3
スクリプトは文字列結合で作ってもよいが、引数となる json
形式に対応する辞書型のデータをつくってからjson.dumps
したほうが簡単だろう。DataFrame.to_json
でデータを直接 変換できると楽なのだが、フォーマットが違うため無理そうだ。
# NG! df.to_json() # '{"Jane":{"Apples":1,"Bananas":0,"Oranges":4}, # "John":{"Apples":5,"Bananas":7,"Oranges":3}}'
そのため、個々の要素ごとに変換を考えていく。うまいこと辞書ができたら json.dumps
する。
[{'name': c, 'data': col.tolist()} for c, col in df.iteritems()] # [{'data': [1, 0, 4], 'name': 'Jane'}, {'data': [5, 7, 3], 'name': 'John'}] chartdict = {'chart': {'type': 'bar'}, 'title': {'text': 'Fruit Consumption'}, 'xAxis': {'categories': df.index.tolist()}, 'yAxis': {'title': {'text': 'Fruit eaten'}}, 'series': [{'name': c, 'data': col.tolist()} for c, col in df.iteritems()] } import json json.dumps(chartdict) # '{"series": [{"data": [1, 0, 4], "name": "Jane"}, {"data": [5, 7, 3], "name": "John"}], # "yAxis": {"title": {"text": "Fruit eaten"}}, "chart": {"type": "bar"}, # "xAxis": {"categories": ["Apples", "Bananas", "Oranges"]}, # "title": {"text": "Fruit Consumption"}}'
あとは 必要な HTML / Javascript のテンプレートを文字列として作って format
すればよい。
template = """ <script src="http://code.highcharts.com/highcharts.js"></script> <script src="http://code.highcharts.com/modules/exporting.js"></script> <div id="{chart}" style="width:100%; height:400px;"></div> <script type="text/javascript"> plot = function () {{ $("#{chart}").highcharts({data}); }}; plot(); </script> """ HTML(template.format(chart='container2', data=json.dumps(chartdict))) # 略
Highstock
でのプロット
同様に、Highstock
へプロットすることもできる。サンプル Single line series を pandas
のデータを使ってプロットしてみる。
補足 株価の取得には以下のパッケージを使う。
import japandas as jpd toyota = jpd.DataReader(7203, 'yahoojp', start='2015-01-01') toyota.head() # 始値 高値 安値 終値 出来高 調整後終値* # 日付 # 2015-01-05 7565 7575 7416 7507 9515300 7507 # 2015-01-06 7322 7391 7300 7300 12387900 7300 # 2015-01-07 7256 7485 7255 7407 11465400 7407 # 2015-01-08 7500 7556 7495 7554 10054500 7554 # 2015-01-09 7630 7666 7561 7609 10425400 7609
Highstock
に渡すデータの形式は以下のサイトがわかりやすい。引数としては [UNIX時間(ミリ秒), 始値, 高値, 安値, 終値]
を入れ子のリストにして渡せばよいようだ。
時刻は UNIX時間で渡す必要があるので、少し操作が必要だ。上で取得した DataFrame
は日時型の Index
である DatetimeIndex
を持っている。DatetimeIndex
はUNIXエポックを基準とした現在時刻をナノ秒で保存しているため、int
型に変換して 1000000 で割ればUNIX時間(ミリ秒)となる。したがって、Highstock
へ渡すデータは以下のようにして作れる。
toyota.index.astype(int) / 1000000 # array([1420416000000, 1420502400000, 1420588800000, 1420675200000, # 1420761600000, 1421107200000, 1421193600000, 1421280000000, # .... # 1433721600000, 1433808000000, 1433894400000, 1433980800000, # 1434067200000]) toyota['time'] = toyota.index.astype(int) / 1000000 toyota[['time', u'始値', u'高値', u'安値', u'終値']].values.tolist() # [[1420416000000, 7565, 7575, 7416, 7507], # [1420502400000, 7322, 7391, 7300, 7300], # ... # [1433980800000, 8250, 8326, 8241, 8322], # [1434067200000, 8387, 8394, 8329, 8394]]
描画する。アニメーションも含め、うまく動いているようだ。
chartdict = {'rangeSelector': {'selected': 1}, 'title': {'text' : 'Stock Price'}, 'series': [{'name' : u'トヨタ', 'data': toyota[['time', u'始値', u'高値', u'安値', u'終値']].values.tolist(), 'tooltip': {'valueDecimals': 2}}]} template = """ <div id="{chart}" style="width:100%; height:400px;"></div> <script type="text/javascript"> plot = function () {{ $('#{chart}').highcharts('StockChart', {data}); }}; plot(); </script> HTML(template.format(chart='container3', data=json.dumps(chartdict)))
まとめ
pandas
のデータを任意の Javascript ライブラリでプロットする際には、
- 当該の Javascript ライブラリが利用するデータ構造を確認する
- そのデータ構造にあうように
pandas
のデータを変換し、辞書型を作る json.dumps
でスクリプトに渡す
とやればだいたいできると思う。
補足 ここで今 話題の spyre
上でもプロットしたいな?と思ったのだが、spyre
で普通に HTML としてレンダリングするとうまく動かない。spyre
が内部で使っている cherrypy
も DOM を書き換えているのが原因かと思っているのだが、自分にはそのあたりの知識がまったくないのでよくわからない。できた方がいれば教えてください。
Pythonによるデータ分析入門 ―NumPy、pandasを使ったデータ処理
- 作者: Wes McKinney,小林儀匡,鈴木宏尚,瀬戸山雅人,滝口開資,野上大介
- 出版社/メーカー: オライリージャパン
- 発売日: 2013/12/26
- メディア: 大型本
- この商品を含むブログ (9件) を見る