はじめに
Pythonには様々な可視化ライブラリが存在しており、代表的なものとしてMatplotlibが挙げられます。データ分析業務においてはインタラクティブな可視化が求められ、Matplotlibでも実現は可能なものの、実装に手間がかかります。
一方でPlotlyやBokehのような、簡単な実装でインタラクティブな可視化が可能なライブラリが存在します。
特にこの2つのライブラリは機能的にも似ているところがあり、今回自分が業務で使用するにあたりどのような観点からライブラリの選定を行ったかと併せて紹介します。
また、今回の記事に関連して、下記サイトではPlotlyとBokehの比較についてかなり詳細にまとめられていますので、ぜひご確認ください。
Plotly vs. Bokeh: Interactive Python Visualisation Pros and Cons | Dr. Paul IACOMI
BokehとPlotlyそれぞれの強み
PlotlyとBokeh(と書いて「ボケ」と読む)は、ともにインタラクティブな可視化を可能とするライブラリである一方で、それぞれが得意とする領域があります。
Plotlyについてはまず、簡単な可視化であればとても手軽に行えるという利点があります。PandasのDataFrameを直接読んできて、たった2行(importを含めず)のコードで可視化が行えます。
import plotly.express as px
fig = px.scatter(df, x="x", y="y", color='label', width=800, height=450)
fig.show()
また、描画できるグラフの種類もPlotlyの強みです。特に3Dプロットの描画に優れています。Plotlyと比較するとBokehは3Dプロットに対応していない他、Boxplotなどもそれなりの長さのコードでないと描画できません。
一方でBokehの強みとしてまず、動作が軽いことやリアルタイムでの可視化が行えることが挙げられます。
(参考:https://pauliacomi.com/2020/06/07/plotly-v-bokeh.html)
また、Bokehの方が多機能で柔軟なカスタマイズが行えます(その分コードは長くなりますが...)
今回の業務で感じたBokehの強み
業務において、画像から得られた特徴量を次元削減して二次元プロットを行い、また、その際にプロットにマウスオーバーすると元の画像や画像と結びついた特徴量を表示する必要がありました。 こういったケースにおいて、どちらのライブラリを使用するべきか検討します。
実際の業務と類似した仮想のユースケースとして、6万件のMNISTのデータのプロットを想定します。
まずMNISTをUMAPで次元削減した結果と画像へのパスをDataFrameに格納します。
得られたDataFrameは下記の形式です。
x | y | label | img_url | color |
---|---|---|---|---|
5.89 | 8.48 | 5 | /home/plot_trial/data/mnist/0.jpg | #8c564b |
... | ... | ... | ... | ... |
-0.39 | 5.24 | 1 | /home/plot_trial/data/mnist/59999.jpg | #ff7f0e |
Plotlyでのプロットを行います。
from dash import Dash
from dash import dcc, html, Input, Output, no_update
import plotly.graph_objects as go
import pandas as pd
import base64
fig = go.Figure(data=[
go.Scatter(
x=df["x"],
y=df["y"],
mode="markers",
marker=dict(
colorscale='viridis',
color=df["color"],
line={"color": "#444"},
reversescale=True,
sizeref=45,
sizemode="diameter",
opacity=0.8,
)
)
])
# turn off native plotly.js hover effects - make sure to use
# hoverinfo="none" rather than "skip" which also halts events.
fig.update_traces(hoverinfo="none", hovertemplate=None)
fig.update_layout(
xaxis=dict(title='x'),
yaxis=dict(title='y'),
plot_bgcolor='rgba(255,255,255,0.1)',
width=720, # 横幅
height=540 # 縦幅
)
app = Dash(__name__)
app.layout = html.Div([
dcc.Graph(id="graph", figure=fig, clear_on_unhover=True),
dcc.Tooltip(id="graph-tooltip"),
])
def encode_image(image_file):
encoded = base64.b64encode(open(image_file, 'rb').read())
return 'data:image/png;base64,{}'.format(encoded.decode())
@app.callback(
Output("graph-tooltip", "show"),
Output("graph-tooltip", "bbox"),
Output("graph-tooltip", "children"),
Input("graph", "hoverData"),
)
def display_hover(hoverData):
if hoverData is None:
return False, no_update, no_update
# demo only shows the first point, but other points may also be available
pt = hoverData["points"][0]
bbox = pt["bbox"]
num = pt["pointNumber"]
df_row = df.iloc[num]
img_src = df_row['img_url']
name = df_row['label']
encoded_image = encode_image(img_src)
children = [
html.Div(children=[
html.Img(src=encoded_image, style={"width": "100%"}),
html.H2(f"label: {name}", style={"color": "darkblue"}),
],
style={'width': '100px', 'white-space': 'normal'})
]
return True, bbox, children
if __name__ == "__main__":
app.run_server(debug=True, mode='inline')
(参考:https://community.plotly.com/t/displaying-image-on-point-hover-in-plotly/9223/15)
挙動を確認すると、データ数が多いだけあってドラッグ時に固まってしまっています。
続いてBokehでのプロットを行います。
from bokeh.plotting import figure, show
from bokeh.models import HoverTool, ColumnDataSource
from bokeh.transform import factor_cmap
from bokeh.palettes import Category10
# ラベルごとに色をつけるためのカラムを作成
color = Category10[10]
color = list(color)
colors = []
for _, row in df.iterrows():
c = color[row["label"]]
colors.append(c)
df["color"] = colors
# ColumnDataSourceを作成
data = df.to_dict(orient='list')
source = ColumnDataSource(data)
plot = figure(width=720, height=540)
# 散布図をプロット
scatter = plot.scatter(x='x', y='y', size=5, source=source, color='color', alpha=0.5)
# 画像を重畳して表示
hover = HoverTool(renderers=[scatter], tooltips=None, point_policy="snap_to_data")
hover.tooltips = """
<div>
<span style="font-size: 18px; font-weight:bold">label: @label</span>
</div>
<div>
<img src="@img_url" height="100"></img>
</div>
"""
plot.add_tools(hover)
# 出力
show(plot)
(参考:PythonとBokehを使用して画像付きの散布図を作成する #MachineLearning - Qiita)
動画を確認するとPlotlyと比較してかなり軽快に動作していることが確認できます。
以上より、今回のケースにおいてはBokehが適していると判断できます。
おわりに
インタラクティブな可視化を行うPythonライブラリの二大巨頭としてPlotlyとBokehがあり、それぞれで強みが異なります。
今回は仮想のユースケースにおいて比較検討を行いました。
大量のデータの可視化においてはBokehに軍配が上がりましたが、今後も可視化を行うときはどういったライブラリが適切か検討することが重要です。