patchworklibを使って複数のplotを組み合わせる

# patchworklib
この記事は、[matplotlibのsubplotはもうやめたい その２](https://qiita.com/ponnhide/items/62b7e062e79b0eca6daa)の続きである。
最近、matplotlibで作ったplotを簡単に並べるためのツール[patchworklib](https://github.com/ponnhide/patchworklib)を公開した。ここではその利用法を簡単に解説する。
正直なところ、自分がmatplotlibのplotを並べるために使っていた自分用ツールを公開しているだけなので、他の人にとって使いどころがあるのかについては、あまり自信がない。。。しかし、subplotで時間を浪費している方々の役に立つのではないかと思う。　　
ツールを作った経緯についてはは前の記事を参考にしてほしい。

# インストール
installは
`git clone https://github.com/ponnhide/patchworklib.git`
でgithubのレポジトリをクローンしてもらって、
`python setup.py install`
できるはず。

# 使い方
利用法は至ってシンプル。matplotlibの`matplotlib.axes.Axes class`のsubclassとして実装された`Brick class`のobjectを`'|'`と`'/'`演算子でくっつけてやれば、簡単に様々なレイアウトを試すことができる。　
つまり、Patchworklibを使えば、subplotが面倒な、seabornを利用して作ったplotも簡単に1つのFigureにまとめることができる。  （ただし、axis objectを引数にできるものに限る。）　
以下の例では、seabornの各plot functionの引数`ax`に`Brick class object`を指定することで、plotが描画された`Brick class object`を生成し、それらを `"|"` or `"/"` のoperandで組み合わせている。　　

## 下準備
`Brick class object`は作成時に*`label`*と*`figsize`*の2つの引数をとる。
*`label`*の値は、スクリプト内で生成されう他のBrick class objectとは被らない固有の値を指定する必要がある。
*`figsize`*の値は、プロットを並べる過程で自動的に調整されてしまうし、アスペクト比も後で`change_aspectratio()`で変更可能なので何でもいい。
とりあえず、seabornのexample datasetを利用して、1つplotを作成してみる。

```python
import patchworklib as pw #pathworklibのimport
import seaborn as sns #seabornのimport

#reference: https://seaborn.pydata.org/examples/errorband_lineplots.html
fmri = sns.load_dataset("fmri")
ax1 = pw.Brick("ax1", figsize=(4,2))
sns.lineplot(x="timepoint", y="signal", hue="region", style="event", data=fmri, ax=ax1)
ax1.set_title("ax1")
ax1.savefig() 
```
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167957/4564e79a-7f76-60d3-caab-566f38e19a37.png)

つついて、もう一つ作成。 
大した機能ではないのだが、seabornのlegend指定が使いにくすぎてムカついたので、`Brick class object`にはプロット後にlegendの位置を変更できる `move_legend(`*`new_loc`*`,`*`bbox_to_ancor`*`)` を実装してある。デフォルトのlegendの位置が気に入らなかった場合に使ってほしい。

```python
#reference: https://seaborn.pydata.org/tutorial/categorical.html 
#オリジナルの例は、catplotを使ってるけど、caplotはaxis objectを引数にできないのでbarplotを使っている。
titanic = sns.load_dataset("titanic")
ax2 = pw.Brick("ax2", figsize=(1,2))
sns.barplot(x="sex", y="survived", hue="class", data=titanic, ax=ax2)
ax2.move_legend(new_loc='upper left', bbox_to_anchor=(1.05, 1.0))
ax2.set_title("ax2")
ax2.savefig()
```
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167957/21f23d84-90bd-ca6a-12dd-3664f07c1583.png)

さらに、もう1つ作成しちゃおう。

```python 
#reference: https://seaborn.pydata.org/examples/histogram_stacked.html
diamonds = sns.load_dataset("diamonds")
ax3 = pw.Brick("ax3", (6,2))
sns.histplot(diamonds, x="price", hue="cut", multiple="stack",
    palette="light:m_r", edgecolor=".3", linewidth=.5, log_scale=True,
    ax = ax3)
ax3.move_legend(new_loc='upper left', bbox_to_anchor=(1.0, 1.0))
ax3.set_title("ax3")
ax3.savefig()
```
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167957/99456b4b-c34e-6b89-925d-342feef6efd0.png)

## シンプルな連結の例
それでは、この３つのプロットを並べてみよう。 
自分でアピールするのもなんだが、patchworklibを使ってプロット並べるのはとても簡単である。 
例えば、横方向に並べるときは`|`を使ってBrick objectを連結してあげればいい。 
連結後のobjectは、.savefig(*filename*)`で任意のファイルに出力することができる。　*filename* を指定しなければ、単にmatplotlibのFigure class objectが返り値として返ってくる。

```python
ax123 = ax1 | ax2 | ax3
ax123.savefig()
``` 

![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167957/46e6f224-1d0e-559d-5fbb-bb970e0f7361.png)

縦方向に並べるときは、`/`を使うことができる。 当然、`|`をつかった連結と組み合わせることも可能。
例えば、１行目にax1とax2を、2行目にax3を並べたい場合は以下のようすれば良い。

```python3 
ax123 = (ax1 | ax2) / ax3
ax123.savefig()
```
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167957/0f3b7232-d0a9-e65c-1a60-cd411c025cc2.png)

「やっぱり、ax1のlegendが図と被っててじゃまだから、動かしたいなぁ」と思った場合は、`move_legend`でlegend位置を調整した後、もう一度連結し直してもらえればおk。　図の位置や大きさは、お互いが被らないように勝手に調整されるので安心してほしい。

```python
ax1.move_legend(new_loc='upper left', bbox_to_anchor=(1.0, 1.0))
ax123 = (ax1 | ax2) / ax3
ax123.savefig()
```
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167957/525febe7-dea8-8dd5-c351-4d6e4f9ef8d1.png)

「やっぱり、1行目と2行目の位置関係を逆にしたい！」みたいなときも、以下のように並べ直してあげればおk。

```python
ax123 = ax3 / (ax1 | ax2) 
ax123.savefig()
```
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167957/9a5fcbc8-2303-0831-ceb2-d43a5766bb82.png)

「ax3のアスペクト気に入らんなぁ」と思った場合は、ax3のアスペクト比を`change_aspectratio`で調整した後にもう一度並べ直してもらえればおk。

```python
ax3.change_aspectratio((4,1))
ax123 = ax3 / (ax1 | ax2) 
ax123.savefig()
```
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167957/0bfc42cd-7ef7-ec26-313f-129975e6cac4.png)

## ちょっと複雑な例。
実は、Brick objectを連結した返り値である`Bricks class object`は、他の`Brick class object`や`Bricks class object`と容易に連結することが可能である。
ここでは、この機能を使って"joint plot"という2次元と1次元の分布図を組み合わせた形式の plot を作成する。
尚、「seabornの`jointplot`使えば、すぐにできるんですけど?」といった意見は受け付けていない。

まずは、2次元分布図の上側の配置するヒストグラムを作成する。

```python
penguins = sns.load_dataset("penguins")
ax4 = pw.Brick("ax4", figsize=(2,1))
sns.histplot(data=penguins, x="flipper_length_mm", kde=True, ax=ax4)
ax4.spines["top"].set_visible(False)
) 
ax4.spines["right"].set_visible(False)
ax4.savefig()
```
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167957/94ce8194-405b-30b0-3c67-13a20728d348.png)

同様に、2次元分布図の右側に配置するヒストグラムを作成する

```python
ax5 = pw.Brick("ax5", figsize=(1,2))
sns.histplot(data=penguins, y="bill_length_mm", kde=True, ax=ax5)
ax5.spines["top"].set_visible(False) 
ax5.spines["right"].set_visible(False)
ax5.savefig()
```
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167957/8622074f-0697-6fd9-5ea6-3e20f7ad7c13.png)

そして、2次元分布図を作成。これで部品は揃った。

```python
ax6 = pw.Brick("ax6", figsize=(2,2))
sns.scatterplot(data=penguins, x="flipper_length_mm", y="bill_length_mm", ax=ax6)
ax6.spines["top"].set_visible(False) 
ax6.spines["right"].set_visible(False)
ax6.savefig()
```

上側ヒストグラムのx軸、右側ヒストグラムのy軸目盛りとラベルは、邪魔くさいので消してしまおう。
`Brick class object`は matplotlibの`axis class`のサブクラスなので、普通の`axis class object`と同様に操作することができる。

```python
ax4.set_xlim(ax6.get_xlim())
ax4.set_xticks([])
ax4.set_xlabel("") 

ax5.set_ylim(ax6.get_ylim())
ax5.set_yticks([])
ax5.set_ylabel("")
```

説明するのを忘れてしまっていたが、図を並べる際にのmargin(余白)は、以下のように変更することが可能。
まずは、余白を0.5から0.1に変更して、上側のヒストグラム(ax4)を2次元分布図(ax6)と連結しよう。

```python
pw.param["margin"] = 0.1 #default value is 0.5
ax46 = ax4 / ax6
ax46.savefig()
```
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167957/5b4aa96f-df8f-231f-20dd-7f69aa96ffad.png)

さて、ここで右側のヒストグラム(ax5)を単に`|`でax46と連結すると以下のような不細工な図が生成されてしまう。　

```python
a = 1
ax456 = ax46 | ax5 
ax456.savefig()
```
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167957/37212f90-f94a-2b32-6240-d1a0e966de5e.png)

これでは、jointplotの体を成していないので、ax5が、ax46の中のax6の右側にくるように配置を修正する必要がある。
実は、`Bricks　class object`に対する連結では、`Bricks class object`中にある`Brick class object`のlabel名を指定することで、並べるobjectの位置を調整することができる。ここでは、`ax46["ax6"]`としてやれば、ax5をax6の右側に配置することができる。　

```python
ax456 = ax46["ax6"] | ax5
ax456.savefig()
```
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167957/26ca4cc8-ca62-fe2b-0d7b-a1fb40192ad8.png)

当然`Bricks class object`同士も並べることができるので、先ほど作ったax123と、ax456を連結してやると以下のようになる。

```
pw.param["margin"] = 0.5
ax123456 = ax123|ax456
ax123456.savefig() 
```

![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167957/19e8a024-95b8-564c-a20e-b53c81613f96.png)

## 終わりに
使い方はこれで以上である。残念ながら本家patchworkのように、タイトルやキャプション、各図のラベルなどを調整する機能は持ち合わせていない。まぁそこらへんはaxes class objectを直接いじって調整していただきたい。

また、現状polor plotと組み合わせる機能も存在しない。
matplotlibで良い感じのpolor plotを作成するためのツールを公開していることもあり ( https://github.com/ponnhide/pyCircos )、 個人的にもpolor plotは扱えるようにしたいのだが、大元のaxes classが直交座標と極座標では別classということもあり実装が面倒なのである。

自身が論文の図を作成するときには、patchworklibのカスタマイズ版を使っていたわけだが、色々なプロットを組み合わせた図を作成することが多い人には、そこそこ有用だと思うので参考にしてもらえればと思う。