この記事では以前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)。
目的とする描画結果。選択されたノード(赤)、隣接したノード(黄)、その他(青)
まずは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で保存することができ、ファイルの受け渡しのみでインタラクティブな可視化結果を共有することができる利点があります。(こちらのリンクで実際の動作を確認いただけます)
一方で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調べ)。
上とほぼ変わりませんが、jupyter で行う場合は少しコードを変える必要があります(主にドキュメント周り)。こちらにコードがありますのでご参考ください(https://github.com/satorin1204/bokeh-interactive-example/blob/main/notebook/example_python_callback.ipynb )。
ノードをインタラクティブに選択することはできませんが、選択したいノードをテキストやボタン等の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でも動作することができます。
興味のある方は 採用ページ も見ていただけると嬉しいです。
Twitter @mot_techtalk のフォローもよろしくお願いします!