サブプロット

6.3. サブプロット#

論文などでよく見かける図の中には、複数のグラフがひとつの図として並べられているものが多くあります。こうした図は、Matplotlib のサブプロット機能を使うことで作成できます。本節は、この機能を使って、一枚の図に複数のグラフを描く方法を紹介します。とはいえ実際の現場では、個別にグラフを作ってパワーポイントに貼り付け、手作業でレイアウト調整するほうが、素早く柔軟に仕上がることも少なくありません。技術力で並べるより、根性で揃えるほうが効率的。

6.3.1. subplot#

これまで見てきたように、グラフを描く際には fig.add_subplot 関数を使って描画領域(座標軸オブジェクト)を取得し、その上にグラフを描いてきました。この関数にはオプションを指定することができ、それによって 1 つの描画デバイス(図)上に複数のサブ描画領域(サブプロット)を配置することが可能になります。これにより、複数のグラフを 1 つの図にまとめて表示することができます。

まずは簡単な例として、1 つの図にグラフを横に 3 つ並べて描画する方法を見てみましょう。この場合、描画デバイス(図)を 1 行 3 列に分割し、それぞれの領域にグラフを配置します。たとえば、最初のグラフを左端の領域に描くには、fig.add_subplot(1, 3, 1) と記述します。ここで、最初の 2 つの数字は「行数」と「列数」、3 つ目の数字は「その中で何番目の領域か」を示しています。以降のサブプロットも同様に、3 番目の引数を変更することで、描画位置を指定することができます。

acorn_data = pd.read_csv('acorns.clean.csv')

shirakashi_weight = acorn_data['weight'][acorn_data['tree'] == 'shirakashi']
arakashi_weight = acorn_data['weight'][acorn_data['tree'] == 'arakashi']
matebashii_weight = acorn_data['weight'][acorn_data['tree'] == 'matebashii']


fig = plt.figure()

# left
ax1 = fig.add_subplot(1, 3, 1)
ax1.hist(shirakashi_weight)

# middle
ax2 = fig.add_subplot(1, 3, 2)
ax2.hist(arakashi_weight)

# right
ax3  = fig.add_subplot(1, 3, 3)
ax3.hist(matebashii_weight)

plt.tight_layout()
plt.show()
../_images/85f4fce1222784fb67bba010daa349784ef51848b2c9783a57e6403fcb53f567.png

このように、分割されたサブプロット領域には自動で 1 から順に番号が割り振られます。どの領域にグラフを描くかはその番号で指定できます。なお、plt.tight_layout を実行すれば、グラフ間の間隔が自動的に調整され、見やすいレイアウトになります。

3 つのグラフを縦に並べる例を見てみましょう。この場合は、描画領域を 3 行 1 列に分割することになるため、fig.add_subplot(3, 1, ...) のように指定します。

fig = plt.figure()

# left
ax1 = fig.add_subplot(3, 1, 1)
ax1.hist(shirakashi_weight)

# middle
ax2 = fig.add_subplot(3, 1, 2)
ax2.hist(arakashi_weight)

# right
ax3  = fig.add_subplot(3, 1, 3)
ax3.hist(matebashii_weight)

plt.tight_layout()
plt.show()
../_images/9fbbda422dcfea7e4c7ee7e75fe092c17d7d47fe6c0f65821260fc09dccc29ce.png

次に、描画デバイスを 2 行 2 列に等分割し、グラフを 4 つ描き入れる例を見ていきましょう。サブプロットの番号は左から右、上から下へ順番に割り当てられます。

acorn_data = pd.read_csv('acorns.clean.csv')

shirakashi_weight = acorn_data['weight'][acorn_data['tree'] == 'shirakashi']
arakashi_weight = acorn_data['weight'][acorn_data['tree'] == 'arakashi']
matebashii_weight = acorn_data['weight'][acorn_data['tree'] == 'matebashii']
kunugi_weight = acorn_data['weight'][acorn_data['tree'] == 'kunugi']


fig = plt.figure()

# top left
ax1 = fig.add_subplot(2, 2, 1)
ax1.hist(shirakashi_weight)

# top right
ax2 = fig.add_subplot(2, 2, 2)
ax2.hist(arakashi_weight)

# bottom left
ax3 = fig.add_subplot(2, 2, 3)
ax3.hist(matebashii_weight)

# bottom right
ax4 = fig.add_subplot(2, 2, 4)
ax4.hist(kunugi_weight)


plt.tight_layout()
plt.show()
../_images/f7691258fca4c36d340f8b06b44cf3c8fd15c1ddb9d3019d98b72ff3e25a1c0f.png

さらに応用的な例として、描画デバイスをまず 1 行 2 列に分割し、左側に散布図、右側には 3 つのヒストグラムを縦に並べるレイアウトを考えてみましょう。

このとき、左側のサブ描画領域は fig.add_subplot(1, 2, 1) で取得します。一方、右側の領域をさらに 3 行 1 列に分割する場合、描画デバイス全体を 3 行 2 列とみなして、それぞれ右側の位置を指定することで実現できます。

../_images/matplotlib_subplot.png

Fig. 6.1 Maptplotlib で描画デバイスを分割する例。#

では、実際のコードを見てみましょう。

acorn_data = pd.read_csv('acorns.clean.csv')
shirakashi = acorn_data[acorn_data['tree'] == 'shirakashi']

fig = plt.figure()

ax1 = fig.add_subplot(1, 2, 1)
ax1.scatter(shirakashi['height'], shirakashi['weight'])
ax1.set_xlabel('height')
ax1.set_ylabel('weight')

ax2 = fig.add_subplot(3, 2, 2)
ax2.hist(shirakashi['height'])
ax2.set_title('height')

ax3 = fig.add_subplot(3, 2, 4)
ax3.hist(shirakashi['weight'])
ax3.set_title('weight')

ax4 = fig.add_subplot(3, 2, 6)
ax4.hist(shirakashi['diameter'])
ax4.set_title('diameter')

plt.tight_layout()
plt.show()
../_images/e218f59cd48f1025d829b61186778696a2ff36b9bb801db7d45f0f49dfb269a7.png

このように、fig.add_subplot に与える引数を適切に調整することで、さまざまな形式で描画領域を分割し、複数のグラフを一つの図にまとめることができます。

6.3.2. gridspec#

fig.add_gridspec 関数を使うと、描画デバイスの分割をより直感的に操作できます。考え方として、まず描画デバイスを指定された行数と列数で等分割し、その後、各グリッド領域をインデックスで指定してグラフを描くというものです。このとき、隣接する複数のグリッド領域を結合して 1 つの広い描画領域として使用することもできます。グリッド領域の指定には、リストや二次元配列のようにインデックスやスライスを使うことができます。

例えば次のように描画デバイスを格子状に分割し、いくつかのグリッド領域を組み合わせて座標軸を作成することで、複雑なレイアウトのグラフを描くことができます。

../_images/matplotlib_gridspec.png

Fig. 6.2 Matplotlib の GridSpec モジュールで描画デバイス分割して、複数のグラフを描く例。#

実際のコードは次のようになります。

acorn_data = pd.read_csv('acorns.clean.csv')
shirakashi = acorn_data[acorn_data['tree'] == 'shirakashi']
arakashi = acorn_data[acorn_data['tree'] == 'arakashi']
matebashii = acorn_data[acorn_data['tree'] == 'matebashii']
kunugi = acorn_data[acorn_data['tree'] == 'kunugi']


fig = plt.figure()
gs = fig.add_gridspec(3, 4)

ax1 = fig.add_subplot(gs[0, 0:3])
ax1.hist(shirakashi['weight'])
ax1.set_title('shirakashi weight')

ax2 = fig.add_subplot(gs[0, 3])
ax2.hist(shirakashi['height'])
ax2.set_title('shirakashi height')

ax3 = fig.add_subplot(gs[1:3, 0])
ax3.scatter(arakashi['height'], arakashi['weight'])
ax3.set_label('height')
ax3.set_ylabel('weight')
ax3.set_title('arakashi')

ax4 = fig.add_subplot(gs[1, 1:3])
ax4.hist(matebashii['weight'])
ax4.set_title('matebashii weight')

ax5 = fig.add_subplot(gs[2, 1:3])
ax5.hist(kunugi['weight'])
ax5.set_title('kunugi weight')

ax6 = fig.add_subplot(gs[1:3, 3])
ax6.scatter(kunugi['height'], kunugi['weight'])
ax6.set_label('height')
ax6.set_ylabel('weight')
ax6.set_title('kunugi')

plt.tight_layout()
plt.show()
../_images/3c8e8ff9e60720afbcfa105c7bf23364dedf25e66b98f7f26007402a2fcf92c9.png

サブプロット機能を使えば、複数のグラフを一つの図に描き入れることができます。しかし、グラフのタイトルや座標軸のラベル位置、フォントサイズ、余白などを調整するのは難しいです。プログラミング言語で理想を追うのは構わないが、レポート本文を書き終えてからにしましょう。ちなみに筆者はというと、R で作図してパワーポイントで仕上げています。R の方がきれいに仕上がります。見栄え重視なもので。