akiyoko blog

akiyoko の IT技術系ブログです

あなたのパンダ(pandas)止まってませんか? 〜 Bokeh と ipywidgets でインタラクティブ Jupyter のススメ

この投稿は 「Jupyter Advent Calendar 2017 - Qiita」 の 22日目の記事です。


こんにちは、パンダ好きの akiyoko です。

f:id:akiyoko:20171222151740p:plain:w250

上野動物園の赤ちゃんパンダ、シャンシャン可愛いですよね〜。
Ueno Panda Live.jp というサイトで、開園日なら毎日(ただし開園時間のみ)パンダのライブ映像が見れる(オススメは3カメ!)のは皆さんもうご存知ですよね。しかしながら、たまに小一時間ほどパンタ達がじーーーっと動かないことがあるんですね。ただ寝てるだけという。こっちはあのモフモフが愛くるしく動く姿が見たいのに!! 見たいだけなのにぃ!!!!

f:id:akiyoko:20171222124515p:plain:w400
(親子水入らずで全く動かない様子。キャプチャ元:Ueno Panda Live.jp


ハァハァ。少々取り乱してしまいましたが、ところで、あなたのパンダ(pandas)も止まってしまっていませんか? というのが今回のテーマです。 *1


最近、この本を少しずつ読んでいます。

Python 界隈で大注目のデータ分析ツール「Jupyter Notebook」を中心に、1次元・2次元データを便利に扱うための「pandas」、2次元データからグラフを描画するための「Matplotlib」、インタラクティブなグラフを描画するための「Bokeh」などについて 400ページを超える大容量ボリュームで書かれており、動的・対話的なグラフを Jupyter Notebook 上に描くことが一つのゴールとなっています。


また、fin-py にも何度か参加させていただいているのですが、LT でたまに見かける、Jupyter Notebook 上でグリングリン動かせるインタラクティブなチャートに憧れを抱いていたのは私ではないはず。


いろいろ調べたり聞いたりしたところ、動的にチャートを動かす部分は Bokeh、ウィジェットの部分は ipywidgets がよさげということが分かりました。 *2

上記 Jupyter本の共著者で fin-py 主催の driller さんも昨年の「jupyter notebook Advent Calendar 2016 - Qiita」で「ipywidgetsとBokeh使ってインタラクティブな可視化をする - Qiita」というズバリそのままの記事をアップしていたりします。



一周遅れになった感はありますが、今回は「Bokeh と ipywidgets でインタラクティブ Jupyter のススメ」と称して、Bokeh と ipywidgets を使って Jupyter Notebook 上の pandas のチャートをグリングリン動かしてみたい と思います。

チャートに使うデータは、CoinMarketCap から取得した今年1月からの仮想通貨の価格、および米 Yahoo Finance から取得したドル円為替を使います。



 

Bokeh, ipywidgets のインストール

現在のローカル環境は以下の通りです。

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.12.6
BuildVersion:   16G1114

$ python -V
Python 3.5.2 :: Anaconda custom (64-bit)

 

Bokeh

Anaconda をインストールしていれば、Bokeh は標準で入っていると思います。

(参考)Anaconda package lists | Anaconda: Documentation


 

ipywidgets

一方の ipywidgets のインストール方法は、「Jupyter 本」の付録「A-1 ipywidgets の使い方」に詳しく書かれているのですが、私の環境ではそのままではうまく動きませんでした。

$ conda install -conda-forge ipywidgets

として、

from ipywidgets import interact

@interact(n=5)
def f(n):
    return n

を実行してもウィジェットが起動せず。実行するたびに、Jupyter Notebook のログに

[IPKernelApp] WARNING | Widget Javascript not detected.  It may not be installed or enabled properly.

というワーニングが出ている状態。
結局、ipywidgets が利用している widgetsnbextension のバージョンが「3.0.0」だとダメっぽいと判断。一旦、2系に戻しました。

$ conda install 'widgetsnbextension<3'

とし、

$ jupyter notebook

を止めて、再度立ち上げ直せば動くようになりました。

f:id:akiyoko:20171222140001p:plain

$ pip list | grep bokeh
bokeh (0.12.2)

$ pip list | grep widgets
ipywidgets (6.0.0)
widgetsnbextension (2.0.0)

 

データ収集

仮想通貨の日足チャート

Scrapy という Python 製のクローリング・スクレイピングツールを使って CoinMarketCap から 1月からの日足チャートを取得し、ローカルに CSVファイルとして保存しました。


《過去記事》
akiyoko.hatenablog.jp


なお、Scrapy を使った今回のスクレイピング実行コードは
github.com
に置いてあります。
実行環境は Python 3.5 です。


取得したデータはこのような形式で保存されています。


BTC.csv

symbol,date,open,high,low,close,volume,market_cap
BTC,"Dec 20, 2017",17760.30,17934.70,16077.70,16624.60,"22,149,700,000","297,526,000,000"
BTC,"Dec 19, 2017",19118.30,19177.80,17275.40,17776.70,"16,894,500,000","320,242,000,000"
BTC,"Dec 18, 2017",19106.40,19371.00,18355.90,19114.20,"14,839,500,000","320,000,000,000"
  ...
BTC,"Jan 03, 2017",1021.60,1044.08,1021.60,1043.84,"185,168,000","16,426,600,000"
BTC,"Jan 02, 2017",998.62,1031.39,996.70,1021.75,"222,185,000","16,055,100,000"
BTC,"Jan 01, 2017",963.66,1003.08,958.70,998.33,"147,775,000","15,491,200,000"

ETH.csv

symbol,date,open,high,low,close,volume,market_cap
ETH,"Dec 20, 2017",827.52,845.06,756.00,819.09,"3,969,940,000","79,810,600,000"
ETH,"Dec 19, 2017",793.90,881.94,785.34,826.82,"4,096,550,000","76,552,200,000"
ETH,"Dec 18, 2017",721.73,803.93,689.23,794.64,"3,249,230,000","69,578,400,000"
  ...
ETH,"Jan 03, 2017",8.37,10.00,8.32,9.73,"33,625,200","732,988,000"
ETH,"Jan 02, 2017",8.17,8.44,8.05,8.38,"14,579,600","714,900,000"
ETH,"Jan 01, 2017",7.98,8.47,7.98,8.17,"14,731,700","698,149,000"



ローカルに保存した仮想通貨の日足チャートの CSVファイルを全て読み込みます。1ファイルごとに 1通貨のチャートが入っているので、とりあえず行方向に連結しておきます。

import glob
import pandas as pd

pd.options.display.max_rows = 10

df_all = pd.DataFrame()
paths = glob.glob('/Users/akiyoko/PycharmProjects/ccspider/downloads/*.csv')
for path in paths:
    df = pd.read_csv(path, index_col='date', parse_dates=True)
    df = df.sort_index()
    df_all = df_all.append(df)

df_all

f:id:akiyoko:20171222141316p:plain:w400


例えば、BTC のチャートを取り出すときは以下のようにすればよいでしょう。

df_all[df_all.symbol == 'BTC']

f:id:akiyoko:20171222144226p:plain:w400


pandas.DataFrame の条件による切り出しについては、過去記事に詳しい説明があります。

《過去記事》
akiyoko.hatenablog.jp


 

ドル円為替の日足チャート

ドル円為替は、pandas-datareader というライブラリを使って、米 Yahoo Finance から日足チャートデータを取得しました。

import pandas_datareader.data as DataReader

df_usdjpy = DataReader.get_data_yahoo('JPY=X', start='2017-01-01', end='2017-12-31')
df_usdjpy

f:id:akiyoko:20171222143621p:plain:w400


pandasで為替データUSD/JPYの取得 - はしくれエンジニアもどきのメモ」を参考にさせていただきました。


それにしても、pandas-datareader スゴイ!
たった一行で為替チャートが取ってこれるのですね。他にも株価や世界人口、GDP のデータも簡単に取れるようです。

(参考)pandas-datareaderで株価や人口のデータを取得 | Python / note.nkmk.me


 

実践

移動平均線

まず、n日移動平均のチャートをハンドルで動かせるようにしてみます。ここまではほぼ「Jupyter 本」のパクリです。

from bokeh.io import output_notebook, push_notebook, show
from bokeh.plotting import figure
from ipywidgets import interact

output_notebook()
plot = figure(plot_width=600, plot_height=300, x_axis_type='datetime')
df = df_all[df_all.symbol == 'BTC']
close = plot.line(df.index, df['close'])
ma = plot.line(df.index, df['close'].rolling(10).mean(), color='red')
handle = show(plot, notebook_handle=True)

@interact(n=(0, 30))
def redraw(n=10):
    ma.data_source.data = {'x': df.index, 'y': df['close'].rolling(n).mean()}
    push_notebook(handle=handle)

f:id:akiyoko:20171223171743g:plain:w500


ハンドルを動かすと、赤色の移動平均線がグリグリ動きます。気持ちいいです。


 

ドロップダウンで通貨を入れ替え

次に、pandas.DataFrame の条件による切り出しを使って、ドロップダウンの条件によって通貨を入れ替えられるようにしてみました。

from bokeh.io import output_notebook, push_notebook, show
from bokeh.plotting import figure
from ipywidgets import interact

output_notebook()
plot = figure(plot_width=600, plot_height=300, x_axis_type='datetime')
df = df_all[df_all.symbol == 'BTC']
close = plot.line(df.index, df['close'])
ma = plot.line(df.index, df['close'].rolling(10).mean(), color='red')
handle = show(plot, notebook_handle=True)

@interact(symbol=['BTC', 'ETH', 'BCH', 'XRP', 'LTC', 'MIOTA', 'ADA', 'DASH', 'XEM', 'BTG'], n=(0, 30))
def redraw(symbol='BTC', n=10):
    df = df_all[df_all.symbol == symbol]
    close.data_source.data = {'x': df.index, 'y': df['close']}
    ma.data_source.data = {'x': df.index, 'y': df['close'].rolling(n).mean()}
    push_notebook(handle=handle)

f:id:akiyoko:20171223174020g:plain:w500


Market Cap の上位10通貨を切り替えられるようにしてみましたが、なかなか面白いです。



 

USD ⇔ JPY の切り替え

最後に、USD と JPY の価格表示が切り替えられるようなチェックボックスを付けてみました。

df に「close_jpy」列を追加し、通貨の終値(USD)とドル円為替の終値(1USD あたりの JPY)を掛けた値を入れています。なお、為替は相場が休みの日はデータが取れない(N/Aになる)ため、「fillna(method='ffill')」を使って前日のデータで補完しています。

from bokeh.io import output_notebook, push_notebook, show
from bokeh.plotting import figure
from ipywidgets import interact

output_notebook()
plot = figure(plot_width=600, plot_height=300, x_axis_type='datetime')
df = df_all[df_all.symbol == 'BTC']
close = plot.line(df.index, df['close'])
ma = plot.line(df.index, df['close'].rolling(10).mean(), color='red')
handle = show(plot, notebook_handle=True)

@interact(symbol=['BTC', 'ETH', 'BCH', 'XRP', 'LTC', 'MIOTA', 'ADA', 'DASH', 'XEM', 'BTG'], n=(0, 30), jpy=False)
def redraw(symbol='BTC', n=10, jpy=False):
    df = df_all[df_all.symbol == symbol]
    # interpolate() causes ValueError: Out of range float values are not JSON compliant... why?
    #df = df.assign(close_jpy=(df['close'] * df_usdjpy['Close']).interpolate())
    df = df.assign(close_jpy=(df['close'] * df_usdjpy['Close']).fillna(method='ffill'))
    y = df['close_jpy'] if jpy else df['close']
    close.data_source.data = {'x': df.index, 'y': y}
    ma.data_source.data = {'x': df.index, 'y': y.rolling(n).mean()}
    push_notebook(handle=handle)

f:id:akiyoko:20171223173442g:plain:w500


全体的に 5月、12月に何があったの??という感じのチャートになっています。


 

まとめ

pandas.DataFrame は時系列データを保持したりチャートを描いたりするのに非常に便利に使えますが、Bokeh と ipywidgets でインタラクティブにチャートを描画できるようにすれば、見栄えもよくもっと強力なツールになります。


そんな Bokeh や ipywidgets についてもしっかり書かれている「Jupyter 本」はスゲエなってことを今回再認識しました。つまり、買えってことです(笑)。


ちなみに今回の Advent Calendar では当初、pandas.DataFrame の resample を使ってインタラクティブに5分足 ⇔ 1時間足 ⇔ 日足といったようにチャートを書き換えられるようにしたかったのですが、うまく動かすことができませんでした。figure の X軸を書き換えるのは難しいのかもしれませんね。。


明日は、u1and0 さんの「Jupyter Advent Calendar 2017 - Qiita」 23日目の記事です。よろしくお願いします。




 

おまけ

今回の件とは直接関係ありませんが、notebook 保存時に以下のエラーが出てファイルを保存できなくなって困ってしまいました。

f:id:akiyoko:20171222150328p:plain

結局、
python - plotly plots in jupyter notebooks: Validation fails when saving - Stack Overflow
に書かれているように、

$ conda update nbformat

を更新してから、

$ jupyter notebook

を止めて再度立ち上げ直せば、保存できるようになりました。

原因は不明ですが、急に動かなくなると焦りますよね。

*1:なお、pandas の名前の由来は「panel data」らしいです。 pandasで使われるデータ構造 ~1次元、2次元、3次元のデータの扱い方~ - 今日も窓辺でプログラム

*2:ipywidgets については「Jupyter 本」にも説明があります。