1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Matplotlibで一部のサブプロットにだけ共通ラベルを美しく配置する方法

Posted at

はじめに

Matplotlibで複数のサブプロット(Axes)を並べて図を作成する際、特定のグループだけに共通のX軸ラベルやY軸ラベルを1つだけ配置したいという場面があります。

たとえば、「左側の2つのプロットだけに共通のYラベルを置きたい」「下段の右2つだけに共通のXラベルを付けたい」といったケースです。

このように、任意のAxesグループにだけ共通ラベルを付けるという操作は、意外にもMatplotlibの標準機能では直接サポートされておらず、ごく限られた場合にしか実現できません。

その一例が、Matplotlib 3.4以降で追加された fig.supxlabel()fig.supylabel() といった共通ラベル用の関数です。

fig.supxlabel("Common X")
fig.supylabel("Common Y")

この機能を使えば、Figure全体に対して中央に共通ラベルを配置するのはとても簡単です。
ですが、特定の一部のAxesにだけ共通ラベルを付けたいという、より柔軟なニーズには対応していません。


そこで本記事では、以下のような用途を想定したカスタムな共通ラベル配置手法をご紹介します。

  • 任意の複数のAxesを「グループ」としてまとめ、そのグループにだけ共通ラベルを配置できる
  • direction='x' または 'y' を指定することで、X軸・Y軸どちらのラベルかを明示できる
  • gap=True を指定すると、偶数個のAxesグループにおいて中央の「隙間」にラベルを配置し、視覚的なバランスを取ることができる

複雑なグリッド構成でも柔軟にラベルを扱えるようになるため、学術論文やプレゼン資料の図作成に活用しやすく、見栄えのよい図を手軽に仕上げることができます。


実装コードはGoogle Colab こちら からも閲覧できます。

実装関数

以下の関数を定義することで、任意のAxesグループに共通ラベルを簡単に配置できます。

import matplotlib.pyplot as plt

def get_group_center_position(axes, direction):
    # グループのバウンディングボックス中央
    positions = [ax.get_position() for ax in axes]
    if direction == 'x':
        x0 = min(pos.x0 for pos in positions)
        x1 = max(pos.x1 for pos in positions)
        y0 = min(pos.y0 for pos in positions)
        x_center = (x0 + x1) / 2
        return x_center, y0
    elif direction == 'y':
        y0 = min(pos.y0 for pos in positions)
        y1 = max(pos.y1 for pos in positions)
        x0 = min(pos.x0 for pos in positions)
        y_center = (y0 + y1) / 2
        return x0, y_center
    else:
        raise ValueError("direction must be 'x' or 'y'")

def get_even_gap_position(axes, direction):
    # 偶数個グループ中央gap位置
    positions = [ax.get_position() for ax in axes]
    n = len(axes)
    idxs = list(range(n))
    if direction == 'x':
        idxs_sorted = sorted(idxs, key=lambda i: positions[i].x0)
        idxL, idxR = idxs_sorted[n//2 - 1], idxs_sorted[n//2]
        x_left = positions[idxL].x1
        x_right = positions[idxR].x0
        x_gap = (x_left + x_right) / 2
        y0 = min(pos.y0 for pos in positions)
        return x_gap, y0
    elif direction == 'y':
        idxs_sorted = sorted(idxs, key=lambda i: positions[i].y0)
        idxB, idxT = idxs_sorted[n//2 - 1], idxs_sorted[n//2]
        y_bottom = positions[idxB].y1
        y_top = positions[idxT].y0
        y_gap = (y_bottom + y_top) / 2
        x0 = min(pos.x0 for pos in positions)
        return x0, y_gap
    else:
        raise ValueError("direction must be 'x' or 'y'")

def add_group_label(fig, axes, label, direction='x', offset=0.08, gap=False, rotation=None, **kwargs):
    """
    指定したAxesグループに共通ラベルを配置(gap=Trueなら偶数個グループで溝の間に、gap=Falseでグループ中央)
    """
    axes = list(axes)
    n = len(axes)
    if gap:
        if n % 2 != 0:
            raise ValueError("The gap option (gap=True) can only be used for groups with an even number of axes.")
        x_fig, y_fig = get_even_gap_position(axes, direction)
    else:
        x_fig, y_fig = get_group_center_position(axes, direction)
    if direction == 'x':
        y_fig = y_fig - offset
        rot = 0 if rotation is None else rotation
        ha, va = 'center', 'top'
    elif direction == 'y':
        x_fig = x_fig - offset
        rot = 90 if rotation is None else rotation
        ha, va = 'center', 'center'
    else:
        raise ValueError("direction must be 'x' or 'y'")
    fig.text(x_fig, y_fig, label, ha=ha, va=va, rotation=rot, **kwargs)

関数の引数

add_group_label() は以下のような引数を取ります:

  • fig:共通ラベルを配置する対象の Figure オブジェクト
  • axes:共通ラベルを付けたい Axes オブジェクトのリスト
  • label:表示するラベルの文字列
  • direction'x' または 'y'。X軸・Y軸のどちらにラベルを付けるかを指定
  • offset:ラベルを軸からどれくらい離すか(軸の外側方向にずらす距離)
  • gapTrue にすると、偶数個の axes の中央の隙間(gap)にラベルを配置。視覚的なバランスを整えるのに便利
  • rotation:ラベルの回転角度。'x' ではデフォルト0度、'y' では90度
  • **kwargsfig.text() に渡されるその他のオプション(fontsize, color など)

使用例

1. 基本的な使い方(均一なグリッド構成)

fig, axs = plt.subplots(2, 3, figsize=(9, 4), sharex=True, sharey=True)

for ax in axs.flat:
    ax.plot([0, 1], [0, 1])

# 最左列(上下ペア)に共通Yラベル
add_group_label(fig, [axs[0,0], axs[1,0]], "Left Pair Y", direction='y', offset=0.07)

# 下段の左2つに共通Xラベル
add_group_label(fig, [axs[1,0], axs[1,1]], "Bottom Left X", direction='x', offset=0.08)

# 下段の右端だけ個別Xラベル
add_group_label(fig, [axs[1,2]], "Bottom Right X", direction='x', offset=0.08)

plt.show()

出力結果
image.png

ラベルのペアとして指定したいパネル(Axes)をリストで選び、add_group_label() に渡すだけで、共通ラベルを配置できます。
さらに、fontsizecolor など fig.text() に渡せる引数もそのまま指定できるため、ラベルの見た目も自由にカスタマイズ可能です。


2. width_ratiosheight_ratios を変えたときの注意

等間隔グリッドでの使い方に慣れたところで、次は「幅や高さが異なるグリッド構成」に対応するケースを見てみましょう。
gridspec_kw={'width_ratios':[2, 1, 1]} のようにプロットの横幅比が異なる場合どうなるでしょうか。

Gap=False
fig, axs = plt.subplots(2, 3, figsize=(9, 4), width_ratios=[2, 1, 1], sharex=True, sharey=True)

for ax in axs.flat:
    ax.plot([0, 1], [0, 1])

# 1. 最左列(上下ペア)に共通Yラベル
add_group_label(fig, [axs[0,0], axs[1,0]], "Left Pair Y", direction='y', offset=0.07)

# 2. 下段の左2つに共通Xラベル(Gapにラベル)
add_group_label(fig, [axs[1,0], axs[1,1]], "Bottom Left X (Gap=False)", direction='x', offset=0.08, gap=False, c='blue')

# 3. 下段の右端だけ個別Xラベル
add_group_label(fig, [axs[1,2]], "Bottom Right X", direction='x', offset=0.08)

plt.show()

出力結果(デフォルトと同じGap=False
image.png

このようなケースでは、左二つが単純な中央配置だと視覚的に片寄って見えることがあります。


グラフとグラフの間(ギャップ)にラベルが配置されていると、「どのパネルが共通の軸を持っているのか」が直感的に伝わりやすくなりと筆者は思いました。
それを実現できるのが、gap=Trueオプションです。

Gap=True
fig, axs = plt.subplots(2, 3, figsize=(9, 4), width_ratios=[2, 1, 1], sharex=True, sharey=True)

for ax in axs.flat:
    ax.plot([0, 1], [0, 1])

# 最左列(上下ペア)に共通Yラベル
add_group_label(fig, [axs[0,0], axs[1,0]], "Left Pair Y", direction='y', offset=0.07)

# 下段の左2つに共通Xラベル(Gapにラベル)
add_group_label(fig, [axs[1,0], axs[1,1]], "Bottom Left X (Gap)", direction='x', offset=0.08, gap=True, c='blue')

# 下段の右端だけ個別Xラベル
add_group_label(fig, [axs[1,2]], "Bottom Right X", direction='x', offset=0.08)

plt.show()

出力結果(Gap=True
image.png

グループの「中央のGap」にラベルを置くことで、どのパネルが軸を共有しているのか直感的に捉えることができるようになります。(この機能は、偶数個のAxesに対してのみ使用できる仕様にしています。)

3. 複雑なグリッド構成にも対応

たとえば、5行×3列で上下・左右で幅や高さを変えたプロットがある場合でも、グループとGapをうまく活用するだけで、視覚的にわかりやすいラベルを作ることができます。

fig, axs = plt.subplots(5, 3, figsize=(9, 6), height_ratios=[2, 1, 1, 1, 1], width_ratios=[2, 1, 1], sharex=True, sharey=True)

for ax in axs.flat:
    ax.plot([0, 1], [0, 1])

add_group_label(fig, [axs[0,0], axs[1,0]], "Y label (gap top two)", direction='y', offset=0.07, gap=True, c='blue')
add_group_label(fig, [axs[2,0], axs[3,0], axs[4,0]], "Y label (three-group center)", direction='y', offset=0.07)
add_group_label(fig, [axs[4,0], axs[4,1]], "X label (gap left two)", direction='x', offset=0.08, gap=True, c='blue')
add_group_label(fig, [axs[4,2]], "X label (right only)", direction='x', offset=0.08)

plt.show()

出力結果
image.png

おわりに

本記事では、一部のサブプロットにだけ共通ラベルを付ける方法を紹介しました。

学術論文や技術資料などで複数のプロットを並べる場面では、細かな例外や特別なケースがたくさんあります。そのひとつが、共通ラベルを整った位置に配置する方法ではないでしょうか。

今回は、隙間(Gap)をうまく活かしたラベル配置というアイデアを共有しました。
図の美しさというのは、奥が深いものでして、
近づけているつもりが、実は遠ざかっているような……そんなことも、よくある気がします。

この記事が、図作成における書き手と読み手のあいだにあるGapを、ほんの少しでも埋めるきっかけになれば嬉しいです。

1
1
0

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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?