はじめに
Python の可視化ライブラリ Matplotlib で論文用の図を作成する際、subplot に (a), (b), (c) といったラベルを配置するのは一般的です。しかし、Matplotlib の plt.subplots
で subplot を並べ、height_ratios
などで大きさが異なると、ax.text()
で相対位置を指定した場合、見た目としてラベルの位置が揃わず違和感が生じることがあります。
本記事では、Python と Matplotlib を用いた subplot におけるラベル配置の課題について解説し、論文用グラフとして一貫した美しい見た目を保つための実用的な関数例を紹介します。
実装コードはGoogle Colab こちら からも閲覧できます。
よくあるズレの例
たとえば、次のように 2:1 の subplot を並べる場合を考えます。
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
fig = plt.figure(figsize=(6, 4))
gs = gridspec.GridSpec(2, 1, height_ratios=[2, 1])
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[1, 0])
ax1.plot([1, 2, 3], [1, 4, 9])
ax2.plot([1, 2, 3], [1, 4, 9])
ax1.text(0.02, 0.95, '(a)', ha='left', va='top', transform=ax1.transAxes)
ax2.text(0.02, 0.95, '(b)', ha='left', va='top', transform=ax2.transAxes)
plt.show()
transform=ax.transAxes
により各 subplot 内の正規化された座標系でラベルが配置されていますが、subplot のプロット領域では (a) の位置が (b) に比べて少し下がって見えます。これを「ズレ」として、これを整える方法を考えたいと思います。
原因
ax.transAxes
は subplot 内の座標系なので、subplot の大きさが異なると同じ (0.02, 0.95) という指定でも、見た目として異なる位置に配置されます。
手っ取り早い解決策としては、以下のように個別に値を調整することも可能です。
ax1.text(0.02, 0.975, '(a)', ha='left', va='top', transform=ax1.transAxes)
ax2.text(0.02, 0.95, '(b)', ha='left', va='top', transform=ax2.transAxes)
ax1 の height_ratio
は ax2 に対して 2 倍なので、比率も 2 倍になります。そのため、0.025 の余白を足すことで両者の余白を統一しています。
ただし、このように個別に微調整するのは手間がかかります。そこで、以下に紹介する方法を用いると、任意の subplot 配置に対して一貫したラベル配置が可能になります。
解決策:figure 全体基準で統一
ax.get_position()
により subplot の配置領域(bbox)を取得し、その bbox に対する相対位置でラベルを配置することで、subplot のサイズに依存しない一貫した「見た目の位置」にラベルを配置できます。
自動化関数の例
以下は、複数 subplot に (a), (b), (c)… を自動で配置する関数の例です。
def add_subplot_labels(
fig, axes, labels=None, dx=0.01, dy=-0.01, loc='upper left', **kwargs
):
"""
fig : Figure
axes : list of Axes
labels : list of str, optional
dx, dy : float, オフセット(fig座標系、デフォルトは左上から少し右下)
loc : str, 'upper left', 'lower left', 'upper right', 'lower right'
kwargs : fig.textに渡す引数
"""
if labels is None:
labels = ['({})'.format(chr(97 + i)) for i in range(len(axes))]
for ax, label in zip(axes, labels):
bbox = ax.get_position()
if loc == 'upper left':
xpos = bbox.x0 + dx
ypos = bbox.y1 + dy
ha = 'left'
va = 'top'
elif loc == 'upper right':
xpos = bbox.x1 + dx
ypos = bbox.y1 + dy
ha = 'right'
va = 'top'
elif loc == 'lower left':
xpos = bbox.x0 + dx
ypos = bbox.y0 + dy
ha = 'left'
va = 'bottom'
elif loc == 'lower right':
xpos = bbox.x1 + dx
ypos = bbox.y0 + dy
ha = 'right'
va = 'bottom'
else:
raise ValueError("loc must be 'upper left', 'upper right', 'lower left', or 'lower right'.")
fig.text(xpos, ypos, label, ha=ha, va=va, **kwargs)
使用例
この関数を subplot に適用すると次のようになります。
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
fig = plt.figure(figsize=(6, 4))
gs = gridspec.GridSpec(2, 1, height_ratios=[2, 1])
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[1, 0])
ax1.plot([1, 2, 3], [1, 4, 9])
ax2.plot([1, 2, 3], [1, 4, 9])
axes = [ax1, ax2]
add_subplot_labels(fig, axes, dx=0.01, dy=-0.02, loc='upper left')
plt.show()
subplot のサイズが異なっていても、見た目としてきれいに揃った位置にラベルを配置できます。
補足
任意のラベルを指定する場合
任意のラベルを指定したい場合は以下のようにします。
axes = [ax1, ax2]
labels = ['(1)', '(2)']
add_subplot_labels(fig, axes, labels, dx=0.01, dy=-0.02, loc='upper left')
スタイルを調整したい場合
このほかにも、color='blue'
や fontsize=14
などのスタイル調整用の引数を、そのままこの関数に渡すことで、文字色やフォントサイズも自由に調整できます。
add_subplot_labels(fig, axes, dx=0.01, dy=-0.02, loc='upper left', color='blue', fontsize=16)
まとめ
この記事では、subplot のサイズが異なる場合にもきれいにラベルを自動配置する方法を紹介しました。
筆者自身、毎回手動で位置を調整する呪縛から解放されたように感じており、この関数をこれから愛用したいと思っています。
論文ってデジタルタトゥーだから、ついミリ単位で整えたくなるんですよね。
この記事がそんな方のお役に立てばうれしいです。