「複数パネルの複雑な図を作成するためのMatplotlibサブフィギュアのガイド」

「Matplotlibサブフィギュアのガイド:複数パネルで複雑な図を作成する方法」

サブフィギュア — 美しいマルチパネル図のための強力なツール

モチベーション

複数のサイズや注釈を持つ複雑な(科学的な)図は、matplotlib/seabornのエコシステムで作成するためのさまざまな方法があります。例えば、gridspecを使用する方法などです。ただし、これは非常に挑戦的なものとなり、特に< a href=”/?s=jointplot”>jointplotやpairgridのように、軸を入力パラメータとして提供できないseabornのマルチ軸プロットをフィギュアに統合したい場合はさらに困難になります。しかし、subplotだけでなくmatplotlibで図を組み立てる別の方法があります。サブフィギュアです。このようなマルチパネル図を作成するための強力なフレームワークです:

この記事の目標は、この図を作成する方法を示すことです。

この記事では、サブフィギュアとその機能について紹介します。サブフィギュアをサブプロットやグリッドスペックと組み合わせて、この図を再現します。この記事については、matplotlibのsubplotsgridspecの基本的な理解が必要ですが(そうでない場合は、リンクされたチュートリアルを参照できます)。

Matplotlibのサブフィギュア

まず、matplotlibとseabornをインポートし、いくつかのサンプルデータをロードします。これはプロットに内容を埋めるために使用します:

import matplotlib.pyplot as pltimport seaborn as snsdata = sns.load_dataset('mpg')

matplotlibでは、サブフィギュアを作成するためにまずフィギュアを作成する必要があります:

fig = plt.figure(figsize=(10, 7))

この時点から、subplotと同様にサブフィギュアを定義できます。行(2)と列(1)を指定してサブフィギュアのグリッドを作成することもできます。さらに、フィギュアの背景を色付けして強調表示することもできます:

(topfig, bottomfig) = fig.subfigures(2, 1)topfig.set_facecolor('#cbe4c6ff')topfig.suptitle('トップ')bottomfig.set_facecolor('#c6c8e4ff')bottomfig.suptitle('ボトム')

プロットのないフィギュアだけでは表示されないため、各サブフィギュアに対してサブプロットを定義する必要があります。ここで、サブフィギュアのもう1つの素晴らしい機能が見えてきます。それぞれのサブフィギュアに対して、サブプロットのレイアウトを異なるものにすることができるということです:

top_axs = topfig.subplots(2, 4)bottom_axs = bottomfig.subplots(3, 7)plt.show()

これで2つの別々のフィギュアを用意し、それぞれを異なる設定で1つの最終フィギュアにまとめることができます。もちろん、サブフィギュアのサイズ比率も調整することができます:

figure = plt.figure(figsize=(10, 7))figs = figure.subfigures(2, 2, height_ratios=(2,1), width_ratios=(2,1))figs = figs.flatten()for i, fig in enumerate(figs): fig.suptitle(f'Subfigure {i}') axs = fig.subplots(2, 2)plt.show()

ただし、サブフィギュアには欠点もあります。重なり合うラベルやフィギュアの外側にある要素を排除するために、`plt.tight_layout()`を使用するとスマートに図全体にきれいに収まります。ただし、サブフィギュアではサポートされていないため、それを使用しようとするとどうなるかを以下で確認できます:

figure = plt.figure(figsize=(10, 7))figs = figure.subfigures(2, 2, height_ratios=(2,1), width_ratios=(2,1))figs = figs.flatten()for i, fig in enumerate(figs): fig.suptitle(f'サブフィギュア {i}') axs = fig.subplots(2, 2)plt.tight_layout()plt.show()

私たちが意図したものではありません… プロット間のスペースを追加し、重複を除去するには、サブプロットと境界の間にスペースを挿入することができる `subplots_adjust` 関数を使用する必要があります:

fig = plt.figure(figsize=(10, 7))(topfig, bottomfig) = fig.subfigures(2, 1)topfig.set_facecolor('#cbe4c6ff')topfig.suptitle('トップ')bottomfig.set_facecolor('#c6c8e4ff')bottomfig.suptitle('ボトム')top_axs = topfig.subplots(2, 4)bottom_axs = bottomfig.subplots(3, 7)# プロットとサイドのスペースを追加してスペースを増やすtopfig.subplots_adjust(left=.1, right=.9, wspace=.5, hspace=.5)# サブプロットを下に引き伸ばすbottomfig.subplots_adjust(wspace=.5, hspace=.8, top=.7, bottom=.3)plt.show()

サブフィギュアのもう一つの素晴らしい側面は、ネストすることができるということです。つまり、すべてのサブフィギュアをさらにいくつかのサブフィギュアに分割することができます:

fig = plt.figure(figsize=(10, 7))(topfig, bottomfig) = fig.subfigures(2, 1)topfig.set_facecolor('#cbe4c6ff')topfig.suptitle('トップ')top_axs = topfig.subplots(2, 4)(bottomleft, bottomright) = bottomfig.subfigures(1, 2, width_ratios=(1,2))bottomleft.set_facecolor('#c6c8e4ff')bottomleft.suptitle('ボトム 左')bottom_axs = bottomleft.subplots(2, 2)bottomright.set_facecolor('#aac8e4ff')bottomright.suptitle('ボトム 右')bottom_axs = bottomright.subplots(3, 3)# サブプロット間のスペースtopfig.subplots_adjust(left=.1, right=.9, wspace=.4, hspace=.4)bottomleft.subplots_adjust(left=.2, right=.9, wspace=.5, hspace=.4)bottomright.subplots_adjust(left=.1, right=.9, wspace=.4, hspace=.4)plt.show()

この図に ジョイントプロット を挿入しましょう。残念ながら、これは簡単ではありません。なぜなら、seaborn の関数は図オブジェクトを入力として受け取ることができないからです。しかし、このプロットの ソースコード を見ると、グリッドスペックを介して定義された、x 軸と y 軸を共有する3つのサブプロットからなることがわかります。

つまり、このプロットを簡単にサブフィギュア内にプロットすることができます:

fig = plt.figure(figsize=(10, 7))(topfig, bottomfig) = fig.subfigures(2, 1)topfig.set_facecolor('#cbe4c6ff')topfig.suptitle('トップ')top_axs = topfig.subplots(2, 4)# ボトム 左のサブフィギュアにジョイントプロットを使用しています(bottomleft, bottomright) = bottomfig.subfigures(1, 2, width_ratios=(1,2))# このパラメータはメインプロットとマージンプロットのサイズ比を定義しますratio=2# サブプロットが配置されるグリッドスペックを定義gs = plt.GridSpec(ratio + 1, ratio + 1)# メインの散布図ax_joint  = bottomleft.add_subplot(gs[1:, :-1])# マージンプロットはメインプロットと軸を共有ax_marg_x = bottomleft.add_subplot(gs[0, :-1], sharex=ax_joint)ax_marg_y = bottomleft.add_subplot(gs[1:, -1], sharey=ax_joint)# マージンプロットの軸ラベルと目盛りラベルを非表示に設定# メインプロットと共有しているため、# マージンから削除するとメインプロットも削除されるplt.setp(ax_marg_x.get_xticklabels(), visible=False)plt.setp(ax_marg_y.get_yticklabels(), visible=False)plt.setp(ax_marg_x.get_xticklabels(minor=True), visible=False)plt.setp(ax_marg_y.get_yticklabels(minor=True), visible=False)# プロットにデータを埋めるsns.scatterplot(data=data, y='horsepower', x='mpg', ax=ax_joint)sns.histplot(data=data, y='horsepower', ax=ax_marg_y)sns.histplot(data=data, x='mpg', ax=ax_marg_x)bottomright.set_facecolor('#aac8e4ff')bottomright.suptitle('ボトム 右')bottom_axs = bottomright.subplots(3, 3)# サブプロット間のスペースtopfig.subplots_adjust(left=.1, right=.9, wspace=.4, hspace=.4)bottomright.subplots_adjust(left=.1, right=.9, wspace=.4, hspace=.4)plt.show()

比率パラメーターで遊んで、プロットがどのように変化するかを見ることができます。

さて、サブフィギュア、サブプロット、およびグリッドを使用して複雑な図を作成するために必要なすべてのツールを持っています。このような図では、各プロットに注釈を付けてキャプションで説明するか、テキストで参照するためにアルファベットで注釈を付けることがしばしば重要です。これは、図が作成された後にAdobe IllustratorやInkscapeなどの他のソフトウェアを使用して通常行われます。しかし、Pythonで直接行うこともできます。これにより、後で追加の手間を省くことができます。

そのために、このような注釈を作成するための関数を定義します:

def letter_annotation(ax, xoffset, yoffset, letter): ax.text(xoffset, yoffset, letter, transform=ax.transAxes,         size=12, weight='bold')

この関数は、axesとx座標、y座標を入力として取ります。これらは相対的なaxes座標に変換されます。これを使用して、以前に作成した図のいくつかのプロットに注釈を付けることができます:

fig = plt.figure(figsize=(10, 7))(topfig, bottomfig) = fig.subfigures(2, 1)topfig.set_facecolor('#cbe4c6ff')topfig.suptitle('上')top_axs = topfig.subplots(2, 4)letter_annotation(top_axs[0][0], -.2, 1.1, 'A')(bottomleft, bottomright) = bottomfig.subfigures(1, 2, width_ratios=(1,2))bottomleft.set_facecolor('#c6c8e4ff')bottomleft.suptitle('左下')bottoml_axs = bottomleft.subplots(2, 2)letter_annotation(bottoml_axs[0][0], -.2, 1.1, 'B')bottomright.set_facecolor('#aac8e4ff')bottomright.suptitle('右下')bottomr_axs = bottomright.subplots(3, 3)letter_annotation(bottomr_axs[0][0], -.2, 1.1, 'C')# サブプロット間の間隔topfig.subplots_adjust(left=.1, right=.9, wspace=.4, hspace=.4)bottomleft.subplots_adjust(left=.2, right=.9, wspace=.5, hspace=.4)bottomright.subplots_adjust(left=.1, right=.9, wspace=.4, hspace=.4)plt.show()

記事の冒頭に表示されるプロットを作成できます。3つのサブフィギュアで構成されています。1つは上のサブフィギュアで、2つは下のサブフィギュアです。左下のサブフィギュアはジョイントプロットに使用され(前述の通り)、右下のサブフィギュアでは異なるサイズの4つのサブプロットを配置するためにグリッドスペックを定義します。

fig = plt.figure(figsize=(10, 7))# 最初の行と2番目の行のためのサブフィギュアを作成する(row1fig, row2fig) = fig.subfigures(2, 1, height_ratios=[1, 1])# 下の行のサブフィギュアを2つに分割する(fig_row2left, fig_row2right) = row2fig.subfigures(1, 2, wspace=.08, width_ratios = (1, 2))# ###### Row 1のプロットを作成# ###### 最初の行のサブフィギュア用に4つのサブプロットを作成するrow1_axs = row1fig.subplots(1, 4)row1fig.subplots_adjust(wspace=0.5, left=0, right=1, bottom=.16)ax = row1_axs[0]sns.histplot(data=data, x='mpg', ax=ax)ax.set_title('MPG')# プロットにアルファベットを付けるletter_annotation(ax, -.25, 1, 'A')# 図のスタイリングを行い、より良く見えるようにし、統一された外観にするための設定sns.despine(offset=5, trim=False, ax=ax)ax = row1_axs[1]sns.histplot(data=data, x='displacement', ax=ax)ax.set_title('Displacement')letter_annotation(ax, -.25, 1, 'B')sns.despine(offset=5, trim=False, ax=ax)ax = row1_axs[2]sns.histplot(data=data, x='weight', ax=ax)ax.set_title('Weight')letter_annotation(ax, -.25, 1, 'C')sns.despine(offset=5, trim=False, ax=ax)ax = row1_axs[3]sns.histplot(data=data, x='horsepower', ax=ax)ax.set_title('Horsepower')letter_annotation(ax, -.25, 1, 'D')sns.despine(offset=5, trim=False, ax=ax)# ###### Row 2のプロットを作成# ###### ### Seabornジョイントプロット# ### SeabornのJointGridクラスからのコードを使用# メインプロットとマージンプロットのサイズ比率ratio=2# サブフィギュア内部のためのグリッドスペックの定義gs = plt.GridSpec(ratio + 1, ratio + 1)ax_joint  = fig_row2left.add_subplot(gs[1:, :-1])# マージンプロットとメインプロットの間で軸を共有ax_marg_x = fig_row2left.add_subplot(gs[0, :-1], sharex=ax_joint)ax_marg_y = fig_row2left.add_subplot(gs[1:, -1], sharey=ax_joint)# マージンプロットの軸ラベルと目盛りを削除plt.setp(ax_marg_x.get_xticklabels(), visible=False)plt.setp(ax_marg_y.get_yticklabels(), visible=False)plt.setp(ax_marg_x.get_xticklabels(minor=True), visible=False)plt.setp(ax_marg_y.get_yticklabels(minor=True), visible=False)sns.scatterplot(data=data, y='horsepower', x='mpg', ax=ax_joint)sns.histplot(data=data, y='horsepower', ax=ax_marg_y)sns.histplot(data=data, x='mpg', ax=ax_marg_x)sns.despine(offset=5, trim=False, ax=ax_joint)sns.despine(offset=5, trim=False, ax=ax_marg_y)sns.despine(offset=5, trim=False, ax=ax_marg_x)# 重なりを排除するために右側にスペースを残すためにfig_row2left.subplots_adjust(left=0, right=.8)letter_annotation(ax_marg_x, -.25, 1, 'E')# ### Row 2の右側のプロット# ##gs = plt.GridSpec(2, 3)ax_left   = fig_row2right.add_subplot(gs[:, 0])ax_middle = fig_row2right.add_subplot(gs[:, 1])ax_up     = fig_row2right.add_subplot(gs[0, 2])ax_down   = fig_row2right.add_subplot(gs[1, 2])fig_row2right.subplots_adjust(left=0, right=1, hspace=.5)ax = ax_leftsns.scatterplot(data=data, x='model_year', y='weight', hue='origin', ax=ax)sns.despine(offset=5, trim=False, ax=ax)letter_annotation(ax, -.3, 1, 'F')ax = ax_middlesns.boxplot(data=data, x='origin', y='horsepower', ax=ax)sns.despine(offset=5, trim=False, ax=ax)letter_annotation(ax, -.3, 1, 'G')ax = ax_upsns.kdeplot(data=data, x='mpg', y='acceleration', ax=ax)sns.despine(offset=5, trim=False, ax=ax)letter_annotation(ax, -.3, 1, 'H')ax = ax_downsns.histplot(data=data, x='weight', y='horsepower', ax=ax)sns.despine(offset=5, trim=False, ax=ax)letter_annotation(ax, -.3, 1, 'I')plt.show()

結論

matplotlibでは、サブフィギュアという比較的新しい概念があります。これにより、多くのプロットを持つ大きな図を簡単に組み立てることができます。この記事で紹介されたすべてのことは、gridspecを使用して完全に実現することもできます。ただし、各サブプロットのサイズに対する多くの考慮事項を持つ大きなグリッドが必要です。サブフィギュアはよりプラグアンドプレイであり、同じ結果をより少ない作業で実現することができます。

私にとって、サブフィギュアは科学的な図を作成するための非常に便利なツールであり、あなたにも役立つことを願っています。

この記事のコードはGitHubでも見つけることができます:https://github.com/tdrose/blogpost-subfigures-code

特に記載のない限り、すべての画像は著者によって作成されました。

We will continue to update VoAGI; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more