【Matplotlib】TwoSlopeNormを用いた任意の中心値やスケールに合わせたカラーバーの配置方法

## はじめに

[Matplotlib](https://matplotlib.org/)のカラーマップを使用する際、値のスケールを任意の中心から小さい側と大きい側で異なるスケールを適用する場合、[TwoSlopeNorm](https://matplotlib.org/stable/api/_as_gen/matplotlib.colors.TwoSlopeNorm.html)がよく利用されます。しかし、この方法で得られるカラーマップは一般的に問題にはなりませんが、得られるカラーバーに関して、スケールに合わせて表示したい、またはその任意の中心値をカラーバーの中央に配置させるなど、細かい部分の実装がやや複雑です。そこで、本記事では、[先行資料](https://github.com/matplotlib/matplotlib/issues/22197)を参考にして2種類の方法（カラーバーの中央に配置する方法、カラーバーをスケールに合わせて配置する方法）について紹介しようと思います。

:::note warn
Matplotlibのバージョン3.5.0以降とそれ以前のバージョンにおいて、TwoSlopeNormの振る舞いが異なるため、カラーバーの調整方法も変わってきます。ここでは、それぞれのバージョンについての調整方法を説明します。なお、本記事ではバージョン間の比較のため、Matplotlibのバージョン3.7.1と3.4.2を使用しています。
:::

この記事のコードは、Google Colab[こちら](https://colab.research.google.com/drive/1Q0gPjO2uyPHckAaaktnZyJ3IJAVpCNzx?usp=sharing)からも参照できます。

## 方法 (matplotlib バージョン3.5.0以降)
matplotlibのバージョン3.7.1での実行結果になります。
### 任意の中央値をカラーバーの中央に配置

デフォルトの場合は、任意の中心値が中央に配置されます。

```python
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors

# サンプルデータ
data = np.arange(-20, 80).reshape(10, 10)

# カラーマップの設定
cmap = 'coolwarm'
norm = mcolors.TwoSlopeNorm(vmin=-20, vcenter=0, vmax=80)

# プロット
plt.imshow(data, cmap=cmap, norm=norm)
plt.colorbar()

plt.show()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/69c9dffb-63bf-bed6-3d45-57258108774b.png)


### カラーバーをスケールに合わせて配置

カラーバーをスケールに合わせて配置するためには、`set_yscale('linear')`を使用して、カラーバーのスケールを線形に設定します。

```python
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors

# サンプルデータ
data = np.arange(-20, 80).reshape(10, 10)

# カラーマップの設定
cmap = 'coolwarm'
norm = mcolors.TwoSlopeNorm(vmin=-20, vcenter=0, vmax=80)

# プロット
plt.imshow(data, cmap=cmap, norm=norm)
cbar = plt.colorbar()
cbar.ax.set_yscale('linear')  # カラーバを目盛りのスケールに合わせて表示

plt.show()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/1d441180-e0c9-71fc-cfd7-d3db36e08e0a.png)



## 方法 (matplotlib バージョン3.5.0より前)
matplotlibのバージョン3.4.2での実行結果になります。

### カラーバーをスケールに合わせて配置
先ほどのバージョン3.7.1とは対照的に、バージョン3.4.2ではデフォルトがスケールに合わせて配置するようになっています。


```python
# サンプルデータ
data = np.arange(-20, 80).reshape(10, 10)

# カラーマップの設定
cmap = 'coolwarm'
norm = mcolors.TwoSlopeNorm(vmin=-20, vcenter=0, vmax=80)

# プロット
plt.imshow(data, cmap=cmap, norm=norm)
plt.colorbar()

plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/2e8867d3-adec-77e9-2968-6ff9d05076f0.png)

### 任意の中央値をカラーバー中央に配置

`plt.colorbar(spacing='proportional')`を使うことで、中央に配置させることができます。

```python
# サンプルデータ
data = np.arange(-20, 80).reshape(10, 10)

# カラーマップの設定
cmap = 'coolwarm'
norm = mcolors.TwoSlopeNorm(vmin=-20, vcenter=0, vmax=80)

# プロット
plt.imshow(data, cmap=cmap, norm=norm)
plt.colorbar(spacing='proportional')

plt.show()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/83f36240-7472-1da9-5c1a-0837152d290b.png)

## 両者のバージョンの違いについての考察

Matplotlibのバージョン3.5.0以前(検証したのは3.4.2)では、色々と試してみるとカラーバーの色(256段階)を予め割り振ることで実装しているようです(左図参照)。一方、バージョン3.5.0以降(検証したのは3.7.1)では、右図のようにカラーバーが後に挿入されて、そこに目盛りを対応させているような結果になっていると考えられます。この真偽性についてはよく把握できていないので、精通している方がおられましたらご教示いただけると幸いです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/39dc53d6-852b-273b-5492-e07ddcdbf1ca.png)

## まとめ
本記事では、Matplotlibのカラーバーの調整方法について、バージョン3.5.0以降とそれ以前の2つのバージョンに分けて紹介しました。本記事が同じような問題を抱えた方の一助になれば幸いです。

## 参考資料

https://matplotlib.org/stable/api/_as_gen/matplotlib.colors.TwoSlopeNorm.html

https://github.com/matplotlib/matplotlib/issues/22197

https://stackoverflow.com/questions/74595690/twoslopenorm-in-matplotlib-not-working-as-expected