▲Bokehライブラリ

Bokehライブラリについて説明します。

参考

Bokehは、データを可視化するためのライブラリです。 bokeh モジュールを使った、基本的なグラフの描画について説明します。

線グラフ

Bokehライブラリ使用してグラフを描画するには、bokeh.plotting のモジュールをインポートします。 基本的なグラフの描画をノートブック上で行うには、図形を生成する bokeh.plotting.figure()、図形を表示する bokeh.plotting.show()、出力先をノートブック上に設定する bokeh.plotting.output_notebook() があれば充分です。 通例、output_notebook() は最初に呼び出されます。

グラフで可視化するデータは配列を用いることが多いため、numpy モジュールも併せてインポートします。

[1]:
import numpy as np
from bokeh.plotting import figure, output_notebook, show
output_notebook()
Loading BokehJS ...

次は、figure() が返す Figure クラスの line() メソッドを使って、リストの要素の数値をy軸の値としてグラフを描画しています。 y軸の値に対応するx軸の値は、リストの各要素のインデックスとしています。

[2]:
# プロットするデータ
d = [0, 1, 4, 9, 16]
p = figure()
p.line(list(range(len(d))), d) # 第1引数がx軸、第2引数がy軸
show(p)

line() メソッド(及び他の描画用メソッド)では、キーワード引数も使えます。

[3]:
p = figure()
p.line(y=d, x=list(range(len(d))))
show(p)

次に示すように、複数のグラフをまとめてプロットして表示することもできます。 プロットするメソッドではグラフの線の色や線の種類を、line_color 引数や line_dash 引数で指定できます。 また、legend_label 引数に値を設定すると、プロットしたグラフが凡例に現れます。 引数の詳細はFigure.lineのページ(英語)を参照してください。

[4]:
data = [0, 1, 4, 9, 16]
x = list(range(len(data)))
p = figure()
p.line(x, x, line_color='blue', legend_label='linear', line_dash='dashed')
p.line(x, data, line_color='green', legend_label='quad', line_dash='dotted')
show(p)

figure() 関数の引数に、軸のラベルや、グラフのタイトルを設定できます。 プロット点を線グラフ上に重ねたいときには、circle() メソッドや cross() メソッドで同色の円や十字を追加で描けばよいです。

[5]:
p = figure(x_axis_label='x', y_axis_label='y', title='Linear vs. Quadratic')
p.line(x, x, line_color='blue', legend_label='linear', line_dash='dashed')
p.circle(x, x, color='blue', line_width=5)
p.line(x, data, line_color='green', legend_label='quad', line_dash='dotted')
p.cross(x, data, color='green', size=16)
show(p)
BokehDeprecationWarning: 'cross() method' was deprecated in Bokeh 3.4.0 and will be removed, use "scatter(marker='cross', ...) instead" instead.

色の使い分けを全て自分で決めるのは面倒です。 良く使われる色のリストがパレットとして、提供されています。 次は、d3Category10 という種類の3色パレットを用いています。 詳細は、paletteのページを参照してください。

[6]:
from bokeh.palettes import d3
c = d3['Category10'][3]
p = figure(x_axis_label='x', y_axis_label='y', title='Linear vs. Quadratic')
p.line(x, x, line_color=c[0], legend_label='linear', line_dash='dashed')
p.circle(x, x, color=c[0], line_width=5)
p.line(x, data, line_color=c[1], legend_label='quad', line_dash='dotted')
p.cross(x, data, color=c[1], size=16)
show(p)
BokehDeprecationWarning: 'cross() method' was deprecated in Bokeh 3.4.0 and will be removed, use "scatter(marker='cross', ...) instead" instead.

グラフを描画するときのプロット数を増やすことで任意の曲線のグラフを作成することもできます。 次の例では、numpy モジュールの arange() 関数を用いて、\(- \pi\) から \(\pi\) の範囲を 0.1 刻みでx軸の値を配列として準備しています。 そのx軸の値に対して、numpy モジュールの cos() 関数と sin() 関数を用いて、y軸の値をそれぞれ準備し、cosカーブとsinカーブを描画しています。

[7]:
# グラフのx軸の値となる配列
x = np.arange(-np.pi, np.pi, 0.1)

# 上記配列をcos, sin関数に渡し, y軸の値として描画
p = figure(title='cos and sin Curves', x_axis_label='x', y_axis_label='y')
p.line(x, np.cos(x), line_color=c[0])
p.line(x, np.sin(x), line_color=c[1])
show(p)

プロットの数を少なくすると、曲線は直線をつなぎ合わせることで描画されていることがわかります。

[8]:
x = np.arange(-np.pi, np.pi, 0.5)
p = figure(title='cos and sin Curves', x_axis_label='x', y_axis_label='y')
p.line(x, np.cos(x), line_color=c[0])
p.line(x, np.sin(x), line_color=c[1])
show(p)

グラフの例:ソートアルゴリズムにおける比較回数

[9]:
import random

def bubble_sort(lst):
    n = 0
    for j in range(len(lst) - 1):
        for i in range(len(lst) - 1 - j):
            n = n + 1
            if lst[i] > lst[i+1]:
                lst[i + 1], lst[i] = lst[i], lst[i+1]
    return n

def merge_sort_rec(data, l, r, work):
    if l+1 >= r:
        return 0
    m = l+(r-l)//2
    n1 = merge_sort_rec(data, l, m, work)
    n2 = merge_sort_rec(data, m, r, work)
    n = 0
    i1 = l
    i2 = m
    for i in range(l, r):
        from1 = False
        if i2 >= r:
            from1 = True
        elif i1 < m:
            n = n + 1
            if data[i1] <= data[i2]:
                from1 = True
        if from1:
            work[i] = data[i1]
            i1 = i1 + 1
        else:
            work[i] = data[i2]
            i2 = i2 + 1
    for i in range(l, r):
        data[i] = work[i]
    return n1+n2+n

def merge_sort(data):
    return merge_sort_rec(data, 0, len(data), [0]*len(data))
[10]:
x = np.arange(100, 1100, 100)
bdata = np.array([bubble_sort([random.randint(1,10000) for i in range(k)]) for k in x])
mdata = np.array([merge_sort([random.randint(1,10000) for i in range(k)]) for k in x])
[11]:
p = figure(title='bubble sort vs. merge sort', x_axis_label='number of items', y_axis_label='number of comparisons')
p.line(x, bdata, line_color=c[0])
p.circle(x, bdata, color=c[0], line_width=5)
p.line(x, mdata, line_color=c[1])
p.circle(x, mdata, color=c[1], line_width=5)
show(p)

散布図

散布図の描画には、点のプロットを marker 引数で指定できる scatter() メソッドが便利です。 以下では、ランダムに生成した20個の要素からなる配列 xy の各要素の値の組みを点としてプロットした散布図を表示します。 プロットする点のマーカは円とし、size 引数で大きさを、alpha 引数で透明度を設定しています。

[12]:
# グラフのx軸の値となる配列
x = np.random.rand(20)
# グラフのy軸の値となる配列
y = np.random.rand(20)

p = figure()
p.scatter(x, y, marker='circle', size=16, alpha=0.5)
show(p)

これと同じグラフは、単に circle() メソッドでプロットをすることでも描画できます。

[13]:
p = figure()
p.circle(x, y, size=16, alpha=0.5)
show(p)
BokehDeprecationWarning: 'circle() method with size value' was deprecated in Bokeh 3.4.0 and will be removed, use 'scatter(size=...) instead' instead.

棒グラフ

棒グラフは、vbar() メソッドを用いて描画できます。 次の例では、ランダムに生成した10個の要素からなる配列 y の各要素の値を縦の棒グラフで表示しています。 x は、x軸上で棒グラフのバーの並ぶ位置を示しています。 ここでは、numpy モジュールの arange() 関数を用いて、1 から 10 の範囲を 1 刻みでx軸上のバーの並ぶ位置として配列を準備しています。

[14]:
# x軸上で棒の並ぶ位置となる配列
x = np.arange(1, 11, 1)
# グラフのy軸の値となる配列
y = np.random.rand(10)

p = figure()
p.vbar(x, 0.5, y) # 第2引数は幅
show(p)

ヒストグラム

ヒストグラムの描画には、quad() メソッドが便利です。 次の例では、numpy.random.randn() 関数を用いて、正規分布に基づく 1000 個の数値の要素からなる配列を用意し、numpy.histogram() 関数を使って20個のビンに分類したヒストグラムを計算しています。 その計算結果を、quad() メソッドを使って、描画しています。 ビンの境界を見やすくするように、line_colorfill_color(デフォルト色)を別の色にしています。

[15]:
# 正規分布に基づく1000個の数値の要素からなる配列
d = np.random.randn(1000)
# numpy.histogramで20のビンに分割
hist, bin_edges = np.histogram(d, 20)
p = figure()
p.quad(top=hist, bottom=0, left=bin_edges[:-1], right=bin_edges[1:], line_color='white', alpha=0.5)
show(p)

ヒートマップ

最後に、複雑な応用例として、ヒートマップの描画方法を示します。 次の例は、10x10 のマスに 0.0 以上 1.0 未満の乱数の温度を割り当て、その値に応じた色で塗ったヒートマップです。 ここでは、これまでと違って、x軸、y軸、温度の3つの値が必要になります。 そこで、bokeh.models.ColumnDataSource 型を用いて、その3つ組を、'x''y''T' の属性を持った表データを構築しています。 この表データの構築には、7-1で説明するpandasも使えます。

ヒートマップでは、温度に応じた階調のある色選択が必要です。 そこで、色階調と値を対応付ける bokeh.models.LinearColorMapper 型の mapper を準備します。 rect() メソッドでは、表データの属性を参照して描画しています。 色は、表データの値を mapper に適用して色に変化させたものを用いることで、温度に応じた色選択を実現しています。 最後に、目盛り付きのカラーバーを生成して、右に配置しています。

[16]:
from bokeh.models import LinearColorMapper, BasicTicker, PrintfTickFormatter, ColorBar, ColumnDataSource
from bokeh.transform import transform

# 10行10列のランダム要素からなる行列
n = 10
data = np.random.rand(n*n)
src = ColumnDataSource({'x': [yx % n for yx in range(n*n)], 'y': [yx // n for yx in range(n*n)], 'T' : data})

colors = ['#75968f', '#a5bab7', '#c9d9d3', '#e2e2e2', '#dfccce', '#ddb7b1', '#cc7878', '#933b41', '#550b1d']
mapper = LinearColorMapper(palette=colors, low=data.min(), high=data.max())
p = figure()
p.rect('x', 'y', 1, 1, source=src, line_color=None, fill_color=transform('T', mapper))
color_bar = ColorBar(color_mapper=mapper, location=(0, 0),
                     ticker=BasicTicker(desired_num_ticks=len(colors)),
                     formatter=PrintfTickFormatter(format='%2.1f'))
p.add_layout(color_bar, 'right')
show(p)

グラフのファイル出力

これまで表示されてきたグラフには画像保存ボタンがあるので、それをクリックすればPNG形式の画像を保存できます。

bokeh.plotting.output_file() を用いると、グラフ単独をHTMLファイルとして保存できるようになります。 ただし、既に output_notebook() を読んでいる場合、bokeh.plotting.reset_output() で状態をリセットする必要があります。

[17]:
from bokeh.plotting import save, output_file, reset_output
x = np.arange(-2*np.pi, 2*np.pi, 0.1)
p = figure(title='sin Curves', x_axis_label='x', y_axis_label='y')
p.line(x, np.sin(x))

reset_output() # output_notebook()の効果を消す
output_file('sin.html') # 出力先の設定
save(p) # グラフを保存するだけ
show(p) # 保存した上でブラウザを開く
0:116: execution error: ファイル“不特定のオブジェクト”が見つかりませんでした。 (-43)

注意output_notebook() を呼んだ状態と output_file() を呼んだ状態が重なると、show() でエラーが起きます。

[ ]: