#はじめに
この記事で紹介しているmaplotlib用のパッケージpatchworklibを用いると、以下のような図版が簡単に作れるようになる。図版作成で時間を浪費してる方には是非使ってみて欲しい。
##Seabornやplotnineのplotが並べられない。
Pythonでグラフを作る際に最も有名なライブラリといえばmaplotlibであろう。ただ、matplotlibで凝った図を作ろうとするとめちゃくちゃめんどい。そんなとき、凝った図をわずか数行で作成することを可能にしてくれるのが、Seabornやplotnine(python版 ggplot)といったライブラリである。
しかし、seabornやplotnineが作ってくれる図には1つ大きな欠点がある。それは、異なるコマンドで作成された図を並べることができないということだ。。。
厳密には、seabornではaxes levelの関数には自分で作ったaxes objectを渡すことができるので、これらの関数で作ったプロットについては、自由に並べることが可能である(詳しい説明は、公式のページがわかりやすい)。ただ、ぶっちゃけaxes levelの関数で実現できることなら、matplotlibで1から書けないこともないし、その方がカスタマイズも効く。どちらかというと、lmplot, relplot, displot, 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が並べられるよ?」
と言う人もいるかもしれない。(この記事が詳しい)。それはその通りなのだが、解析と図の作成でPyhtonとRを行き来するのも面倒くさいし、Google colabで解析のプロセスを共有するときも不便だ。
ということで、筆者は長らく、どうにかしてseabornやplotnineのplotを良い感じ1つの図版にまとめられないだろうかと思案していたのであった。
##無理矢理、作ってみた。
細かい実装の説明は省くが、色々と頑張って、以前に自分が作成した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
で取り込む。
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を並べたい場合は、以下のように書くことができる。
((g0/g3)|(g1/g2)).savefig() #savefigは引数がない時はFigure ojbectを返す。"hoge.png"のようにFile nameを書けば、結果の図を保存できる。
すると、以下の図が出力される。g0とg1の枠がずれてるぞと思われるかもしれないが、これは、(g0/g3)と(g1/g2)の枠が揃うようにスケーリングしているためで、g0とg1の枠が揃うことは考慮されていないからである。
もし、g0とg1の枠を揃えたいのであれば、以下のように書くこともできる。
(((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から変化する点には注意。)
また、plotのlegendを動かしたり、論文の図のように、"A", "B", "C", "D"といったindexをつけることも可能である。 例えば、g0のlegendが右側にあるのは邪魔なので、プロット中左上に動かし、各plotに"A", "B", "C", "D"のindexをつけるといった場合は、以下のようなコードを書いて、プロットを再配置すれば良い。
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()
こんな風にpatchworklibを使えば、seabornのplot配置もおちゃのこさいさいである。上記のコードは、Google colabからも実行することができるので、ぜひ試してみて欲しい。
###plotnineの配置の例
同様に、plotnineのplotについてもpatchworlklib.load_ggplot()
で取り込めば自在に配置することが可能である。以下、コードと結果の図。
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()
###ggplotとseabornの夢の共演。
もちろん、ggplot(plotnine)とseabornの図を混ぜて配置することも可能である。例えば、以下のような図を作ることができる。コードはGoogle colabを参照して欲しい。