matplotlibでplotとfill_betweenを1つの凡例アイコンにまとめる方法

## はじめに

matplotlibでグラフを作成するとき、線グラフに信頼区間などの塗り帯（`fill_between`）を追加することはよくあります。
そうした場面で、「線と塗りを1つの凡例アイテムとしてまとめたい」というニーズが出てきました。

実際に使う機会があり、古い記事では少し冗長な方法が紹介されていましたが、色々と試してみたところ**シンプルに実装できる方法**が見つかったので、備忘録としてまとめておきます。
同じことで悩んでいる方の参考になれば幸いです。

実装コードはGoogle Colab [こちら](https://colab.research.google.com/drive/1EUJ5D0C6nXD84VA65HbTw0bs8TH7WK8i?usp=sharing) からも閲覧できます。

## 方法

`fill_between` と `plot` の戻り値をタプルにして `legend()` に渡すだけです。

```python
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 + fill_between の凡例アイコン例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/4ef143d2-6ea3-4941-931a-c75a42335e9a.png)


**ポイント**

* `plot` は線 (`Line2D`)、`fill_between` は帯 (`PolyCollection`)
* タプルにして `legend` に渡すと、**塗りと線が合成された凡例アイコンになる**


## 補足：他の方法について

凡例で「帯＋線」の1つのアイコンを作る方法は他にもあります。  
たとえば以下の Stack Overflow では、同様の凡例アイコンの作成例が紹介されています。

> [Combined legend entry for plot and fill_between – Stack Overflow](https://stackoverflow.com/questions/26217687/combined-legend-entry-for-plot-and-fill-between)

ここでは `fill_between` をそのまま使うのではなく、`fill` を用いて透明な `Patch` を新たに作り、それと `plot` の線をタプルにして `legend()` に渡す方法が提案されています。

この挙動は **matplotlib のバージョンによって微妙に異なる可能性**もあり、筆者自身も完全には把握しきれていませんが、参考までに掲載します。

なお、この記事で紹介した方法は、**matplotlib version 3.10.0** で動作確認を行いました。

---

以下では、凡例用にだけ描画オブジェクトを追加することで実現する方法を紹介します。
（先ほどのコードは、matplotlibのバージョンによって挙動が異なる可能性があるためです。）

<details>
<summary>方法①: <code>mpatches.Patch</code> を使う方法（凡例だけに表示）</summary>

```python
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()
````

</details>

<details>
<summary>方法②: <code>fill</code> で NaN を使ってダミーアーティストを作る方法（グラフには表示されず、凡例だけに反映される）</summary>

```python
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()
```

</details>

## おわりに

凡例のカスタムって、少し手間がかかる印象がありましたが、実は `plot` と `fill_between` の戻り値を**タプルにするだけ**で、とてもシンプルに実現できることがわかりました。

グラフを“ちょっと映えさせたいとき”などに、ぜひ試してみてください。

<br>

少し話が脱線しますが、`line_patch, = ax.plot()` と `line_patch = ax.plot()` の違いって、意外と気づきにくいですよね。私も最近まであまり意識していませんでした。

matplotlibって、そういう“小さな気づき”がたくさんあって、
知れば知るほど、さらに深みが見えてくるんですよね。

気がつけば、matplotlibまわりだけで一週間が終わっていたことも珍しくありません（笑）。

ほんと、matplotlibって、いい肴になります。
でも、多すぎて……正直、一人では昇華しきれません。