Python
matplotlib
pandas
plotly
Bokeh

Jupyter-notebook の作図ライブラリ比較

More than 1 year has passed since last update.

時は戦国

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
(DataFrame 経由でプロットした場合は自動で色をつけてくれる?)


どれも、モジュールをインポートして、Jupyter Notebook 上にグラフを表示するための呪文を一行だけ唱える必要がある。全部単純なので問題無いですね

折れ線グラフ

まずは一番シンプルなグラフとして、折れ線グラフを描いてみた。

データ準備

data_x = np.linspace(0, 100, 10)
data_ys = np.random.random(size=(3, len(data_x)))
labels = ['hoge', 'fuga', 'piyo']

matplotlib

image

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

image

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

image

df = pd.DataFrame(data=data_ys.T, index=data_x, columns=labels)
df.plot.line()

DataFrame から直接グラフ描画関数を呼び出せる。4
出来上がる図は matplotlib と同じだが、「どうせやるんだろ?」的な部分を自動化してくれている (凡例の追加とか)。

Plotly

image
↑は本当はウリウリ動く。

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

image
↑は本当はウリウリ動く。

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

image

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

image

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

image

df = pd.DataFrame(data=data_ys.T, index=data_x, columns=labels)
df.plot.line()

一度データフレームにさえしてしまえば、もうなにもしなくていい・・・matplotlib のときは fig.autofmt_xdate() しないと横軸のラベルがカオスになっていたが、Pandas の作図機能の設定自動化はとても賢い。だいたいやりたいことはPandasがやってくれる

Plotly

image
↑は本当はウリウリ動く。

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

image
↑は本当はウリウリ動く。

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

image

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

image

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

image

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

image
↑は本当はウリウリ動く

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

image

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 はデータフレームに入ってなくても綺麗に書ける。気がする。
  • Seaborn には不利なデータが多く申し訳無い比較記事になってしまった。Seaborn はもうちょっと入り組んだデータを可視化するときにびっくりするほど便利

  1. ちなみに語源は日本語で、写真の「ボケ」なので、「ボケー」じゃなくて「ボケ」と呼ぶべきなのかもしれない。 

  2. import plotly だけして掘って呼び出してもいいけど 

  3. コレも import bokeh から掘ってもいいけど 

  4. これに快感を覚える人種が世の中にいるという話を聞いた。 

  5. 冒頭で言ったとおり、今回はできるだけプレーンな設定でおこないますので、ラベルのフォーマットをいじったりといった設定はしません。 

  6. Seaborn には tsplot といういかにも時系列プロット的なAPIがあるが、tsplot に適した形式にデータを加工するのも大変 (最初から Seaborn が期待する形式でデータを持っているならもちろんはやいが) だし、そもそもx軸のラベルを時刻にするのがとてもトリッキーなのでここでは matplotlib とおなじ方法でプロットしています