Matplotlib で複数のグラフを自由自在に配置する
上記のような図をmatplotlibで作成するのは意外と難しい。
まず、思いつくのはsubplotを持ちいることである。しかしながら、subplot では同じアスペクトの図しか並べることができない。subplot2grid を利用すると多少は複雑な配置も可能にはなるが、アスペクトの制約はつきまとう。そこで今回は、 add_axesとbbox_inchesを駆使して、上記の図を作成した。
まずは、1パネル分のスクリプト
# coding: UTF-8
import sys
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib import rcParams
rcParams['font.family'] = 'Arial'
if __name__ == "__main__":
fig = plt.figure(figsize=(3,3))
#add_axes([x軸の開始位置, y軸の開始位置, x軸の長さ(全体に対する比率), y軸の長さ(全体に対する比率)])
ax1 = fig.add_axes([0.10,0.10,0.60,0.60])
ax2 = fig.add_axes([0.71,0.10,0.20,0.60])#左のヒストグラム図
ax3 = fig.add_axes([0.10,0.71,0.60,0.20])#上のヒストグラム図
ax1.spines['top'].set_visible(False)
ax1.spines['right'].set_visible(False)
ax1.tick_params(top="off",right="off",direction="out",labelsize=10,pad=5)
ax1.tick_params(right="off")
ax2.spines['top'].set_visible(False)
ax2.spines['right'].set_visible(False)
ax2.spines['bottom'].set_visible(False)
ax2.set_xticks([])
ax2.set_yticks([])
ax3.spines['top'].set_visible(False)
ax3.spines['right'].set_visible(False)
ax3.spines['left'].set_visible(False)
ax3.set_xticks([])
ax3.set_yticks([])
data = np.random.randn(2000,2000)
ax1.scatter(data.T[0],data.T[1],s=5)
ax2.hist(data.T[1],range=(-2,2),bins=100,orientation="horizontal")
ax3.hist(data.T[1],range=(-2,2),bins=100)
ax1.set_xlim(-2,2)
ax1.set_ylim(-2,2)
ax3.set_xlim(-2,2)
fig.savefig("test.pdf",bbox_inches="tight")
Seabornをつかわなくても、こんな風にadd_axesを使えば散布図とヒストグラムを組み合わせた joint plot を作ることができる。ただし、最後にbbox_inches="tight"をつけないとlabel等が少しはみ出ることがある(左図)。つけるとはみ出た部分まで表示してくれる(右図)。
厳密には bbox_inches=tight を指定すると、枠外にはみ出しているいないに関わらず、全てのobjectをきっちり表示してくれるようになる。枠外にはみ出しているいないに関わらず、全てのobjectをきっちり表示してくれるようになる。
つまり、add_axesにおいてobjectを 0≤x≤1、0≤y≤1 の枠の中に納める必要なんてものは実はどこにもない、ということで、先のパネルを複数枚並べたいのであれば、もうあとは簡単。add_axes で指定した x,y 座標の始点を並べたい方向に1.0 ずらしてしまえばいい。たとえば、5x5個ならべたいのであれば、以下のようにfor文で回して、、、
# coding: UTF-8
import sys
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib import rcParams
rcParams['font.family'] = 'Arial'
if __name__ == "__main__":
fig = plt.figure(figsize=(3,3))
for i in range(5):
for j in range(5):
ax1 = fig.add_axes([j + 0.10, 5 - i + 0.10,0.60,0.60])
ax2 = fig.add_axes([j + 0.71, 5 - i + 0.10,0.20,0.60])
ax3 = fig.add_axes([j + 0.10, 5 - i + 0.71,0.60,0.20])
ax1.spines['top'].set_visible(False)
ax1.spines['right'].set_visible(False)
ax1.tick_params(top="off",right="off",direction="out",labelsize=10,pad=5)
ax1.tick_params(right="off")
ax2.spines['top'].set_visible(False)
ax2.spines['right'].set_visible(False)
ax2.spines['bottom'].set_visible(False)
ax2.set_xticks([])
ax2.set_yticks([])
ax3.spines['top'].set_visible(False)
ax3.spines['right'].set_visible(False)
ax3.spines['left'].set_visible(False)
ax3.set_xticks([])
ax3.set_yticks([])
data = np.random.randn(2000,2000)
ax1.scatter(data.T[0],data.T[1],s=5)
ax2.hist(data.T[1],range=(-2,2),bins=100,orientation="horizontal")
ax3.hist(data.T[1],range=(-2,2),bins=100)
ax1.set_xlim(-2,2)
ax1.set_ylim(-2,2)
ax3.set_xlim(-2,2)
ax3.set_title(str(1+i*5+j),fontsize=10)
fig.savefig("test.pdf",bbox_inches="tight")
とすればOK。