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の引数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を作成してみる。
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()
つついて、もう一つ作成。
大した機能ではないのだが、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()
さらに、もう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()
シンプルな連結の例
それでは、この3つのプロットを並べてみよう。
自分でアピールするのもなんだが、patchworklibを使ってプロット並べるのはとても簡単である。
例えば、横方向に並べるときは|
を使ってBrick objectを連結してあげればいい。
連結後のobjectは、.savefig(filename)`で任意のファイルに出力することができる。 filename を指定しなければ、単にmatplotlibのFigure class objectが返り値として返ってくる。
ax123 = ax1 | ax2 | ax3
ax123.savefig()
縦方向に並べるときは、/
を使うことができる。 当然、|
をつかった連結と組み合わせることも可能。
例えば、1行目にax1とax2を、2行目にax3を並べたい場合は以下のようすれば良い。
ax123 = (ax1 | ax2) / ax3
ax123.savefig()
「やっぱり、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()
「やっぱり、1行目と2行目の位置関係を逆にしたい!」みたいなときも、以下のように並べ直してあげればおk。
ax123 = ax3 / (ax1 | ax2)
ax123.savefig()
「ax3のアスペクト気に入らんなぁ」と思った場合は、ax3のアスペクト比をchange_aspectratio
で調整した後にもう一度並べ直してもらえればおk。
ax3.change_aspectratio((4,1))
ax123 = ax3 / (ax1 | ax2)
ax123.savefig()
ちょっと複雑な例。
実は、Brick objectを連結した返り値であるBricks class object
は、他のBrick class object
やBricks 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()
同様に、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()
そして、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()
さて、ここで右側のヒストグラム(ax5)を単に|
でax46と連結すると以下のような不細工な図が生成されてしまう。
a = 1
ax456 = ax46 | ax5
ax456.savefig()
これでは、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()
当然Bricks class object
同士も並べることができるので、先ほど作ったax123と、ax456を連結してやると以下のようになる。
pw.param["margin"] = 0.5
ax123456 = ax123|ax456
ax123456.savefig()
終わりに
使い方はこれで以上である。残念ながら本家patchworkのように、タイトルやキャプション、各図のラベルなどを調整する機能は持ち合わせていない。まぁそこらへんはaxes class objectを直接いじって調整していただきたい。
また、現状polor plotと組み合わせる機能も存在しない。
matplotlibで良い感じのpolor plotを作成するためのツールを公開していることもあり ( https://github.com/ponnhide/pyCircos )、 個人的にもpolor plotは扱えるようにしたいのだが、大元のaxes classが直交座標と極座標では別classということもあり実装が面倒なのである。
自身が論文の図を作成するときには、patchworklibのカスタマイズ版を使っていたわけだが、色々なプロットを組み合わせた図を作成することが多い人には、そこそこ有用だと思うので参考にしてもらえればと思う。