MoTLab -GO Inc. Engineering Blog-MoTLab -GO Inc. Engineering Blog-

Bokehを使ったインタラクティブな可視化ツール

AIAlgorithm
October 21, 2021

この記事では以前Bokehを使ったインタラクティブな可視化ツールの作成したときの経験に基づいて、複数の実現方法を比較・検証します。


はじめに

アルゴリズムグループの佐藤です。この記事では以前Bokeh を使ったインタラクティブな可視化ツールの作成したときの経験に基づいて、複数の実現方法を比較・検証します。なお検証にはbokeh == 2.3.1 を用いており、紹介したコードはこちらにあります(GitHub - satorin1204/bokeh-interactive-example

やりたいこと

今回は以下の要件を満たすものを作成したいとします。

  • グラフ構造を描画する
  • ノードをクリック等して選択した際に何かしらのアルゴリズムによってインタラクティブに計算し描画する

Python を用いた可視化ツールはたくさんありますが、今回はbokeh を選択しました。他にメジャーなライブラリとしてplotlyもありますが今回は扱いません。

グラフ構造の可視化

これに関してはネット上に数多くサンプルプログラムがあるので省きますが、bokeh.plotting.from_networkxを用いることでnetworkxのグラフ構造から描画することができます。

G = nx.Graph()
graph_renderer = from_networkx(G, nx.spring_layout, scale=1, center=(0, 0))
plot = Plot(width=400, height=400)
plot.renderers.append(graph_renderer)

Visualizing network graphs を参照)

インタラクティブな可視化

あるノードをクリックすることで可視化結果がインタラクティブに変わることを実現したいです。今回は「選択したノードが赤、隣接したノードは黄色、残りは青色に変わる」ことを目的とします。ただし、「選択したノードと隣接したエッジ(または選択したエッジと隣接したノード)を表示する」機能は標準で用意されています(NodesAndLinkedEdges, EdgesAndLinkedNodes)。

An image from Notion

CustomJS を用いた可視化

まずはTapToolやBoxSelectToolなど必要な選択ツールを加えます(全方法で共通です)。Jupyter notebook で作業する場合はブラウザの機能でconsole.logを確認できるようにするとデバッグがしやすくなるのでおすすめです。

Bokehでは各オブジェクトごとにJSを用いたコールバックを設定することができます。今回は「ノードオブジェクトが選択された時」が必要なので以下のように書きます。Bokehではdata_sourceと呼ばれるものにデータが格納・参照され、選択された場合data_source.selectedのindices が変化します。

code = """
    some_proces();
    # 結果を反映
    node_data.change.emit();
"""
args = {"node_data": graph_renderer.node_renderer.data_source}
callback = CustomJS(args=args, code=code)
# js_on_change である値の変化に対してCustomJS callbackを設定
graph_renderer.node_renderer.data_source.selected.js_on_change("indices", callback)
plot.add_tools(TapTool())
show(plot)

CustomJSで実装した場合htmlで保存することができ、ファイルの受け渡しのみでインタラクティブな可視化結果を共有することができる利点があります。(こちらのリンクで実際の動作を確認いただけます)

An image from Notion

Python callback を用いた可視化(Python script 版)

一方でPythonで分析している際に複雑なcallbackをJavaScriptで書くのは再利用性が悪かったりコストが大きかったりする場合があります。私はJavaScriptを書いたことがないので上の可視化に苦労しました…。そこでBokehではCustomJSを避けてPythonでcallbackを書くことができます(以下のコードはbokeh==2.4.0では正常に動作しません)。

Python callbackを用いる場合はjs_on_changeではなくon_changeを用います。

# callback はattr, old, new を引数に持つ
def callback(attr, old, new):
    some_process()
# on_change でpython callback を設定
graph_renderer.node_renderer.data_source.selected.on_change("indices", callback)
plot.add_tools(TapTool())
curdoc().add_root(plot)

こうすることでPythonで完結して書くことができます。bokeh serve --show hoge.py として実行することで結果を見ることができます。

このやり方では全てをPythonで書くことができますが、一方でhtmlに保存することができないという短所があります。またgoogle colabやsagemaker notebook等の環境では実行できないという点もあります(2021/10/12調べ)。

Python callback を用いた可視化(jupyter版)

上とほぼ変わりませんが、jupyter で行う場合は少しコードを変える必要があります(主にドキュメント周り)。こちらにコードがありますのでご参考ください(https://github.com/satorin1204/bokeh-interactive-example/blob/main/notebook/example_python_callback.ipynb )。

Jupyter ipywidges を用いた可視化

ノードをインタラクティブに選択することはできませんが、選択したいノードをテキストやボタン等のwidgetsで入力させることはできます。ipywidgetsで実装する場合はcallbackの終点でpush_notebookをすることで反映させられます。

def callback(change):
  some_process()
  push_notebook(handle=t)

text_widgets = ipywidgets.Text(value="")
text_widgets.observe(callback, names="value")

t = show(plot, notebook_handle=True)

まとめ

今回はbokehを用いた可視化について紹介しました。高級なインタラクティブな可視化にはどの方法でもちょっと大変な箇所がありますね。また本記事では紹介しませんでしたが、他にメジャーな可視化ライブラリであるplotlyは同じようにnetworkxから描画することができ、またjupyter_dashを用いることでgoogle colabでも動作することができます。


We're Hiring!

📢
Mobility Technologies ではともに働くエンジニアを募集しています。

興味のある方は 採用ページ も見ていただけると嬉しいです。

Twitter @mot_techtalk のフォローもよろしくお願いします!