ggplot2使いのためのグラフseaborn⇆ggplot

#はじめに
Rユーザーのグラフ作成や可視化といえばggplot2が鉄板中の鉄板です。
レイヤー機能という、データに関する要素をプロットに重ねていく仕組みは簡潔なコードを可能にし、設定されているスタイルは野暮ったさのないグラフ作成を可能にしています。
Pythonを学び始めたユーザー（私）がPythonだとどうコード書くんだっけ、というときに使えるよう辞書的にまとめています。

#Rユーザーのグラフ作成は？
###ggplot
**Rのggplot2がそのまま使用できる**（2が取れているのが特徴です）
しかし完成度が甘く、Rのggplot2ほど自由に操作できません。例えば、

- boxplotでcolorが作動しない
- 「ggplot()」の関数内で変数を指定する必要がある

等々の不便さはあります。
###plotly
Rでもお世話になりましたplotly。特徴はインタラクティブなグラフが作れることです。
しかしggplot2ほど簡単な操作ではないです。これから利用が伸びてきそう。

###seaborn
pythonで主にグラフ作成に使われてきたmatplotlibを改良したモノで以下のような特徴があります。

- 適切なプロットスタイルや配色
- シンプルな関数
- pandasデータフレーム対応

個人的にはseabornがお勧めです。Rユーザーはggplot2自体は使い慣れていると思いますので、ggplot2 ⇄ seaborn表記をまとめます。

#実例：ggplot2 ⇄ seaborn
seabornライブラリにはR同様データセット付属しておりRではみんな大好き定番のirisもあります。
いくつか読み込んで使用します。

##事前準備
```Python:Python-seaborn：ライブラリなど
import matplotlib.pyplot as plt
import seaborn as sns; sns.set() #seabornライブラリを読み込み、スタイルをセットする
iris = sns.load_dataset('iris') #irisデータセット読み込み
fmri = sns.load_dataset('fmri') #fmriデータセット読み込み（Rにはありません）
tips = sns.load_dataset("tips") #tipsデータセット読み込み（Rにはありません）
```

##ヒストグラム histogram

```R:R-ggplot2：geom_hist
ggplot(aes(x=Sepal.Length, fill=Species), data=iris) +
  geom_histogram(position = "identity", alpha = 0.6, bins=9, binwidth = 0.5)
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/318124/298f6864-36e1-ac4f-4b53-0b44b48971d4.png)

```Python:Python-seaborn：distplot
#seabornでは各変数ごとにヒストグラムを重ねます。
sns.distplot(iris.query('species=="setosa"').sepal_length, kde=False, bins=np.linspace(4,8,9), label="setosa")
sns.distplot(iris.query('species=="versicolor"').sepal_length, kde=False, bins=np.linspace(4,8,9), label="versicolor") 
sns.distplot(iris.query('species=="virginica"').sepal_length, kde=False, bins=np.linspace(4,8,9), label="virginica") 
plt.legend()
 #kde = カーネル密度曲線の有無
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/318124/7d336125-34e2-7787-ee3e-d19ee1d838d0.png)

##散布図　scatter

```R:R-ggplot2：geom_point
ggplot(aes(y=Sepal.Length, x=Sepal.Width, color=Species), data=iris) + geom_point()
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/318124/1937993a-b309-2ad0-1e1d-6f9460adce24.png)


```Python:Python-seaborn：scatterplot
sns.scatterplot(x='sepal_length', y='sepal_width', hue='species', data=iris)
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/318124/b9e711d3-4b38-4f47-2877-308335aa3deb.png)

##棒グラフ（count）
nカウントをする棒グラフ

```R:R-ggplot2：geom_bar
ggplot(aes(x=Species, fill=Species), data=iris) + geom_bar(stat="count")
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/318124/ec56d541-9464-4d37-01ff-1f4c3c61dcf7.png)

```Python:Python-seaborn：countplot
sns.countplot(x='species', data=iris)
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/318124/482e5e6d-d62a-b269-e999-8239058f4df9.png)



##棒グラフ（bar）
棒グラフにするにはxに対するy値を事前に計算してグラフ化しますが、プロット化の際にも計算は可能です。ggplot2では計算式を指定できます。一方seabornではyに指定した変数の平均値とエラーバーが自動で計算されるようです。

```R:R-ggplot2：geom_bar
#平均値の棒グラフ
ggplot(aes(x=Species, y=Sepal.Length, fill=Species), data=iris) +
  stat_summary(fun.y=mean,geom="bar")
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/318124/db1b54d0-94ae-3732-c11e-4ce3c7f36e62.png)

```Python:Python-seaborn：barplot
#平均値の棒グラフ
sns.barplot(x='species', y='sepal_length', data=iris)
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/318124/8879b17d-0445-911c-e5d5-07fd4cacff8c.png)

##折れ線グラフ line
```R:R-ggplot2：geom_line
#正弦波のデータを作成
x <- seq(0, 10, len=30)
y <- sin(x)
data.frame(x,y)-> dataframe
ggplot(aes(x=x, y=y), data=dataframe) + geom_line()
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/318124/b848a546-9378-5df6-ac9b-9016a76d6874.png)

```Python:Python-seaborn：lineplot
import numpy as np
import pandas as pd
x = list(np.linspace(0,10,30))
y = list(np.sin(x))
dataframe = pd.DataFrame(list(zip(x,y)), columns=['x','y'])
sns.lineplot(x="x", y="y", data=dataframe)
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/318124/009060fd-67e9-172f-1be3-61aeb52e684b.png)

**ちなみに、xの値に対してyの値が複数存在するデータを使用した場合、ggplot2ですと…**

```R:R-ggplot2：geom_line
#fmriデータセットを使用(Pythonから持ってきた）
ggplot(aes(x=timepoint, y=signal, color=event), data=fmri) + geom_line()
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/318124/662ce0a8-0163-d70b-ad21-0f024539029d.png)
このようにわけのわからないグラフになってしまいますが、
**seabornでは...**

```Python:Python-seaborn：lineplot
sns.lineplot(x='timepoint', y='signal', hue='event',
             style="event", data=fmri, markers=True, dashes=False) #エラー変更はci ='sd'
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/318124/11110cca-ad73-fbe0-486a-8b5cc5c051ce.png)

ライン（マーカー）を平均値、~~エラーバンド（sdの帯）を計算して表示してくれます。~~
**エラーバンドは信頼区間（各マーカー間の回帰直線の信頼区間）ですね。**
標準偏差に変更する場合はオプションでci='sd'と記述する必要があります。
（普通、折れ線グラフのエラーってSDじゃないのか？）

これ結構便利な気がします。

##箱ひげ図 box

```R:R-ggplot2：geom_boxplot
ggplot(aes(x=Species, y=Sepal.Length, color=Species), data=iris) + geom_boxplot()
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/318124/ff296369-4a1a-1714-f2ee-b444fc50e80a.png)

```Python:Python-seaborn：boxplot
sns.boxplot(x='species', y='sepal_length', data=iris)
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/318124/2e5028fd-4205-0b42-076c-320eef6e20f8.png)

##subplots
複数の図を並べるsubplot表示

```R:R-ggplo2：subplots
degree <-levels(factor(fmri$event))

p1 = ggplot(data=fmri%>%filter(event==degree[2]),aes(x=timepoint, y=signal)) +
  stat_summary(fun.y=mean,geom="line",linetype = 2) +
  stat_summary(fun.ymin = function(x) mean(x) - sd(x),
               fun.ymax = function(x) mean(x) + sd(x),
               geom = "errorbar") +
  ggtitle(paste0('event = ',degree[2]))

p2 = ggplot(data=fmri%>%filter(event==degree[1]),aes(x=timepoint, y=signal)) +
  stat_summary(fun.y=mean, geom="line",linetype = 2) +
  stat_summary(fun.ymin = function(x) mean(x) - sd(x),
               fun.ymax = function(x) mean(x) + sd(x),
               geom = "errorbar") +
  ggtitle(paste0('event = ',degree[1]))

gridExtra::grid.arrange(p1, p2, nrow = 1)
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/318124/acf84903-def6-6fce-e9d7-f5966fd6281a.png)

※y軸の幅をylimで設定してしまうとその範囲に含まれないデータはstat_summaryでの計算に含まれなくなるので注意が必要です（上記のmean, sd）。

RではgridExtraライブラリを使用して図を並べるのが多いようです。
patchwork関数も便利みたいです（[解説されている記事はこちら](https://qiita.com/nozma/items/4512623bea296ccb74ba)）。

```Python:Python-seaborn：subplots
fig, ax = plt.subplots(1, 2, figsize=(16, 6))
fig.subplots_adjust(left=0.0625, right=0.95, wspace=0.2)

for i, degree in enumerate(fmri['event'].unique()):
    tmp = fmri[fmri['event'] == degree]
    sns.lineplot(x='timepoint', y='signal', data=tmp,
                 ax=ax[i], err_style='bars', ci='sd') #axで図番号の指定
    ax[i].set_ylim(-0.1, 0.25)
    ax[i].lines[0].set_linestyle("--")
    ax[i].set_title('event = {0}'.format(degree), size=18)

```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/318124/3e995246-d616-2ea2-636b-0031b381e67f.png)



#plot設定
##seaborn plotの細かな設定
ラベルやタイトル、凡例は頻繁に変更しますよね。
.set_xxxx()で図の設定を追加できます。
変更できるパラメーターはmatplotlibの[こちらの項目](https://matplotlib.org/api/axes_api.html)ですね。

```Python:Python-seaborn：図の設定
plt.figure(figsize=(10, 6))#グラフサイズ変更
xtick_label = ["A","B","C"] #xラベル指定
g = sns.boxplot(x='species', y='sepal_length', data=iris)
g.set_xticklabels(labels=xtick_label, fontsize=15, rotation=30 ) #x軸目盛ラベル
#plt.xticks(rotation=90, horizontalalignment='center', fontsize=15)　x軸目盛ラベルの回転はこれも可
g.set_ylim(3,9) #y軸目盛範囲
g.set_yticklabels(labels = g.get_yticks(), fontsize=15) #y軸目盛ラベル
g.set_xlabel("種", fontsize=20) #x軸ラベル
g.set_ylabel("長さ") #y軸ラベル
g.set_title("ぷろっと", fontsize=20) #title
h, l = g.get_legend_handles_labels()#凡例情報取得
g.legend(h,('任意のhogehoge'))#凡例設定
plt.legend(loc="best", frameon=True, edgecolor="gray") #凡例位置情報
#g.legend().set_visible(False) #凡例を消去
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/318124/51afb1ae-4f75-fa6f-f314-60d05f118f73.png)


##スタイル設定
前述の事前準備の項でsns.set()によりseabornの描画スタイルを適用しましたが、別スタイルにも変更できます。

```Python:Python-seaborn：style変更
sns.set(style='darkgrid')
plt.figure(figsize=(3,3))
sns.boxplot(y='sepal_length', data=iris)
#sns.despine()
```

| darkgrid（標準） | whitegrid | dark |
|:---------------:|:---------:|:----:|
|![image.png](https://qiita-image-store.s3.amazonaws.com/0/318124/4e53db2b-01e0-802f-a155-060bb9c9cde9.png)|![image.png](https://qiita-image-store.s3.amazonaws.com/0/318124/79f5f1eb-24eb-6815-2867-2ecd1bb1012f.png)|![image.png](https://qiita-image-store.s3.amazonaws.com/0/318124/4e1c7179-0180-b0bb-d52b-1fd9a689dcb3.png)|

| white | ticks | sns.dispine |
|:-----:|:-----:|:-----------:|
|![image.png](https://qiita-image-store.s3.amazonaws.com/0/318124/bf2334aa-3ad7-4226-fc0f-73745c39fc51.png)|![image.png](https://qiita-image-store.s3.amazonaws.com/0/318124/475ebfa0-f0a7-753c-f23f-217459cfbb55.png)|![image.png](https://qiita-image-store.s3.amazonaws.com/0/318124/39fff60b-073e-4c61-286c-ad5841c3cad7.png)|



#おまけ：seabornによる層別グラフ（FacetGrid）

##層別ヒストグラム
```Python:Python-seaborn：FacetGrid_plt.hist
g = sns.FacetGrid(iris, col="species") #colとrowでgridを描画
g = g.map(plt.hist, "sepal_length", bins=np.linspace(4,8,9)) #histを描画
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/318124/4962faed-5a45-4c20-4792-ba3a3e6e1067.png)


##層別散布図
```Python:Python-seaborn：FacetGrid_plt.hist
g = sns.FacetGrid(iris, col="species", height=3, aspect=1.2) #colとrowでgridを描画、heightとaspcetで描画サイズを変更
g = g.map(plt.scatter, "sepal_length", "sepal_width", edgecolor="white") #scatterを描画
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/318124/d559c91b-583a-9eb5-f76c-2e4592580a9f.png)

```Python:Python-seaborn：FacetGrid_plt.scatter
g = sns.FacetGrid(tips, col="sex", hue="smoker") #グループ分けしてみたり
g.map(plt.scatter, "total_bill", "tip", alpha=.7)
g.add_legend();
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/318124/e525f66d-52a4-e5db-52f0-671d8f7ccf54.png)

##層別棒グラフ
```Python:Python-seaborn：FacetGrid_sns.barplot
g = sns.FacetGrid(tips, col="sex")
g = g.map(sns.barplot, "smoker", "total_bill")
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/318124/d112fb81-618b-c8af-f399-fdb762f9c245.png)

#まとめ
seabornのスタイルはggplot2に似ているため非常に見やすいグラフです。
同様に簡単に作れるので積極的に使っていこうと思います。