温度を表す理想のカラーマップを求めて（matplotlib, GrADS）

# はじめに

温度を視覚的に表現するためのカラーマップは、**低温側に寒色、高温側に暖色を配したものが最も一般的かつ直感的** です。その一方で、多くのソフトが標準で準備している **rainbow カラーマップは、視覚的な理想から遠い存在** となっています。

matplotlib は様々なカラーマップを提供しており、その一覧は [Choosing Colormaps in Matplotlib](https://matplotlib.org/stable/tutorials/colors/colormaps.html) にまとめられています。ここにあるカラーマップの中でも、特に気温や海面水温といった温度を表現する際によく用いられるものとして、 `RdYlBu_r` 、 `jet` 、 `turbo` などがあります。同様に GrADS でも、デフォルトのカラーマップが温度を表現する際に広く使われています。ただし、これらのカラーマップには後述するようないくつかの問題点が存在します。

さらに、matplotlibが提供する中には `viridis` のように、色覚多様性に配慮し視認性に優れたカラーマップも存在します。しかし、これらは温度が持つ直感的な色のイメージとカラーマップの色の対応関係が不明確で、温度を表現するカラーマップとしては最適とは言えません。

本記事では、**人々が視覚的に捉えやすく、かつ出来る限り理想的な温度の表現を可能にするカラーマップの作成を目指し、それを matplotlib と GrADS に実装する方法を紹介します。**

# 理想のカラーマップとは
既存のカラーマップの問題点を指摘する前に、理想のカラーマップとはどのようなものかを明確にしておきます。 [matplotlib の公式ドキュメント](https://matplotlib.org/stable/tutorials/colors/colormaps.html)には、どのようなカラーマップを採用するべきかについての解説があり、そこでは以下のように述べられています。

>For many applications, a perceptually uniform colormap is the best choice; i.e. a colormap in which equal steps in data are perceived as equal steps in the color space. Researchers have found that the human brain perceives changes in the lightness parameter as changes in the data much better than, for example, changes in hue. Therefore, colormaps which have monotonically increasing lightness through the colormap will be better interpreted by the viewer.

すなわち、 **明度が単調に変化し、なおかつその勾配が定数となるようなカラーマップ** は、知覚の上で色が連続的に変化するように見えるため、望ましいと考えられます。その最たる例が、 matplotlib のデフォルトのカラーマップである `viridis` です。下図は、その明度特性を示したものです。ここで明度は、 [CIELAB 色空間](https://ja.wikipedia.org/wiki/Lab%E8%89%B2%E7%A9%BA%E9%96%93) での明度を示しています。

![viridis.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2434596/90139f08-f36f-fb4e-e6c8-8c8a0e0786a8.png)

図を見て明らかなように、明度が線形に変化し、知覚的に均一な色変化を示しています。さらに、このカラーマップは色覚多様性に配慮して設計されており、後述するP型やD型といった色覚の下でも色の変化を明確に認識することが可能となっています。matplotlib のデフォルトになっているだけあり、非常に優秀なカラーマップです。

一方で、前述のように、温度は直感的な色のイメージが存在する物理量です。そのため、 `viridis` のようなカラーマップが最適解にはなり得ません。 [matplotlib の公式ドキュメント](https://matplotlib.org/stable/tutorials/colors/colormaps.html)でも、最適なカラーマップを選択するために考慮すべきこととして、

>If there is an intuitive color scheme for the parameter you are plotting

という項目を挙げています。

以下では、温度に用いられることが多い既存のカラーマップにどのような課題点があるかを明らかにし、それらを踏まえた上で、ここで述べたような理想に近づけた新しいカラーマップを紹介します。

# 既存のカラーマップの明度と問題点
温度に用いられることが多い既存のカラーマップとして、前述の　`RdYlBu_r` 、 `jet` 、 `turbo` 、そしてGrADSのデフォルトカラーマップがあります。ここでは、前節で述べたような観点から、それぞれの明度特性を示しつつ問題点を紹介します。

## RdYlBu_r

![RdYlBu_r.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2434596/0c769fbc-ea6e-fa07-a15c-c8da7485f6cc.png)

名前の通り、赤→黄→青と遷移するカラーマップを反転させたものです。明度の変化はシンプルで、視認性も悪くありませんが、温度を表現する際には次のような問題点があります。 

`RdYlBu_r` は **diverging なカラーマップ**で、原則として0を中心に正負の値を持つデータ（e.g., 風ベクトルの大きさ、偏差、差分）を表示するために用意されています。そのため、カラーマップ中央の色はかなり薄い黄色で、図を描画した際に目立ちません。 diverging な量は0付近の値にあまり意味がないことも多いため、このような色配置で問題ないのですが、**温度は diverging な量ではありません**。絶対温度は正の値のみを取り、摂氏の場合でも負の値が正の値と逆の意味を持つことはなく、0℃付近が目立たないことも望ましくありません。

## jet

![jet.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2434596/6c1d95b7-fe6b-1ba1-f940-2e23f284ffb9.png)

RGB色空間のスペクトルに沿った遷移をし、原色に近い色を含む鮮やかな rainbow カラーマップです。この種のカラーマップの課題は様々なところで指摘されており、例えば [Stauffer _et al_. (2015)](https://doi.org/10.1175/BAMS-D-13-00155.1) では、次のように述べられています。

> Vivid colors along the spectrum of the RGB color space strongly differ in their luminance, which can lead to artificial dark or bright bands that can obscure the information shown.

> As shown in the desaturated version of the RGB rainbow, even the three primary colors, red, green, and blue, vary enormously in luminance: red has a luminance value of about 50, green is about 86, and blue is about 30 (100 would be white). This creates unwanted gradients throughout the whole RGB rainbow map. Furthermore, the RGB color map shows several artificial narrow bands most easily seen around yellow/cyan/magenta. What makes the RGB color spaces even worse is that the different colors of the spectrum are not uniformly distributed (the green sector looks wider than the red one), which creates an additional distortion.

また、[Bergman _et al._ (1995)](https://doi.org/10.1109/VISUAL.1995.480803) では、次のように指摘されています。

>The most common default colormap, the “rainbow” colormap, is a hue-based scale from blue, through a rainbow of colors, to red. When this scale is mapped onto scalar data, the user is conceptually mapping a linear scale in hue onto a scalar variable. Perceptually, however, this scale does not appear linear. Equal steps in the scale do not correspond to equal steps in color, but look instead like fuzzy bands of color varying in hue, brightness and saturation. When mapped onto scalar data, this colormap readily gives the user the erroneous impression that the data are organized into discrete regions, each represented by one of the rainbow colors This can lead the user to infer structure which is not present in the data and to miss details that lie completely within a single color region

すなわち、**明度や輝度の不連続変化が図に不自然な暗帯域や明帯域を生み出すこと** や、 **スペクトル内の色が均一に分布していないこと** などの問題があり、こうした点がユーザーに誤った知覚や解釈を与えることにつながります。例えば、水色と緑の間の明度の差が小さいため、色の違いを識別することが困難になります。これは、色覚の多様性に配慮する観点から見ても、大きな問題となり得ます。

このような特性により、表示されたデータに対する人間の認知が歪められ、**結果として読者に誤解を与えるような例** も知られています。その一例として、 [Climate Lab Book](https://www.climate-lab-book.ac.uk/2016/why-rainbow-colour-scales-can-be-misleading/) の記事で紹介されている内容を挙げます。

![davis.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2434596/8a13bdf9-36ae-c870-cdb7-91175b5739fd.gif)

上図は、[Hudson _et al._ (2006)](https://doi.org/10.5194/acp-6-5183-2006) の Fig. 1 を NOAA の Sean Davis が再解析して作成したもので、前述の記事から引用したものです。3枚のパネルのうち左2枚は、全球のオゾン全量（TCO：地表から大気上限までの単位面積の気柱に含まれる全てのオゾン）を示したもので、オゾン全量の勾配が大きくなる「前線」が3本（赤、黄、青の各実線で示されている）確認できます。左の rainbow カラーマップを使用した図では、青実線で示される前線が最も明瞭に現れている一方で、中央の sequential なカラーマップを使用した図では、青実線よりも黄実線で示される前線がより目立ちます。右はTCOの勾配を示した図で、黄実線で示される領域の勾配が最も大きいことがわかります。すなわち、 **rainbow カラーマップは、物理量が本来持っている特徴とは異なる点を誤って強調してしまう可能性があることが示唆されます。** これは、**赤と緑（補色）の間の色勾配が強く、実際に物理量の勾配が大きい領域よりも色の境界が目立ってしまう** ためです。

## turbo

![turbo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2434596/824ef105-66e5-cf00-2067-85253226c4de.png)

`jet` と同様の rainbow カラーマップで、前述の `jet` の問題点がある程度改善されています。しかし、依然として緑周辺の色相の差は他の色相と比較して不鮮明で、細かな違いを識別するのが困難です。さらに、カラーマップ両端の色の明度が低いため、地図などの上に重ねて表示する際に、極端な値を持つ領域が濃い色に塗りつぶされてしまうという問題もあります。

## GrADSのデフォルト

![GrADS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2434596/324987b7-ff8b-ec9b-baef-6075a0fd59ff.png)

`jet` や `turbo` と同様の rainbow カラーマップです。これら2つと比べて明度がかなり複雑に変化しており、このカラーマップを採用した図をモノクロ印刷すると、値の大きさが分かりにくくなる可能性があります。また、隣接する色相間の差異は `jet` よりも明瞭に見えるものの、P型（1型）色覚（赤に敏感な視細胞であるL錐体の機能に異常がある）の場合には、紫〜青、黄緑〜黄にかけての範囲がほぼ同じ色として認識されてしまいます。

なお、RGB値は[公式の User's Guide](http://cola.gmu.edu/grads/gadoc/colorcontrol.html) を参考にしました。

# 新しいカラーマップ

以上の問題点を踏まえて、新たに作成する温度用カラーマップの目標を以下のように定めました。

- 温度に対する直感的な色のイメージを反映し、低温側が寒色、高温側が暖色とする
- diverging でない量を表すカラーマップであることを意識して、カラーマップ中央の明度を上げすぎないようにする
- 地図が濃い色で塗りつぶされてしまうことがないように、両端の明度は下げすぎないようにする
- 明度の変化は単調かつシンプルなものにする
- 緑周辺の色相は判別しづらいため出来る限り避け、赤と距離を取る
- 目が疲れないように、原色のような極端に鮮やかな色を避ける
- 色覚多様性に配慮する

このようにして作成したカラーマップが、以下の **cmthermal** です。

![cmthermal.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2434596/fc29096e-fa40-1d0a-b6aa-543a6d1c5b14.png)

P型（1型）色覚、D型（2型）色覚（緑に敏感な視細胞であるM錐体の機能に異常がある)での見え方をシミュレートした結果が、下図です。

![cmthermal_protanope.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2434596/ff2742c2-10c9-1499-569c-f179e1fd7450.png)
![cmthermal_deuteranope.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2434596/596a95ce-6261-17d0-f6a1-4b323f61d7b2.png)

上の図がP型、下の図がD型を示しています。一応、区別はつけられるかなと思います。

今回は温度のためのカラーマップを作成することを目的としましたが、温度以外の場合でも、 cmthermal は先に上げたような **rainbow カラーマップを使用する場面での代替になり得る** と考えています。

## Python （matplotlib） での実装

実装方法は、[matplotlibによるグラフ描画時のColormapのカスタマイズ](https://qiita.com/kenmatsu4/items/fe8a2f1c34c8d5676df8)を参考にしました。

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

def generate_cmap(colors, cmap_name = 'custom_cmap'):
    values = range(len(colors))
    vmax = np.ceil(np.max(values))
    color_list = []
    for vi, ci in zip(values, colors):
        color_list.append( ( vi/ vmax, ci) )

    return mcolors.LinearSegmentedColormap.from_list(cmap_name, color_list, 256)

cmthermal = generate_cmap(['#1c3f75', '#068fb9','#f1e235', '#d64e8b', '#730e22'], 'cmthermal')
```

## GrADSでの実装
[color.gs](http://kodama.fubuki.info/wiki/wiki.cgi/GrADS/script/color.gs?lang=jp) というスクリプトが必要です。

```grads
color min max -kind (28,63,117)->(6,143,185)->(241,226,53)->(214,78,139)->(115,14,34)
```

ここで、 `min` ， `max` はそれぞれカラーマップの最小値、最大値です。他のオプションは、 [color.gs](http://kodama.fubuki.info/wiki/wiki.cgi/GrADS/script/color.gs?lang=jp) を参考にして下さい。

# 描画サンプル

最後に、 `cmthermal` を使用した例と `jet` を使用した例との比較を示します。データには [JRA-55](https://jra.kishou.go.jp/JRA-55/index_ja.html) ([Kobayashi _et al._, 2015](https://doi.org/10.2151/jmsj.2015-001)) および [NOAA OI SST V2 High Resolution](https://psl.noaa.gov/data/gridded/data.noaa.oisst.v2.highres.html) を使用しました。

## SAT（18段階）

**cmthermal**
![test_cmthermal.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2434596/f8e6f1e1-1cab-ec7c-0ea6-866c51bd61d8.png)

**jet**
![test_jet.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2434596/2a46b20b-549d-22c9-2344-118a68132202.png)

## SST（10段階）

**cmthermal**
![test_sst_cmthermal.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2434596/280bc1b0-e8f4-bdb1-b483-e605303f6905.png)

**jet**
![test_sst_jet.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2434596/170dfa25-a47b-4ae4-ccdd-d0bdab9bb7f8.png)


# 参考文献
https://doi.org/10.1175/BAMS-D-13-00155.1

https://doi.org/10.1109/VISUAL.1995.480803

https://www.climate-lab-book.ac.uk/2016/why-rainbow-colour-scales-can-be-misleading/

https://matplotlib.org/stable/tutorials/colors/colormaps.html

https://qiita.com/kenmatsu4/items/fe8a2f1c34c8d5676df8