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

# 時は戦国
Python には作図ライブラリがたくさんあります。
最もデファクトスタンダードに近く歴史も古い作図ライブラリは matplotlib で間違いないでしょうが、それでも R における [ggplot2](http://ggplot2.org/) ほどの地位は確立していないように思います。
特に、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]
[^1]: [ちなみに語源は日本語で、写真の「ボケ」なので](http://bokehplots.com/pages/technical-vision.html)、「ボケー」じゃなくて「ボケ」と呼ぶべきなのかもしれない。

# 今回試さないけど有名っぽいやつ
## 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
```py3
import matplotlib.pyplot as plt
%matplotlib inline
```
よく見る呪文。

## Seaborn
```py3
import seaborn as sns
%matplotlib inline
```
Seaborn は中身が matplotlib なので呪文は同じ。

## Pandas の作画機能
```py3
import pandas as pd
%matplotlib inline
```
これも中身は matplotlib

## Plotly
```py3
import plotly.offline as po
import plotly.graph_objs as go
po.init_notebook_mode()
```
オフライン用のプロット (グラフを公開しないやつ) がトップレベルのモジュールとして分かれているので一行多い[^2]
[^2]: `import plotly` だけして掘って呼び出してもいいけど

## Bokeh
```py3
import bokeh.plotting as bp
from bokeh import palettes, charts
bp.output_notebook()
```
Bokeh は色分けが自動ではないのでわざわざ `palettes` もインポート[^3]。
[^3]: コレも `import bokeh` から掘ってもいいけど
(DataFrame 経由でプロットした場合は自動で色をつけてくれる？)

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

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


## データ準備
```py3
data_x = np.linspace(0, 100, 10)
data_ys = np.random.random(size=(3, len(data_x)))
labels = ['hoge', 'fuga', 'piyo']
```

## matplotlib
![image](https://qiita-image-store.s3.amazonaws.com/0/132984/905fefe8-60ed-df5a-2a8c-4b380b76ad70.png)

```py
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](https://qiita-image-store.s3.amazonaws.com/0/132984/f33df559-e576-7008-bef3-b9761256591b.png)

```py
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](https://qiita-image-store.s3.amazonaws.com/0/132984/7bf30a68-b0ef-96ea-8fca-808e51446d78.png)

```py
df = pd.DataFrame(data=data_ys.T, index=data_x, columns=labels)
df.plot.line()
```
DataFrame から直接グラフ描画関数を呼び出せる。[^4]
[^4]: これに快感を覚える人種が世の中にいるという話を聞いた。
出来上がる図は matplotlib と同じだが、「どうせやるんだろ？」的な部分を自動化してくれている (凡例の追加とか)。

## Plotly
![image](https://qiita-image-store.s3.amazonaws.com/0/132984/ad57250d-c7de-45bb-68e2-2c74eee8c7ab.png)
↑は本当はウリウリ動く。

```py
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](https://qiita-image-store.s3.amazonaws.com/0/132984/98c40751-c98a-e001-4eb1-c1d84478da68.png)
↑は本当はウリウリ動く。

```py
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)
```
これもコードの見た目はちょっと違うがやっていることは同じ。ただし、色を明示的に指定しないと全部同じ色になる。

# 時系列プロット
横軸が時間ってよくあると思う。

## データ準備
```py
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](https://qiita-image-store.s3.amazonaws.com/0/132984/2cc78a10-d1eb-fa96-dec1-89a4c6b630dd.png)

```py
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]。
[^5]: 冒頭で言ったとおり、今回はできるだけプレーンな設定でおこないますので、ラベルのフォーマットをいじったりといった設定はしません。

## Seaborn
![image](https://qiita-image-store.s3.amazonaws.com/0/132984/2e42d730-f3ce-a8b8-3e44-67eb740862f9.png)

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

## Pandas
![image](https://qiita-image-store.s3.amazonaws.com/0/132984/973b69dd-d4c6-bc1f-89e0-c73865e5e2f3.png)

```py
df = pd.DataFrame(data=data_ys.T, index=data_x, columns=labels)
df.plot.line()
```
一度データフレームにさえしてしまえば、もうなにもしなくていい・・・matplotlib のときは `fig.autofmt_xdate()` しないと横軸のラベルがカオスになっていたが、Pandas の作図機能の設定自動化はとても賢い。だいたいやりたいことはPandasがやってくれる

## Plotly
![image](https://qiita-image-store.s3.amazonaws.com/0/132984/5bf00c55-1c1d-15a0-0a38-46fb37fb2f41.png)
↑は本当はウリウリ動く。

```py
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](https://qiita-image-store.s3.amazonaws.com/0/132984/8dc7aa84-b4b0-8090-0b05-66427c4b6b97.png)
↑は本当はウリウリ動く。

```py
df = pd.DataFrame(data=data_ys.T, index=data_x, columns=labels)
p = charts.TimeSeries(df, legend=True)
bp.show(p)
```

こちらは Plotly と違って、時系列プロット用の専用関数がある。(本当は Plotly もデータフレームを経由すれば一行で済むのかもしれない)

* * *
時系列プロットは、インタラクティブなグラフのほうがかなり良い感じです。静的な画像だとラベルも固定ですが、インタラクティブなグラフだとズームイン／アウトしたときにラベルも合わせて動くし、しかもスケールに合わせて日付だったり時刻だったりを切り替えてくれます。

# 散布図
みんな大好きクラスタ分析

## データ準備
```py
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](https://qiita-image-store.s3.amazonaws.com/0/132984/087e9f77-7453-1522-a835-cffc124612bf.png)

```py
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](https://qiita-image-store.s3.amazonaws.com/0/132984/fc16d626-8232-bb1c-923d-321823cb99a6.png)

```py
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](https://qiita-image-store.s3.amazonaws.com/0/132984/edd85013-1cc9-a59c-58d5-0da9eb6e9204.png)

```py
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](https://qiita-image-store.s3.amazonaws.com/0/132984/b95fd075-ee6c-e8b1-72b3-5c61300d9798.png)
↑は本当はウリウリ動く

```py
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](https://qiita-image-store.s3.amazonaws.com/0/132984/76577c44-3d97-13ee-935a-acb7b04b75d5.png)

```py
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 はもうちょっと入り組んだデータを可視化するときにびっくりするほど便利