ヒートマップを描いたら、見せたい構造がぼやけてしまった。散布図に第3の変数を色で重ねたいが、どの配色を選べばよいかわからない。論文をモノクロ印刷したら、グラフの色の違いが全部消えてしまった — matplotlibで可視化をしていると、誰もが一度はこうした「色の悩み」にぶつかります。これらをまとめて解決してくれる仕組みがカラーマップ(colormap、引数名はcmap)です。
カラーマップを正しく選べるようになると、効果は見た目の美しさだけにとどまりません。たとえば次のような場面で、図の「伝わる力」が大きく変わります。
- ヒートマップ・等高線図: 2次元データの山や谷の構造を、色の明暗で正確に伝える
- 散布図への情報追加: x・yに加えて、温度・密度・予測確率といった第3の変数を色で重ねる
- 偏差・相関の可視化: 「プラスかマイナスか」が一目でわかる配色で、相関行列や気温偏差を表示する
- 学術論文・報告書: 色覚多様性やモノクロ印刷でも情報が失われない、アクセシブルな図を作る
本記事の内容
- カラーマップの仕組み(「数値 → 0〜1 → 色」の2段階変換)
- 主要カラーマップの実描画一覧(連続型・発散型・定性型・循環型)と使い分けの指針
- viridisがデフォルトである理由 — 知覚的均等性とjetの問題点
Normalize・LogNorm・BoundaryNormによる「数値→0〜1」の制御- カラーバーの操作と、
ListedColormap/LinearSegmentedColormapによる自作 imshow・scatter・contourfでの指定方法と逆順_r
前提知識
この記事を読む前に、以下の記事を読んでおくと理解が深まります。
カラーマップとは — 「数値を色に変換する対応表」
カラーマップを一言でいうと、0〜1の数値を受け取って、対応する色を返す関数(対応表)です。
身近なイメージで考えてみましょう。天気予報の雨雲レーダーでは、雨の強さが「水色 → 青 → 黄 → 赤」と色分けされています。あれは「降水量という数値」を「色」に置き換える対応表が裏で動いているわけです。標高を茶色の濃淡で塗り分けた地図も、サーモグラフィの温度表示も、すべて同じ仕組みです。matplotlibのカラーマップは、この対応表をプログラムで扱えるようにしたものです。
ここで重要なのは、変換が2段階で行われていることです。次の図がその全体像です。

この図のとおり、元のデータ(例では12〜95のばらばらの値)は、まずステップ2のNormalize(正規化)で0〜1の範囲に変換されます。最小値12が0.00に、最大値95が1.00に対応します。そのうえで、ステップ3のカラーマップが0〜1の値を実際の色(viridisなら紫→緑→黄)に変換します。
つまり、imshowやscatterにcmap='viridis'と渡したとき、内部では
$$ \text{データの値} \xrightarrow{\ \text{Norm(正規化)}\ } [0, 1] \xrightarrow{\ \text{cmap(色変換)}\ } \text{RGBA色} $$
という処理が走っています。この「Normとcmapの分業」を頭に入れておくと、本記事の後半で扱うLogNormやBoundaryNormが「cmapはそのままに、前段の正規化だけを差し替える仕組み」だとすんなり理解できます。
まずは、この仕組みが実際のプロット関数でどう使われるのかを確認しましょう。
cmapの基本的な指定方法 — imshow・scatter・contourf
cmapの指定方法は拍子抜けするほど簡単で、主要なプロット関数の引数cmapに名前の文字列を渡すだけです。代表的な3つの関数で試してみます。
import numpy as np
import matplotlib.pyplot as plt
# 2次元データ(2つの山を持つ関数)と散布データを用意
xx, yy = np.meshgrid(np.linspace(-2, 2, 300), np.linspace(-2, 2, 300))
zz = np.exp(-(xx**2 + yy**2)) + 0.6 * np.exp(-((xx - 1.2)**2 + (yy - 0.8)**2) * 3)
rng = np.random.default_rng(1)
pts = rng.normal(0, 1, (300, 2))
cvals = np.sqrt(pts[:, 0]**2 + pts[:, 1]**2) # 原点からの距離を色にする
fig, axes = plt.subplots(1, 3, figsize=(13, 4.0))
# 1. imshow: 2次元配列を画像として表示
im0 = axes[0].imshow(zz, cmap='magma', origin='lower')
fig.colorbar(im0, ax=axes[0], shrink=0.85)
# 2. scatter: 引数cに「色にしたい数値」を渡す
sc = axes[1].scatter(pts[:, 0], pts[:, 1], c=cvals, cmap='magma', s=22)
fig.colorbar(sc, ax=axes[1], shrink=0.85)
# 3. contourf: 塗りつぶし等高線
cf = axes[2].contourf(xx, yy, zz, levels=12, cmap='magma')
fig.colorbar(cf, ax=axes[2], shrink=0.85)
plt.show()

3つの図はどれもcmap='magma'を渡しただけで、共通の配色(黒→赤紫→オレンジ→淡黄)になっています。注目してほしいのは関数ごとの「数値の渡し方」の違いです。imshowとcontourfは2次元配列zzの値そのものが色に変換されるのに対し、scatterでは引数cに渡した1次元配列(ここでは原点からの距離)が色になります。どの関数でも、数値→色の変換規則は同じcmapが担っている、という構造です。
なお、格子データxx, yy, zzの作り方はnp.meshgridの解説記事で詳しく扱っています。
指定方法はこれだけなので、実際の悩みどころは「どのcmapを選ぶか」に尽きます。そこで次に、matplotlibに用意されているカラーマップを分類ごとに眺めていきましょう。
カラーマップ一覧 — 4つの分類と使い分け
matplotlibには170種類以上のカラーマップが登録されていますが、やみくもに眺めても選べません。公式ドキュメントは、カラーマップをデータの性質に応じた4つのカテゴリに分類しています。この分類を知ることが、cmap選びの最短ルートです。
連続型(Sequential)— 「小さい→大きい」の単調なデータ向け
温度、密度、確率、標高のように「小さい値から大きい値へ」と単調に変化するデータには、明るさが一方向に変化する連続型を使います。

上5つ(viridis・plasma・inferno・magma・cividis)は、後述する「知覚的均等性」を満たすよう設計された現代的なカラーマップで、迷ったらこの中から選べば失敗しません。下6つ(Greys・Blues・GnBuなど)は単色〜2色系の伝統的なカラーマップで、「青が濃いほど値が大きい」のような直感的な読み取りをさせたいときや、複数の量を色相で塗り分けたいときに便利です。
発散型(Diverging)— 「基準値からの正負・偏差」を表すデータ向け
平年からの気温偏差、相関係数(-1〜+1)、損益のように「中央に意味のある基準値があり、そこから両側に広がる」データには発散型を使います。

どれも「中央が白っぽく、両端に向かって別系統の色が濃くなる」構造です。たとえば相関行列をcoolwarmで描くと、「白=無相関、赤=正の相関、青=負の相関」が一目でわかります。発散型を使うときは、データの基準値(多くは0)が色の中央に来るようにvmin/vmaxを対称に設定するのがポイントです(例: vmin=-1, vmax=1)。
定性型(Qualitative)— カテゴリの区別向け(大小の意味なし)
国別、クラスタ番号、商品カテゴリのように「大小関係のないラベル」を塗り分けるには定性型を使います。

定性型は隣り合う色が意図的に「似ていない」よう設計されていて、グラデーションになっていません。逆にいうと、連続値のヒートマップに定性型を使うと値の大小が読めなくなるので、用途を混同しないよう注意してください。線グラフの色分けで使われるtab10は、matplotlibのデフォルトの色サイクルそのものです。
循環型(Cyclic)— 角度・位相など「端がつながる」データ向け
風向(0°と360°は同じ)、時刻(23時と0時は隣)、複素数の位相のように、最大値と最小値がつながっている周期的なデータには循環型を使います。

twilightは両端が同じ色(白)に戻ってくるので、0°と360°が同じ色になり、周期データに不自然な「継ぎ目」が出ません。hsvも循環しますが、明るさの変動が激しいため、現在はtwilight系が推奨されています。
使い分けの早見表
ここまでの4分類を表にまとめます。
| データの性質 | 分類 | 代表的なcmap | 例 |
|---|---|---|---|
| 小→大の単調な量 | 連続型 | viridis, plasma, Blues | 温度、密度、確率 |
| 基準値からの偏差 | 発散型 | coolwarm, RdBu, seismic | 相関係数、気温偏差 |
| 大小のないカテゴリ | 定性型 | tab10, Set2 | クラスタ、クラスラベル |
| 周期的な量 | 循環型 | twilight, hsv | 角度、位相、時刻 |
なお、これらの一覧図は次のようなコードで描けます。1×Nの勾配配列をimshowで描くのが定番テクニックです。
import numpy as np
import matplotlib.pyplot as plt
names = ['viridis', 'plasma', 'inferno', 'magma', 'cividis']
gradient = np.linspace(0, 1, 256)[None, :] # shape (1, 256) の横長グラデーション
fig, axes = plt.subplots(len(names), 1, figsize=(7.5, 3.0))
for ax, name in zip(axes, names):
ax.imshow(gradient, aspect='auto', cmap=name)
ax.set_yticks([]); ax.set_xticks([])
ax.set_ylabel(name, rotation=0, ha='right', va='center')
plt.show()
np.linspace(0, 1, 256)で0〜1を256分割した配列を作り、[None, :]で(1, 256)の「高さ1ピクセルの画像」とみなして表示しています。cmapの全色域を端から端まで眺められるので、配色の比較に便利です。
分類はわかりました。では、なぜ現代のmatplotlibは数ある連続型の中からviridisをデフォルトに選んだのでしょうか。そこには明確な科学的理由があります。
viridisがデフォルトである理由 — 知覚的均等性とjetの問題
matplotlib 2.0より前のデフォルトは、虹色のjetでした。MATLABの伝統を引き継いだ配色で、今でも論文や教科書で頻繁に見かけます。しかしjetには、可視化の研究者から長年指摘されてきた重大な欠陥があります。データにない構造が、あたかも存在するかのように見えてしまうのです。
実際に同じデータをjetとviridisで描き比べ、さらに各cmapの「知覚される明るさ(輝度)」をプロットしてみます。
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 1, 256)
def luminance(cmap_name):
"""cmapの各位置の知覚輝度(sRGB相対輝度の近似)を計算"""
rgb = plt.get_cmap(cmap_name)(x)[:, :3]
return 0.2126 * rgb[:, 0] + 0.7152 * rgb[:, 1] + 0.0722 * rgb[:, 2]
plt.figure(figsize=(8, 4))
plt.plot(x, luminance('jet'), label='jetの明るさ(輝度)')
plt.plot(x, luminance('viridis'), label='viridisの明るさ(輝度)')
plt.xlabel('カラーマップ上の位置(0〜1)')
plt.ylabel('知覚される明るさ')
plt.legend()
plt.show()

この図から、jetの問題点が2つ読み取れます。
- 上段左(jet)では、シアンや黄色の付近に「縞」が見える: 元データは2つの山がなだらかに重なっただけの滑らかな関数なのに、jetで描くと黄色やシアンの帯が輪郭のように浮かび、まるで等高線のような構造があるかに見えます。これはデータに存在しない偽のエッジです。
- 下段の輝度プロファイルで、jet(赤線)は明るさが上下に波打っている: 値0.3付近のシアンと0.7付近の黄色で輝度が急上昇し、その間で下降しています。人間の視覚は色相よりも明るさの変化に敏感なので、この輝度の凹凸が偽の縞として知覚されるのです。さらに、モノクロ印刷すると「中間の値が一番明るい」という意味不明な図になります。
一方のviridis(緑線)は、輝度が0から1へほぼ直線的に単調増加しています。この性質を知覚的均等性(perceptual uniformity)と呼びます。「データの値が一定量変化したとき、知覚される色の変化も一定量になる」よう、人間の色覚モデル(CAM02-UCS色空間)に基づいて数値最適化で設計されているのです。viridisはさらに、
- 色覚多様性(色弱)の人が見ても順序が保たれる
- モノクロ印刷しても「暗い=小、明るい=大」が保たれる
という性質も併せ持ちます。だからこそmatplotlib 2.0でデフォルトに採用されました。plasma・inferno・magma・cividisも同じ設計思想の仲間で、特にcividisは色覚多様性への対応をさらに強化したものです。既存コードでjetを見かけたら、特別な理由がない限りviridis系への置き換えをおすすめします。
cmap選びの軸ができたところで、こまごまとした便利機能を1つ挟みます。「配色は良いのだが、向きが逆」というときの_rです。
逆順カラーマップ — 名前に _r を付けるだけ
「値が大きいほど濃い色にしたいのに、このcmapは逆だ」という場面はよくあります。matplotlibでは、すべてのカラーマップに、名前へ_rを付けた逆順バージョンが自動登録されています。

図のとおり、viridis_rはviridisを左右反転したもの(黄→緑→紫)、coolwarm_rはcoolwarmの赤と青を入れ替えたものです。cmap='viridis_r'と書くだけで使えます。コード上でcmapオブジェクトから作る場合はplt.get_cmap('viridis').reversed()でも同じものが得られます。たとえば「降水量ゼロを白、多いほど濃い青」にしたければBluesそのまま、「標高が高いほど白(雪)」にしたければ地形系cmapの_r、のように使い分けます。
ここまでは「0〜1 → 色」を担うcmap側の話でした。次は、冒頭の2段階変換のもう半分、「データの値 → 0〜1」を担うNormを制御してみましょう。実はここを調整しないと救えないデータがあります。
Normで「数値→0〜1」を制御する — Normalize・LogNorm・BoundaryNorm
Normalize — デフォルトの線形変換
何も指定しないとき、matplotlibはNormalize(vmin, vmax)による線形変換を使います。数式で書くと、データの値 $x$ は
$$ x_{\text{norm}} = \frac{x – v_{\min}}{v_{\max} – v_{\min}} $$
によって0〜1に写されます。$v_{\min}$・$v_{\max}$はデフォルトではデータの最小値・最大値ですが、imshow(..., vmin=0, vmax=100)のように明示すれば「色とデータ値の対応」を固定できます。複数の図で色の意味を揃えたいときに必須のテクニックです。
LogNorm — 桁が散らばるデータを救う
線形変換で困るのが、値が数桁にわたって散らばるデータです。たとえば最大値が18万で大半の値が数十だと、ほとんどの点が0付近に正規化されて真っ暗に潰れます。そこで対数スケールで正規化するLogNormの出番です。
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
# 桁が大きく散らばるデータ(対数正規分布)
rng = np.random.default_rng(0)
data = 10 ** rng.normal(1.5, 0.8, (200, 200))
fig, axes = plt.subplots(1, 2, figsize=(11, 4.4))
im0 = axes[0].imshow(data, cmap='viridis', norm=mcolors.Normalize())
fig.colorbar(im0, ax=axes[0], label='値')
im1 = axes[1].imshow(data, cmap='viridis', norm=mcolors.LogNorm())
fig.colorbar(im1, ax=axes[1], label='値(対数目盛)')
plt.show()

左の線形Normalizeでは、ごく少数の大きな値に色域を食い尽くされ、画面のほぼ全体が紫一色に潰れています。右のLogNormでは「10倍の違い」が「同じ色の差」になるため、小さい値どうしの違いもきちんと色の違いとして見えます。カラーバーの目盛りも自動的に対数(10^0, 10^1, …)になっている点に注目してください。cmapはどちらもviridisのままで、前段の正規化を差し替えただけです。対数スケールでの可視化全般は対数グラフの解説記事も参考にしてください。
BoundaryNorm — しきい値で段階的に色を切り替える
連続的なグラデーションではなく、「5mm以上は注意、30mm以上は警報」のようにしきい値で離散的に色分けしたい場面もあります。気象図や危険度マップの定番表現です。これにはBoundaryNormを使います。
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
xx, yy = np.meshgrid(np.linspace(-3, 3, 300), np.linspace(-3, 3, 300))
field = 30 * np.exp(-((xx + 1)**2 + yy**2) / 2) \
+ 25 * np.exp(-((xx - 1.5)**2 + (yy - 1)**2) / 1.5)
bounds = [0, 5, 10, 20, 30, 40, 60] # しきい値の列
norm_b = mcolors.BoundaryNorm(bounds, ncolors=256)
fig, axes = plt.subplots(1, 2, figsize=(11, 4.4))
im0 = axes[0].imshow(field, cmap='YlOrRd', origin='lower')
fig.colorbar(im0, ax=axes[0], label='降水量のイメージ [mm]')
im1 = axes[1].imshow(field, cmap='YlOrRd', norm=norm_b, origin='lower')
fig.colorbar(im1, ax=axes[1], label='降水量のイメージ [mm]', ticks=bounds)
plt.show()

左の連続表示はなだらかで美しい一方、「どこからが20mm以上か」は読み取れません。右のBoundaryNormでは色が等値線のように段階的に切り替わるため、「この領域は30〜40mm」と領域単位で値を断言できるようになります。boundsの区間幅は不均等でも構わないので、「0〜5は細かく、それ以上は粗く」といった実務的な区切りが自由に設計できます。
Normとcmapの組み合わせを自在に操れるようになると、最後に残るのは「図に添えるカラーバー」と「cmapそのものを自作したい」という要望です。順に見ていきます。
カラーバーの操作 — 図に「色の凡例」を付ける
カラーバーは色と数値の対応を読者に伝える「凡例」であり、cmapを使う図にはほぼ必須です。基本は、プロット関数の返り値(マッパブル)をfig.colorbar()に渡すだけです。
import numpy as np
import matplotlib.pyplot as plt
xx, yy = np.meshgrid(np.linspace(-2, 2, 200), np.linspace(-2, 2, 200))
zz = np.exp(-(xx**2 + yy**2))
fig, ax = plt.subplots(figsize=(6, 4.5))
im = ax.imshow(zz, cmap='viridis', origin='lower')
cbar = fig.colorbar(im, ax=ax,
label='強度', # カラーバーのラベル
shrink=0.9, # 長さを90%に縮める
ticks=[0, 0.5, 1]) # 目盛りの位置を指定
plt.show()
よく使うオプションを整理しておきます。
| 引数 | 働き |
|---|---|
label |
カラーバーに付けるラベル(単位を書くのが定石) |
shrink |
カラーバーの長さの倍率(図とのバランス調整) |
ticks |
目盛りを置く値のリスト |
orientation='horizontal' |
横向きに配置する |
extend='both' |
範囲外の値があることを示す三角形を両端に付ける |
1つ注意点があります。scatterやimshowは色情報を持った「マッパブル」を返すのでそのまま渡せますが、プロットを介さずカラーバーだけを描きたい場合は、ScalarMappable(norm=..., cmap=...)を自分で作って渡します。本記事のカラーマップ一覧図のような「色見本」を作るときに使うテクニックです。
ここまでで組み込みcmapは自由自在になりました。それでも「会社のブランドカラーで塗りたい」「この論文の配色を再現したい」となったら、いよいよ自作です。
カスタムカラーマップの作り方 — ListedColormapとLinearSegmentedColormap
自作カラーマップには2つの作り方があり、性格がはっきり分かれています。
ListedColormap: 色のリストをそのまま離散的に並べる。クラス分けや段階表示向けLinearSegmentedColormap: 指定した色の間を滑らかに補間する。連続データ向け
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
# 方法1: ListedColormap — 5色をそのまま並べる(離散)
listed = mcolors.ListedColormap(
['#2c7fb8', '#7fcdbb', '#edf8b1', '#fdae61', '#d7191c'])
# 方法2: LinearSegmentedColormap — 3色の間を滑らかに補間(連続)
linear = mcolors.LinearSegmentedColormap.from_list(
'my_linear', ['navy', 'white', 'crimson'])
# 2次元データに適用して比較
xx, yy = np.meshgrid(np.linspace(-2, 2, 300), np.linspace(-2, 2, 300))
zz = np.exp(-(xx**2 + yy**2)) + 0.6 * np.exp(-((xx - 1.2)**2 + (yy - 0.8)**2) * 3)
fig, axes = plt.subplots(1, 2, figsize=(10.5, 4))
im0 = axes[0].imshow(zz, cmap=listed, origin='lower')
fig.colorbar(im0, ax=axes[0])
im1 = axes[1].imshow(zz - zz.mean(), cmap=linear, origin='lower')
fig.colorbar(im1, ax=axes[1])
plt.show()

上段の色見本を比べると、ListedColormap(左)は5色が階段状にパキッと切り替わり、LinearSegmentedColormap(右)は紺→白→深紅が途切れなく滑らかにつながっています。下段の適用例では、左は自動的に5段階の等値域表示(BoundaryNorm的な見た目)になり、右は平均からの偏差を白を中心とした自作の発散型で表現できています。「from_listに渡す色は何色でもよく、[(0, 'navy'), (0.3, 'white'), (1, 'crimson')]のように位置を指定した不等間隔の配置もできる」と覚えておくと、たいていの配色要求に応えられます。
最後に、cmapを「関数として」直接呼び出す小ワザを紹介します。ここまでの仕組みの理解が一気につながります。
cmapオブジェクトから色を直接取り出す
カラーマップの実体は「0〜1を受け取りRGBA色を返す呼び出し可能オブジェクト」なので、プロット関数を介さずに色そのものを取り出すこともできます。線グラフ10本をグラデーションで塗り分ける、といった場面で重宝します。
import numpy as np
import matplotlib.pyplot as plt
cmap = plt.get_cmap('viridis') # matplotlib.colormaps['viridis'] でも同じ
print(cmap(0.0)) # (0.267004, 0.004874, 0.329415, 1.0) 紫
print(cmap(0.5)) # (0.127568, 0.566949, 0.550556, 1.0) 緑
print(cmap(1.0)) # (0.993248, 0.906157, 0.143936, 1.0) 黄
# 線グラフ10本をviridisのグラデーションで塗り分ける
x = np.linspace(0, 2 * np.pi, 200)
plt.figure(figsize=(8, 4.5))
for i, a in enumerate(np.linspace(0.5, 2.0, 10)):
plt.plot(x, np.sin(a * x), color=cmap(i / 9), label=f'a={a:.1f}')
plt.legend(ncol=2, fontsize=8)
plt.show()
cmap(0.5)のように0〜1の値で呼び出すと、RGBA(赤・緑・青・不透明度がそれぞれ0〜1)のタプルが返ります。配列を渡せば一括変換もできます。つまりカラーマップとは結局、「0〜1 → RGBA」の純粋な関数なのです。冒頭の概念図の2段階変換のうち、後段だけを手動で使っている、と理解できます。
まとめ
本記事では、matplotlibのcmap(カラーマップ)について解説しました。要点をまとめます。
- カラーマップは「数値 → Normで0〜1に正規化 → cmapで色に変換」という2段階の仕組みで動く。プロット関数の引数
cmapに名前を渡すだけで使える - cmapは連続型・発散型・定性型・循環型の4分類で選ぶ。単調な量にはviridis系、偏差にはcoolwarm系、カテゴリにはtab10系、角度にはtwilight系が基本
- viridisがデフォルトなのは知覚的均等性(輝度が単調かつ一様に変化)のため。jetは輝度が波打つため偽の縞が見え、モノクロ印刷にも耐えない
- 桁が散らばるデータは
LogNorm、しきい値による段階表示はBoundaryNormで、cmapを変えずに正規化だけ差し替えられる - 自作は離散なら
ListedColormap、連続ならLinearSegmentedColormap.from_list。逆順は名前に_rを付けるだけ
カラーマップを使いこなせると、ヒートマップ・等高線・散布図の表現力が一段上がります。次のステップとして、以下の記事も参考にしてください。