11
8

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-11-27

patchworklib

この記事は、matplotlibのsubplotはもうやめたい その2の続きである。
最近、matplotlibで作ったplotを簡単に並べるためのツール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の引数axBrick class objectを指定することで、plotが描画されたBrick class objectを生成し、それらを "|" or "/" のoperandで組み合わせている。  

下準備

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

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

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

#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

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

#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

シンプルな連結の例

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

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

download.png

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

ax123 = (ax1 | ax2) / ax3
ax123.savefig()

download.png

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

ax1.move_legend(new_loc='upper left', bbox_to_anchor=(1.0, 1.0))
ax123 = (ax1 | ax2) / ax3
ax123.savefig()

download.png

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

ax123 = ax3 / (ax1 | ax2) 
ax123.savefig()

download.png

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

ax3.change_aspectratio((4,1))
ax123 = ax3 / (ax1 | ax2) 
ax123.savefig()

download.png

ちょっと複雑な例。

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

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

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

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

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

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

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と同様に操作することができる。

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)と連結しよう。

pw.param["margin"] = 0.1 #default value is 0.5
ax46 = ax4 / ax6
ax46.savefig()

download.png

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

a = 1
ax456 = ax46 | ax5 
ax456.savefig()

download.png

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

ax456 = ax46["ax6"] | ax5
ax456.savefig()

download.png

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

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

download.png

終わりに

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

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

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

11
8
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
8