プロジェクト

全般

プロフィール

Python Bokeh

Webブラウザ上にインタラクティブな操作が可能なグラフを表示するPythonのライブラリです。グラフの拡大・縮小・スクロール・初期表示にリセット、ホバー表示、データ系列毎のオン・オフ、などのといった操作が可能です。
また、ボタン、スライダー、選択リスト、チェックボックス、表などのウィジェット(ユーザー操作部品)を用意しており、ダッシュボードのような機能も実現できるライブラリとなっています。

はじめに

公式サイト: https://bokeh.org

Bokehの動作概要

Bokehライブラリを利用したグラフ表示には、次の2つの実行形態があります。

  1. Pythonプログラムを実行しHTMLファイルを生成しWebブラウザに表示
  2. BokehサーバーをPythonスクリプトを指定して実行、WebブラウザからBokehサーバーに接続し表示

HTMLファイル生成

Pythonプログラムを実行し、その中でBokehライブラリを利用してグラフをプロットすると、HTMLファイルを生成し、Webブラウザ上でそのHTMLファイルを開くと、インタラクティブ操作が可能なグラフが表示されます。Windows、Mac、LinuxデスクトップでPythonプログラムを実行すると、デフォルトのWebブラウザでHTMLファイルを開きグラフ表示するところまで自動で動きます。

このHTMLファイルのみを、PythonプログラムやPython実行環境のないPCに持っていってもインタラクティブなグラフ表示操作が可能です。

block P["Python code"] space H["HTML file"] space W["Web browser"] P--"create"-->H H--"show"-->W

グラフ表示に加えて、ボタン、スライダー、チェックボックスその他多数のユーザー操作可能な部品(Widgets)が用意されています。HTMLファイルで完結させるにはユーザー操作に対応する処理(イベントハンドラ)をJavaScriptで記述する必要があり、Pythonコード内に文字列でJavaScriptのイベントハンドラー処理を記述します。

Webサーバーとして実行

BokehサーバーをPythonプログラムを読み込み実行し、Webブラウザから接続し、Bokehサーバーとインタラクションして動作する実行形態です。この方式で実行する場合、WidgetのイベントハンドラをPythonコードで記述することができます。例えば、ユーザー操作の結果データを新たに読み直して表示を更新するといった用途で活用できます。

block P["Python Script"] ba1<["load"]>(right) B["Bokeh Server"] ba2<["HTTP"]>(x) W["Web browser"]

最初の一歩

sin関数の折れ線グラフ

sin関数を折れ線グラフにプロットします。

import numpy as np
from bokeh.plotting import figure, show

x = np.linspace(-np.pi, np.pi, 1_000_000)
y = np.sin(x)

p = figure(title="sin(x)")
p.line(x, y)
show(p)

ここでは、Numpyライブラリの ndarray 配列に、X軸の値とY軸の値をそれぞれ格納しています。
Bokehライブラリの figure()関数を呼びFigureオブジェクトを生成します。
生成したFigureオブジェクトのline()メソッドを呼んでX軸の値、Y軸の値を渡します。
実行すると、htmlファイルを生成し、Webブラウザでそのファイルを開きます。
プロットするポイント数を数万〜数十万個としても、そこそこの速さで操作できました。

  • 生成されるHTMLファイルは、デフォルトではカレントディレクトリに実行したPythonスクリプトファイル名と同じで拡張子が.htmlとなります。このファイル名は、output関数で指定したファイル名に変更可能です。

プロットデータの形式

プロットするデータを上述のサンプルでは簡便のためnumpyを使用しました。
Bokehライブラリでは、Python標準のリスト、NumPyデータ、BokehライブラリのColumnDataSource、PandasライブラリのDataFrameなどをプロットデータとして渡すことができます。リストやNumPyの配列を渡すと内部でColumnDataSourceが作られます。

オフライン環境での実行

生成されたhtmlファイルには、bokehのJavaScriptライブラリファイルをインターネットから取得する記述が含まれます。

<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-3.8.2.min.js"></script>

インターネット接続ができない環境で、このhtmlファイルを開いてグラフ表示をする場合、JavaScriptライブラリをhtmlファイルに埋め込み生成するようにします。output_file関数でファイル生成するときのmodeを"inline"と指定すると、JavaScriptライブラリファイルをhtmlに埋め込みます。

from bokeh.plotting import figure, output_file, show
output_file('trigonometric_inline.html', mode='inline')

一般的な設定

グラフのタイトル、各軸の名称

figure関数のキーワード引数に、グラフのタイトル(title)、X軸の名称(x_axis_label)、Y軸の名称(y_axis_label)を指定します。

p = figure(title="sin(x)", x_axis_label='angle', y_axis_label='sin(x)')

グラフの領域の大きさ

figure関数のキーワード引数に、横幅(width)、高さ(height)を指定します。

p = figure(title="sin(x)", x_axis_label='angle', y_axis_label='sin(x)', width=600, height=400)
  • デフォルト値はwidth、heightとも600です

ツールバーの設定

拡大縮小、移動などのツールバーの設定を行います。
使用するツールをfigureのキーワード引数に列挙します。

p = figure(tools="pan,wheel_zoom,box_zoom,crosshair,box_select,lasso_select,reset,save,help")

ツールの抜粋
pan:移動
wheel_zoom: マウスホイールで拡大縮小
box_zoom: 選択範囲にズーム
crosshair: マウスカーソルの位置を交点とする縦横直線表示
box_select: 範囲選択
lasso_select: マウスカーソルの移動軌跡で範囲選択
reset: 表示を初期状態に
save: グラフをPNG画像ファイルに保存
help: Bokehドキュメントサイトのリンク
zoom_in: 拡大
zoom_out: 縮小

ツールバーの表示位置の設定は、toolbar_locationキーワード引数に指定します。

p = figure(toolbar_location="above")

above: グラフの上
below: グラフの下
left: グラフの左
right: グラフの右

グラフの軸の設定

  • X軸、Y軸の種類(x_axis_type, y_axis_type)
    p = figure(x_axis_type="datetime", y_axis_type="log")
    

    数値の他、対数スケール(log)、日時(datetime)、地図座標(mercator)
動的な変更

figureに、軸の種類を設定し作成後、別な軸の種類に変更することはできません。
擬似的に実現するなら、あらかじめ軸の設定をした2つのfigureを用意し、visible属性を変えることでfigureを差し替える方法があります。

グラフの凡例名

一つのプロット領域に複数のデータ列を描画するときに、凡例を記載します。プロット関数(line等)のキーワード引数に凡例名(legend_label)を指定します。

p.line(x, y, legend_label='sin(x)')

線種の設定

line()メソッドで線種を設定するときは、キーワード引数に 破線(line_dash)、色(line_color)、透明度(line_alpha)、線幅(line_width)、を指定します。

  • line_dash
    line_dash=[4, 4] 実線の長さと線のない部分の長さを指定、[4, 1, 1, 1]で一点破線のような形状も指定可能
    line_dash='dashed' 破線の指定、他には 'solid'(実線)、'dotted'(点線)、'dotdash'(一点破線)、'dashdot'
  • line_color
    line_color='pink' 名前付きCSSカラーで指定
    line_color='#70C040' RGB値で指定

マーカーと線の表示

Matplotlibの 'o-' のように線とマーカーを表示したい場合、line()メソッドだけでは表現できないので、line()メソッドとscatter()メソッドの2つを使って重ね合わせて表現します。

p.scatter(x, sin_y, legend_label='sin(x)')
p.line(x, sin_y, legend_label='sin(x)')

legend_labelに同じ名前を指定することで、凡例のグラフ線種も線とマーカーが重ね合わせて表示されます。

マウスホバーでデータ値表示

グラフ上にマウスカーソルを持っていくとそのグラフの値をポップアップ表示するホバー機能を追加します。

簡単な例
p.line(x, y)
p.add_tools(HoverTool(tooltips=[("x", "$x"), ("y", "$y")], mode="vline"))
show(p)

表示例

figureオブジェクトに add_toolsメソッドでHoverToolオブジェクトを指定します。
HoverToolにはtooltips引数にホバー表示するコンテンツの定義を渡します。見出し文字列と$xか$yでX座標の値、Y座標の値を指定、複数項目のホバー表示はカンマで区切って見出しと$座標名の指定を列挙します。$座標名の部分はホバー表示の際はデータの値となります。

pandasのDataFrameを指定してプロットした場合や、ColumnDataSourceで列名とデータ列を束ねて指定しプロットした場合は、$座標 ではなく、@列名 でデータ値を指定することができます。なお、lineメソッド等にndarrayを指定した場合、列名は含まれていないのですがメソッド内部でColumnDataSourceが作られるので、そのデフォルトの列名x, yを@x, @yのように指定することもできます。

p.add_tools(HoverTool(tooltips=[("X", "@x"), ("Y", "@y")], mode="vline"))

mode="vline"を指定すると、例えばライングラフの時にライン上にマウスを寄せなくてもグラフ上にマウスカーソルを置くとそのカーソル位置がら垂直方向の点の値を表示します。他には "hline"、"mouse"が指定可能で、hlineはカーソル位置から水平方向の最寄りの点の値を表示、mouseはマウスカーソルに最寄りの点を表示し、modeのデフォルトはmouseです。

列名指定において、計算式の記述はできないので表示の数値をあらかじめ計算してDataFrameにホバー表示用の列として渡すなどの代替案があります。

表示桁数などの指定
  • $x{0.00} 小数点以下2桁
  • @$x{(0.0 a)} 数値の大きさに応じて、k, m, g のようにキロ、メガ、ギガの値で表示
    ただし、特定の桁に固定することができないので、999,900 は 999.9 kと表示、1,000,000は 1.0 m と表示されます。
複数系列をプロットする際に、特定の系列のみホバー表示
p = figure()

current = p.line(x, y)
past = p.line(px, py)

p.add_tools(HoverTool(tooltips=[("X", "@x"), ("Y", "@y")], mode="vline", renderers=[current]))

複数のデータ列を1つのグラフに表示

色を個々に指定

p.line(x, sin_y, legend_label='sin(x)') # デフォルトは'blue'
p.line(x, cos_y, legend_label='cos(x)', color='red')

データ列の表示有無操作

ショック! Bokehの凡例(Legend)は標準で系列のオン・オフを切り替える機能を持っていた!

Bokehが標準で持っている機能を知らずに実装してしまったものですが、ウィジェットの使用例として残しておきます。

2つのデータ列に対して、チェックボックスでプロットの表示有無の制御をします。
ユーザー操作を受け付ける部品をBokehではWidgetと呼んでいます。今回は、CheckBoxGroupを使ってデータ列の表示有無を受け付け、JavaScriptで表示有無の設定を切り替えます。

ソースコード全体を表示

  • lineメソッドの戻り値を変数に置いて、後ほどチェックボックスのコールバックで表示有無の際に参照
  • CheckboxGroup の生成で、labels引数にチェックボックスの表示文字列を、active引数にデフォルトでチェックを付けておくチェックボックスのインデックスを指定
  • ブラウザ上で完結する処理は、CustomJSでJavaScriptコードで記述
2つの系列を表示 1つの系列を表示

p.legend.click_policy = "hide"

複数のグラフを並べて表示

row

横にグラフを並べる場合、row関数でfigureを指定します。

from bokeh.layout import row

a_fig = figure(...)
b_fig = figure(...)

show(row(a_fig, b_fig))

column

縦にグラフを並べる場合、column関数でfigureを指定します。

from bokeh.layout import column

a_fig = figure(...)
b_fig = figure(...)

show(column(a_fig, b_fig))

gridplot

複数のグラフを行列に並べて表示します。
個々のグラフを figure で作成し、glafplot に、2次元リストでfigureを指定します。

A B
C D

のように並べる場合、

a_fig = figure(...)
b_fig = figure(...)
c_fig = figure(...)
d_fig = figure(...)

p = gridplot([[a_fig, b_fig], [c_fig, d_fig]])

上下に2つ並べる場合は、gridplot([[a_fig], [c_fig]]) のように記述します。

複数グラフのX軸表示を連動

あるグラフで拡大、縮小、パニング操作をした場合に並べて表示している他のグラフも同じ操作を適用することができます。

X軸の表示を連動する場合、連動するグラフのfigureオブジェクトのx_rangeプロパティを共有します。

a_fig = figure(...)
b_fig = figure(...)
b_fig.x_range = a_fig.x_range
show(column(a_fig, b_fig))

Widgets

グラフ以外にも様々なユーザー表示操作部品が利用できます。ボタン、スライダー、チェックボックス、テキスト入力、ドロップダウンリスト、表、カラーピッカー、日付ピッカーなどなど。

操作を受け付けるコールバック処理は、HTML上で完結するJavaScriptコード、それからbokehサーバー上で実行するPythonコードで記述します。

ユーザー操作

T.B.D.

rowでテキストとwidgetを並べる

title属性を持つSelectなどは、widgetの上に名称などを表示できます。しかし、位置を左右や下には変更できないので、そのときはテキストとwidgetをrowで並べます。rowで並べたとき、デフォルトでは高さが揃っていないのて、テキストのalignを"center"にします。

雑多(未整理)事項

グラフの操作

系列の表示制御

  • legend(凡例)をクリックすると、プロットを薄い色にする mute 操作も可能
    p.legend.click_policy = "mute"
    このとき、mute状態の表示色をあらかじめ設定する
    p.line(x, y, legend_label="sin(x)", muted_color="grey", muted_alpha=0.25)

グラフの表示

凡例の位置

凡例(legend)は、デフォルトではグラフ領域の内側、上右側に配置されます。

p = figure()
p.legend.location = "top_left" 

グラフ領域内で凡例を表示する位置は、figureオブジェクトの legend.location 属性に次の文字列で指定可能です。

"top_right"  "top_left"  "bottom_right"  "bottom_left"  "center_right"  "center_left" 
"top"  "bottom"  "center"  "left"  "right" 

グラフ領域外で凡例を表示する場合は、簡易的には次のコードで設定可能です。

p = figure()
p.line(...)
p.line(...)
legend = p.legend[0]
p.add_layout(legend, "right")

レイアウト

データソース

データソース(ColumnDataSource)を使用すると、データソースのデータを変更することでプロットに反映することができます。

データソースを使ってプロット

データソース(ColumnDataSource)を作ってsin関数をプロットします。

import numpy as np
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, show

x = np.linspace(-np.pi, np.pi, 1_000)
sin_y = np.sin(x)

source = ColumnDataSource(data=dict(x=x, y=sin_y))

p = figure(title="sin(x)", width=640, height=400)
p.line("x", "y", source=source)

show(p)

プロットするデータを、ColumnDataSourceオブジェクトのdata属性に保持します。上述サンプルコードでは、コンストラクタでキーワード引数dataにdict型で X/Yデータを渡しています。キーの名前は任意で構いません。データソースをlineメソッドに渡す場合は引数の先頭2つに、データソースのdata属性に設定したdictのキー名(x, y)をそれぞれ指定します。

データソースのデータを更新

まずsin関数とcos関数の系列を作成します。最初にsin関数をプロットし、ボタンを押すとcos関数をプロットします。
ここで、ボタンを押すとデータソースのデータをsin関数からcos関数に変更します。


import numpy as np
from bokeh.layouts import row
from bokeh.models import ColumnDataSource, Button, CustomJS
from bokeh.plotting import figure, output_file, show

output_file('trigonometric_datasource_inline.html', mode='inline')

x = np.linspace(-np.pi, np.pi, 1_000)
sin_y = np.sin(x)
cos_y = np.cos(x)

source = ColumnDataSource(data=dict(x=x, y=sin_y))

p = figure(title="sin(x) then cos(x)", width=640, height=400)
p.line("x", "y", source=source)

button = Button(label="Change")
callback = CustomJS(
    args=dict(
        source=source,
        cos_y=cos_y,
    ),
    code=""" 
        source.data["y"] = cos_y
        source.change.emit()
    """ 
)
button.js_on_click(callback)
layout = row(p, button)
show(layout)

Bokehのユーザー操作部品(Widgets)の一つ Button を作成し、ボタンを押すとコールバック関数を実行、その中でデータソースをsin関数からcos関数に変更します。
Bokehが生成するHTMLの中でボタンの処理をするため、コールバック処理はJavaScriptで記述します。
コールバックを記述するCustomJSクラスのコンストラクタに、コールバック処理でアクセスするデータをdict型に詰めて渡し、コールバックJavaScriptコードを文字列で渡します。
ButtonがクリックされるとCustomJSオブジェクトを実行するようコールバックを紐づけます。

デバッグ

CustomJSのJavaSctit

Webブラウザの開発者ツールでコンソールを開き、
console.log("デバッグメッセージ");
で確認します。

ロギング

bokehサーバー形態のロギング

bokehサーハーがloggingを使用しているので、プログラム側でロガーとログレベルを設定します。

モジュール名でロガーを取得、レベルを設定します。

import logging

logger = logging.geyLogger(__name__)
logger.setLevel(logging.INFO)


13日前に更新