時は戦国
Python には作図ライブラリがたくさんあります。
最もデファクトスタンダードに近く歴史も古い作図ライブラリは matplotlib で間違いないでしょうが、それでも R における ggplot2 ほどの地位は確立していないように思います。
特に、Jupyter-notebook 上ではインタラクティブなグラフを表示するニーズがあり、そこでは静的なグラフよりもさらにライブラリが割拠している印象があります。何がどう違うのかよくわかりません。
そこで今回は代表的な作図ライブラリの Jupyter-notebook 上での 違いについて簡単にまとめます。
注意
各ライブラリはいずれも細かくグラフのスタイルを設定可能で、やろうと思えば同じような見た目のグラフを生成することも可能ですが、今回はできるだけ何も設定せずにプロットした時のグラフを使います。
今回試すライブラリたち
matplotlib
みなさんよくご存知のやつ。Python の作図ライブラリと言ったらたいていこれです。静的なグラフ (画像) を生成します
Seaborn
matplotlib のラッパ。おしゃれな図を簡単に作れる。ラッパなので、Seaborn でできることは matplotlib でもできる。静的なグラフ (画像) を生成します。
Pandas の作図機能
Python のデータ分析では必須の Pandas にも (簡易な) 作図機能が付いている。こいつも matplotlib の仲間。データフレームそれ自体が図を作れるのだ。静的なグラフ (画像) を生成します。
plotly
最近、作成したグラフを公開しないようにする選択肢が追加されたことにより使いやすくなった。しかし、オープンソースとはいえ商用の匂いを強く感じる。インタラクティブなグラフ (by d3.js) を生成します。
今回は、offline プロット (ローカルにグラフを作成する機能) のみを使います。plot.ly に公開はしない方式です。
Bokeh
インタラクティブなグラフ生成する系だと一番オフィシャル感がある (公式サイトが pydata でホストされていて、anaconda の Continuum がかなり関わっている)。巨大なデータに強い。インタラクティブなグラフ (by d3.js) を生成します。1
今回試さないけど有名っぽいやつ
ggplot
yhat が開発している。R の ggplot2 の Python 版。静的なグラフ (画像) を生成します。
pygal
他のインタラクティブなグラフ生成系と違って、独立した SVG を作ることが主目的?詳しくは知らない。独立した SVG を生成できれば、ほかのコンテキストへの埋め込みが楽チンになりそう。インタラクティブなグラフ (SVG) を生成します。
インストール
紹介したすべてのライブラリは PyPI でホスティングされているので、例のごとく pip install <name>
でいけます。
- 共通で使うやつら
pip install numpy pandas jupyter
- matplotlib
pip install matplotlib
- Seaborn
pip install seaborn
- pandas の作図機能
pip install pandas
- plotly
pip install plotly
- Bokeh
pip install bokeh
プロットする前のする前の呪文の比較
Jupyter Notebook にグラフを表示する前に特定の呪文を唱える必要がある。その比較
matplotlib
import matplotlib.pyplot as plt
%matplotlib inline
よく見る呪文。
Seaborn
import seaborn as sns
%matplotlib inline
Seaborn は中身が matplotlib なので呪文は同じ。
Pandas の作画機能
import pandas as pd
%matplotlib inline
これも中身は matplotlib
Plotly
import plotly.offline as po
import plotly.graph_objs as go
po.init_notebook_mode()
オフライン用のプロット (グラフを公開しないやつ) がトップレベルのモジュールとして分かれているので一行多い2
Bokeh
import bokeh.plotting as bp
from bokeh import palettes, charts
bp.output_notebook()
Bokeh は色分けが自動ではないのでわざわざ palettes
もインポート3。
どれも、モジュールをインポートして、Jupyter Notebook 上にグラフを表示するための呪文を一行だけ唱える必要がある。全部単純なので問題無いですね
折れ線グラフ
まずは一番シンプルなグラフとして、折れ線グラフを描いてみた。
データ準備
data_x = np.linspace(0, 100, 10)
data_ys = np.random.random(size=(3, len(data_x)))
labels = ['hoge', 'fuga', 'piyo']
matplotlib
fig = plt.figure()
ax = fig.add_subplot(111)
for data_y, label in zip(data_ys, labels):
ax.plot(data_x, data_y, label=label)
ax.legend()
fig.show()
plt.plot
がよく使われるが、Figure
オブジェクトを作って Axes
に情報を追加していく方式のほうが色んな物が壊れにくい。と思う。
Seaborn
fig = sns.mpl.pyplot.figure()
ax = fig.add_subplot(111)
for data_y, label in zip(data_ys, labels):
ax.plot(data_x, data_y, label=label)
ax.legend()
fig.show()
単純な折れ線グラフは Seaborn の本領を発揮するところではない・・・。
Seaborn をインポートするだけで、matplotlib と同じことをしても見た目はおしゃれになる。
Pandas
df = pd.DataFrame(data=data_ys.T, index=data_x, columns=labels)
df.plot.line()
DataFrame から直接グラフ描画関数を呼び出せる。4
Plotly
lines = list()
for data_y, label in zip(data_ys, labels):
lines.append(go.Scatter(
x=data_x,
y=data_y,
mode='lines',
name=label))
po.iplot(lines)
ちょっとコードの見た目は違うが、やっていることは matplotlib のコードを全く同じ。ループを回して1つずつ系列をグラフに追加している。
Bokeh
p = bp.figure()
for data_y, label, color in zip(data_ys, labels, palettes.Set1_3):
p.line(data_x, data_y, legend=label, color=color)
bp.show(p)
これもコードの見た目はちょっと違うがやっていることは同じ。ただし、色を明示的に指定しないと全部同じ色になる。
時系列プロット
横軸が時間ってよくあると思う。
データ準備
import datetime as dt
delta = dt.timedelta(hours=1)
start = dt.datetime(2016, 8, 1)
data_x = np.array([start + delta * i for i in range(200)])
data_ys = np.random.random(size=(3, len(data_x))).cumsum(axis=1)
labels = ['hoge', 'fuga', 'piyo']
matplotlib
fig = plt.figure()
ax = fig.add_subplot(111)
for data_y, label in zip(data_ys, labels):
ax.plot(data_x, data_y, label=label)
ax.legend()
ax.autofmt_xdate()
fig.show()
折れ線グラフ描いたときと全く同じ。x軸が時間だと勝手に時系列プロットにしてくれる。しかも時系列のスケールによって自動でラベルを日付にしたり時刻にしたりもしてくれる。が、ラベルはアメリカ方式 (Aug 01 2016) だったりしてちょっと工夫しないと見にくい5。
Seaborn
fig = sns.mpl.pyplot.figure()
ax = fig.add_subplot(111)
for data_y, label in zip(data_ys, labels):
ax.plot(data_x, data_y, label=label)
ax.legend()
fig.autofmt_xdate()
fig.show()
matplotlib と同じ6
Pandas
df = pd.DataFrame(data=data_ys.T, index=data_x, columns=labels)
df.plot.line()
一度データフレームにさえしてしまえば、もうなにもしなくていい・・・matplotlib のときは fig.autofmt_xdate()
しないと横軸のラベルがカオスになっていたが、Pandas の作図機能の設定自動化はとても賢い。だいたいやりたいことはPandasがやってくれる
Plotly
lines = list()
for data_y, label in zip(data_ys, labels):
lines.append(go.Scatter(
x=data_x,
y=data_y,
mode='lines',
name=label))
po.iplot(lines)
やっていることは折れ線グラフのときと全く同じだが、横軸が datetime なのを勝手に判別してフォーマットしてくれる。
Bokeh
df = pd.DataFrame(data=data_ys.T, index=data_x, columns=labels)
p = charts.TimeSeries(df, legend=True)
bp.show(p)
こちらは Plotly と違って、時系列プロット用の専用関数がある。(本当は Plotly もデータフレームを経由すれば一行で済むのかもしれない)
時系列プロットは、インタラクティブなグラフのほうがかなり良い感じです。静的な画像だとラベルも固定ですが、インタラクティブなグラフだとズームイン/アウトしたときにラベルも合わせて動くし、しかもスケールに合わせて日付だったり時刻だったりを切り替えてくれます。
散布図
みんな大好きクラスタ分析
データ準備
cluster1 = np.random.normal(loc=1, scale=1, size=(2, 30))
cluster2 = np.random.normal(loc=2, scale=1.5, size=(2, 50))
cluster3 = np.random.normal(loc=-1, scale=0.3, size=(2, 20))
labels = ['cluster1', 'cluster2', 'cluster3']
matplotlib
fig = plt.figure()
ax = fig.add_subplot(111)
for cluster, label, color in zip((cluster1, cluster2, cluster3), labels, 'rbg'):
ax.scatter(*cluster, label=label, c=color)
ax.legend()
fig.show()
Seaborn
df1 = pd.DataFrame(cluster1.T, columns=['x', 'y'])
df1['cluster'] = labels[0]
df2 = pd.DataFrame(cluster2.T, columns=['x', 'y'])
df2['cluster'] = labels[1]
df3 = pd.DataFrame(cluster3.T, columns=['x', 'y'])
df3['cluster'] = labels[2]
df = pd.concat([df1, df2, df3])
g = sns.lmplot('x', 'y', data=df, fit_reg=False, hue='cluster')
g.fig.show()
ようやく Seaborn のAPI経由で作図した
Pandas
df1 = pd.DataFrame(cluster1.T, columns=['x', 'y'])
df1['cluster'] = labels[0]
df2 = pd.DataFrame(cluster2.T, columns=['x', 'y'])
df2['cluster'] = labels[1]
df3 = pd.DataFrame(cluster3.T, columns=['x', 'y'])
df3['cluster'] = labels[2]
df = pd.concat([df1, df2, df3])
colors = 'rgb'
fig, ax = plt.subplots()
grouped = df.groupby(by='cluster')
for (cluster, group), color in zip(grouped, colors):
group.plot(kind='scatter', x='x', y='y', label=cluster, ax=ax, c=color)
ax.legend()
fig.show()
Pandas の作図機能で散布図をクラスタごとに色分けするのは結構めんどう
Plotly
scatters = list()
for cluster, label in zip((cluster1, cluster2, cluster3), labels):
scatters.append(go.Scatter(
x=cluster[0, :],
y=cluster[1, :],
name=label,
mode='markers'))
po.iplot(scatters)
Bokeh
df1 = pd.DataFrame(cluster1.T, columns=['x', 'y'])
df1['cluster'] = labels[0]
df2 = pd.DataFrame(cluster2.T, columns=['x', 'y'])
df2['cluster'] = labels[1]
df3 = pd.DataFrame(cluster3.T, columns=['x', 'y'])
df3['cluster'] = labels[2]
df = pd.concat([df1, df2, df3])
p = charts.Scatter(df, x='x', y='y', color='cluster')
bp.show(p)
散布図は・・・別に画像でもいいかな・・・
ここで力尽きた。本当は BoxPlot とかもっといろんなグラフを比較したかった。
結論 (感想)
- Pandas の作図機能はできる子 (ただし簡易なものに限る)
- Plotly と Bokeh はあまり違いがわからん。Plotly のほうがお金の匂いを感じる。
- しかし、Plotly のほうがAPIは綺麗な印象を受けた。
- データフレームに入っていたら Bokeh も良いが、Plotly はデータフレームに入ってなくても綺麗に書ける。気がする。
- しかし、Plotly のほうがAPIは綺麗な印象を受けた。
- Seaborn には不利なデータが多く申し訳無い比較記事になってしまった。Seaborn はもうちょっと入り組んだデータを可視化するときにびっくりするほど便利
-
ちなみに語源は日本語で、写真の「ボケ」なので、「ボケー」じゃなくて「ボケ」と呼ぶべきなのかもしれない。 ↩
-
import plotly
だけして掘って呼び出してもいいけど ↩ -
コレも
import bokeh
から掘ってもいいけど
(DataFrame 経由でプロットした場合は自動で色をつけてくれる?) ↩ -
これに快感を覚える人種が世の中にいるという話を聞いた。
出来上がる図は matplotlib と同じだが、「どうせやるんだろ?」的な部分を自動化してくれている (凡例の追加とか)。 ↩ -
冒頭で言ったとおり、今回はできるだけプレーンな設定でおこないますので、ラベルのフォーマットをいじったりといった設定はしません。 ↩
-
Seaborn には
tsplot
といういかにも時系列プロット的なAPIがあるが、tsplot
に適した形式にデータを加工するのも大変 (最初から Seaborn が期待する形式でデータを持っているならもちろんはやいが) だし、そもそもx軸のラベルを時刻にするのがとてもトリッキーなのでここでは matplotlib とおなじ方法でプロットしています ↩