はじめに
matplotlibでグラフを作成するとき、線グラフに信頼区間などの塗り帯(fill_between
)を追加することはよくあります。
そうした場面で、「線と塗りを1つの凡例アイテムとしてまとめたい」というニーズが出てきました。
実際に使う機会があり、古い記事では少し冗長な方法が紹介されていましたが、色々と試してみたところシンプルに実装できる方法が見つかったので、備忘録としてまとめておきます。
同じことで悩んでいる方の参考になれば幸いです。
実装コードはGoogle Colab こちら からも閲覧できます。
方法
fill_between
と plot
の戻り値をタプルにして legend()
に渡すだけです。
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 10, 100)
y = np.sin(x)
yerr = 0.2 * np.ones_like(y)
fig, ax = plt.subplots()
# 実際に描画:帯と線
line_patch, = ax.plot(x, y, color='tab:blue', lw=2)
fill_patch = ax.fill_between(x, y - yerr, y + yerr, color='tab:blue', alpha=0.3)
# タプルでまとめて1アイコンに
ax.legend([(fill_patch, line_patch)], ['sin(x) ± 0.2'])
plt.title("line + fill_between in one legend item")
plt.show()
ポイント
-
plot
は線 (Line2D
)、fill_between
は帯 (PolyCollection
) - タプルにして
legend
に渡すと、塗りと線が合成された凡例アイコンになる
補足:他の方法について
凡例で「帯+線」の1つのアイコンを作る方法は他にもあります。
たとえば以下の Stack Overflow では、同様の凡例アイコンの作成例が紹介されています。
Combined legend entry for plot and fill_between – Stack Overflow
ここでは fill_between
をそのまま使うのではなく、fill
を用いて透明な Patch
を新たに作り、それと plot
の線をタプルにして legend()
に渡す方法が提案されています。
この挙動は matplotlib のバージョンによって微妙に異なる可能性もあり、筆者自身も完全には把握しきれていませんが、参考までに掲載します。
なお、この記事で紹介した方法は、matplotlib version 3.10.0 で動作確認を行いました。
以下では、凡例用にだけ描画オブジェクトを追加することで実現する方法を紹介します。
(先ほどのコードは、matplotlibのバージョンによって挙動が異なる可能性があるためです。)
方法①: mpatches.Patch
を使う方法(凡例だけに表示)
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
x = np.linspace(0, 10, 100)
y = np.sin(x)
yerr = 0.2 * np.ones_like(y)
fig, ax = plt.subplots()
# 実際の描画
line, = ax.plot(x, y, color='tab:blue', lw=2)
ax.fill_between(x, y - yerr, y + yerr, color='tab:blue', alpha=0.3)
# 凡例用のダミーPatch
fill_patch = mpatches.Patch(color='tab:blue', alpha=0.3)
# タプルで1つの凡例アイコンに
ax.legend([(fill_patch, line)], ['sin(x) ± 0.2'])
plt.title("Using mpatches.Patch for legend")
plt.show()
方法②: fill
で NaN を使ってダミーアーティストを作る方法(グラフには表示されず、凡例だけに反映される)
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 10, 100)
y = np.sin(x)
yerr = 0.2 * np.ones_like(y)
fig, ax = plt.subplots()
# 実際の描画
line, = ax.plot(x, y, color='tab:blue', lw=2)
ax.fill_between(x, y - yerr, y + yerr, color='tab:blue', alpha=0.3)
# 凡例用にfillでダミーを作成
fill_patch, = ax.fill([np.nan], [np.nan], color='tab:blue', alpha=0.3)
# タプルでまとめて凡例に
ax.legend([(fill_patch, line)], ['sin(x) ± 0.2'])
plt.title("Using ax.fill(NaN) for legend")
plt.show()
おわりに
凡例のカスタムって、少し手間がかかる印象がありましたが、実は plot
と fill_between
の戻り値をタプルにするだけで、とてもシンプルに実現できることがわかりました。
グラフを“ちょっと映えさせたいとき”などに、ぜひ試してみてください。
少し話が脱線しますが、line_patch, = ax.plot()
と line_patch = ax.plot()
の違いって、意外と気づきにくいですよね。私も最近まであまり意識していませんでした。
matplotlibって、そういう“小さな気づき”がたくさんあって、
知れば知るほど、さらに深みが見えてくるんですよね。
気がつけば、matplotlibまわりだけで一週間が終わっていたことも珍しくありません(笑)。
ほんと、matplotlibって、いい肴になります。
でも、多すぎて……正直、一人では昇華しきれません。