複数のseabonやplotnineのplotを並べる。

#はじめに
この記事で紹介しているmaplotlib用のパッケージ[patchworklib](https://github.com/ponnhide/patchworklib)を用いると、以下のような図版が簡単に作れるようになる。図版作成で時間を浪費してる方には是非使ってみて欲しい。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167957/dec27d27-910e-4529-aa55-1b5467ba343e.png" width="600x600">


##Seabornやplotnineのplotが並べられない。
Pythonでグラフを作る際に最も有名なライブラリといえばmaplotlibであろう。ただ、matplotlibで凝った図を作ろうとするとめちゃくちゃめんどい。そんなとき、凝った図をわずか数行で作成することを可能にしてくれるのが、[Seaborn](https://seaborn.pydata.org/index.html)や[plotnine](https://plotnine.readthedocs.io/en/stable/)(python版 ggplot)といったライブラリである。
しかし、seabornやplotnineが作ってくれる図には1つ大きな欠点がある。それは、**異なるコマンドで作成された図を並べることができない**ということだ。。。
厳密には、seabornではaxes levelの関数には自分で作ったaxes objectを渡すことができるので、これらの関数で作ったプロットについては、自由に並べることが可能である(詳しい説明は、[公式のページ](https://seaborn.pydata.org/tutorial/function_overview.html)がわかりやすい)。ただ、ぶっちゃけaxes levelの関数で実現できることなら、matplotlibで1から書けないこともないし、その方がカスタマイズも効く。どちらかというと、[lmplot](https://seaborn.pydata.org/generated/seaborn.lmplot.html), [relplot](https://seaborn.pydata.org/generated/seaborn.relplot), [displot](https://seaborn.pydata.org/generated/seaborn.displot.html#seaborn.displot), [pairplot](https://seaborn.pydata.org/generated/seaborn.pairplot)のような、凝ったplotで作った図を自由に並べる機能が欲しいのである。しかし、これらの関数はSeabornが勝手に用意したFigure objectを返してくるため、Figure objectを並べる機能がないmatplotlibではどうすることもできないのである。
Python版ggplotであるplotnineも同様の問題を抱えている。こちらも数行でいい感じのplotを作成することを可能にしてくれるのだが、やはり返してくるのがplotnineが用意したFigure objectであるため、異なるコマンド作成した複数のplotを並べたりすることはできない。

人によっては、
「個別にplotを並べるのは、Powerpoint とか Keynote とか illustratorでやればよくね？」
思う人もいるかもしれない。しかし、軸とか揃えて並べるのは意外と面倒なのだ。そして、せっかく綺麗に整えた図をボスに見せると、「このプロットの色とかアスペクト比がいまいち」とか言われて、1から並べ直す羽目になるのである。

また、人によっては、
「Rのggplotであれば、patchworkやcowplotと言ったライブラリで自由にplotが並べられるよ？」
と言う人もいるかもしれない。([この記事](https://qiita.com/nozma/items/cd98ec7938e0783d5d89)が詳しい）。それはその通りなのだが、解析と図の作成でPyhtonとRを行き来するのも面倒くさいし、Google colabで解析のプロセスを共有するときも不便だ。

ということで、筆者は長らく、どうにかしてseabornやplotnineのplotを良い感じ1つの図版にまとめられないだろうかと思案していたのであった。

##無理矢理、作ってみた。
細かい実装の説明は省くが、色々と頑張って、以前に自分が作成した[patchworklib](https://github.com/ponnhide/patchworklib)の機能拡張をおこなった。その結果、seabornやplotnineのplotについても、本家ggplotのpatchworkのように`|`と`/`の演算子を用いて直感的に並べられるようになった。

##実際に並べてみた。
以下のようにseabornでもplotnineのplotでも簡単に並べることが可能である。
###Seaborn plotの配置の例
まず、patchworlklibをimportし`patchworlklib.overwrite_axisgrid()`を実行することで、Grid objectをベースとするseabornのプロットを`patchworlklib.load_seaborngrid`で取り込むことができるようにする。そして、個別のseabornのplotを作成し、それらを`patchworlklib.load_seaborngrid`で取り込む。

```python3
import seaborn as sns
import matplotlib.pyplot as plt
import patchworklib as pw 
pw.overwrite_axisgrid() #saebornが提供しているclass objectのメソッド等に変更を加えている。

# An lmplot
iris = sns.load_dataset("iris")
tips = sns.load_dataset("tips")

# An lmplot
g0 = sns.lmplot(x="total_bill", y="tip", hue="smoker", data=tips, 
                palette=dict(Yes="g", No="m"))
g0 = pw.load_seaborngrid(g0, label="g0")

# A Pairplot
g1 = sns.pairplot(iris, hue="species")
g1 = pw.load_seaborngrid(g1, label="g1", figsize=(6,6))

# A relplot
g2 = sns.relplot(data=tips, x="total_bill", y="tip", col="time", hue="time", 
                 size="size", style="sex", palette=["b", "r"], sizes=(10, 100))
g2.set_titles("")
g2 = pw.load_seaborngrid(g2, label="g2")

# A JointGrid
g3 = sns.jointplot("sepal_width", "petal_length", data=iris,
                   kind="kde", space=0, color="g")

g3 = pw.load_seaborngrid(g3, label="g3")
```
次に、これらg0, g1, g2, g3のプロットを`|`と`/`使って並べてみる。 例えば、

```
g0|g1
g3|g2
```

みたいな感じでplotを並べたい場合は、以下のように書くことができる。

```python
((g0/g3)|(g1/g2)).savefig() #savefigは引数がない時はFigure ojbectを返す。"hoge.png"のようにFile nameを書けば、結果の図を保存できる。
```

すると、以下の図が出力される。g0とg1の枠がずれてるぞと思われるかもしれないが、これは、(g0/g3)と(g1/g2)の枠が揃うようにスケーリングしているためで、g0とg1の枠が揃うことは考慮されていないからである。
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167957/372de69d-da2e-6552-0546-2a2260b7f58b.png)

もし、g0とg1の枠を揃えたいのであれば、以下のように書くこともできる。

```python
(((g0/g3)["g0"]|g1)["g1"]/g2).savefig()
```
すると、以下の図が出力される。ここでは、まず`(g0/g3)["g0"]|g1`で`(g0/g3)`の`g0`の右に`g1`を配置しており、そのあとに、`((g0/g3)["g0"]|g1)["g1"]/g2`で`((g0/g3)["g0"]|g1)`の`g1`の下に`g2`を配置するということを行なっている。このとき、最後に配置される`g2`は余っているスペースに収まるようにスケーリングされるため、4つのplotの枠を全て揃えることができる。（ただし、その結果として`g2`のplotのアスペクト比が、オリジナルのplotから変化する点には注意。）


![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167957/e6a21b09-c75f-3378-b7c7-58aedc471de2.png)

また、plotのlegendを動かしたり、論文の図のように、"A", "B", "C", "D"といったindexをつけることも可能である。　例えば、g0のlegendが右側にあるのは邪魔なので、プロット中左上に動かし、各plotに"A", "B", "C", "D"のindexをつけるといった場合は、以下のようなコードを書いて、プロットを再配置すれば良い。

```python
g0.case.set_title('A', x=0, y=1.0, loc="right")
g0.move_legend("upper left", bbox_to_anchor=(0.1,1.0))
g1.case.set_title('B', x=0, y=1.0, loc="right")
g3.case.set_title('C', x=0, y=1.0, loc="right")
g2.case.set_title('D', x=0, y=1.0, loc="right")
(((g0/g3)["g0"]|g1)["g1"]/g2).savefig()
```
すると、以下の図が生成される。　
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167957/dec27d27-910e-4529-aa55-1b5467ba343e.png" width="８00x８00">

こんな風にpatchworklibを使えば、seabornのplot配置もおちゃのこさいさいである。上記のコードは、[Google colab](https://colab.research.google.com/drive/1C1EN1-5L2QjnAAVo4vpx_i9knDRzX4jy?usp=sharing)からも実行することができるので、ぜひ試してみて欲しい。

###plotnineの配置の例
同様に、plotnineのplotについても`patchworlklib.load_ggplot()`で取り込めば自在に配置することが可能である。以下、コードと結果の図。

```python
import patchworklib as pw
from plotnine import *
from plotnine.data import *
g1 = pw.load_ggplot(ggplot(mtcars) + geom_point(aes("mpg", "disp")),figsize=(2, 3))
g2 = pw.load_ggplot(ggplot(mtcars) + geom_boxplot(aes("gear", "disp", group = "gear")),figsize=(2, 3))
g3 = pw.load_ggplot(ggplot(mtcars, aes('wt', 'mpg', color='factor(gear)')) + geom_point() + stat_smooth(method='lm') + facet_wrap('~gear'), figsize=(3,3))
g4 = pw.load_ggplot(ggplot(data=diamonds) + geom_bar(mapping=aes(x="cut", fill="clarity"), position="dodge"), figsize=(5, 2))
g1234 = (g1|g2|g3)/g4
g1234.savefig()
```
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167957/142bae7b-19a0-7056-dcf9-68406fbd9556.png)

###ggplotとseabornの夢の共演。
もちろん、ggplot(plotnine)とseabornの図を混ぜて配置することも可能である。例えば、以下のような図を作ることができる。コードは[Google colab](https://colab.research.google.com/drive/1C1EN1-5L2QjnAAVo4vpx_i9knDRzX4jy?usp=sharing)を参照して欲しい。
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167957/b8093797-35eb-1976-babc-d5fd01abfd3c.png)