「color scale」は統計データ分析の文脈で扱う重要概念のひとつ。 本ページでは「color scale」を取り巻く中核キーワードを以下にチップで一覧化する。 各キーワードは関連する概念・手法・道具立てを含み、 文献検索や学習計画の起点になる。
これらのキーワードは「color scale の理解 → 適用 → 検証」のプロセスを構成する。 各章で詳しく解説する。
🍰 まずはやさしく
数字を色に置き換えるルールです。
データの大きさを直感的に伝えるために使います。
スマホの天気予報の色の塗り分けのようなものです。
おすすめの色使いや注意点を学びます。
カラースケール:値を色で表現するスケールの設計
🍰 まずはやさしく
データを絵にするための道具の一つです。
グラフを正しく見せるために使います。
部活の成績やテストの結果を色で分ける時に役立ちます。
定義から使い方のコツまで順番に解説します。
この用語は 可視化 カテゴリに属します。 関連する別称・略号:(なし)。
論文・実務レポートで カラースケール が登場したら、 まず本ページの「30秒で分かる結論」と「直感で掴む」を読めば、 その文脈で何を言っているか把握できます。
本ページでは「color scale」を扱う。 統計データ分析コンペティション (2026) の教材で、 SSDSE-B-2026 (47 都道府県 × 複数年 × 100 超列) の実データを使った再現可能な学習を目指す。
「color scale」は統計・データサイエンスの体系における重要概念のひとつ。 本ページは「定義・直感・数式・実装・落とし穴・関連手法」の 6 視点で構成され、 各視点は独立して読めるが順序通り読むと体系的な理解が得られる。
🍰 まずはやさしく
色の塗り絵のような仕組みです。
パッと見てデータの違いを分かるようにします。
地図で人口が多い県を濃い色にする例があります。
データの種類に合わせた3つの色の選び方を学びます。
日本地図で都道府県別の人口を色塗りする。 大きい県を濃く、 小さい県を薄く ── これが カラースケール。 「青→緑→黄→赤」のような連続色を使うと一目で大小が伝わる。 但し、 色覚障害がある人にも判別可能か、 印刷で白黒になっても順序が保てるか、 などの配慮が必要。
カラースケール (color scale) は、 大きく 3 タイプに分類できます。 「データの性質」と「カラースケールの種類」を取り違えると、 グラフは読み手を盛大に誤誘導します。 まずはこの 3 分類を頭に叩き込みましょう。
| 分類 | 適するデータ | 代表的パレット | 設計の本質 |
|---|---|---|---|
| Sequential (連続) | 0 から大へ単調増加。 人口、 所得、 気温など順序のある量 | viridis、 plasma、 inferno、 cividis、 Blues、 YlOrRd | 明度を単調に変化させ、 グレースケール変換しても順序が保たれる |
| Diverging (発散) | 中央値 (0 や平均) を境に正負・大小が両側に存在 | RdBu、 BrBG、 PiYG、 coolwarm、 Spectral | 中央色 (白・淡黄) を「基準値」とし、 両端に対比色を配置 |
| Qualitative (定性) | 順序なしのカテゴリ。 地域名、 商品カテゴリ | Set1、 Set2、 Set3、 Paired、 Accent、 tab10 | 色相 (hue) を均等配分し明度はそろえる。 順序を示唆しない |
知覚的均等性とは「データ上で同じ差 (例: 100 万人差) に対して、 色空間上の知覚距離 ΔE が常に同じになる」という性質です。 数式で書くと、 値 $v_1, v_2$ に対する色 $c(v_1), c(v_2)$ について
$$\Delta E_{\text{CIE76}}(c(v_1), c(v_2)) \approx k \cdot |v_1 - v_2|$$
が 定数 k で線形に保たれることを意味します。 viridis や cividis はこの性質を満たすよう、 CIELAB 色空間で明度 (L*) が線形に変化するよう設計されています。 一方、 古典的な jet や rainbow は緑付近で明度が突出 (バンディング) し、 ΔE が不均一です ── これが「ジェットの罠」と呼ばれる現象です。
SSDSE-B-2026 の 「総人口 (A1101)」「出生数 (A4101)」「高齢化率 (A1303/A1101)」 をヒートマップや choropleth で示すとき、 どの cmap を選ぶかで読み手の印象が変わる。 ここでは「データの型 × 目的」で cmap を選ぶ実務指針をまとめる。
| データ型 | 推奨 cmap | SSDSE-B 例 |
|---|---|---|
| 単方向の量 (0 から増加) | viridis / plasma | 総人口、 出生数、 婚姻件数 |
| 中央値からの偏差 (発散) | RdBu_r / coolwarm | 全国平均との差 (人口、 失業率) |
| 順序カテゴリ | YlOrRd / Blues | 高齢化率の 5 段階区分 |
| 名義カテゴリ | tab10 / Set2 | 8 地方区分 (北海道, 東北, …) |
colorblind チェックの実務手順: matplotlib の viridis は CVD 安全だが、 自作カラーマップを使う場合は ① colorspacious.cspace_convert で deuteranopia 変換 ② 結果を保存して隣に並べて目視比較 ③ 主要 3 色がすべて識別可能か確認、 の 3 ステップを踏むこと。 SSDSE-B-2026 の総人口の choropleth では「東京 (最大値) と鳥取 (最小値)」の両極端が CVD 後も明確に区別できるかを最優先でチェックする。
jet を避けるべき理由 (再掲・要点): jet は明度が非単調 (黄色付近で最大、 青と赤で最小) なため、 グレースケール変換すると「中央値が最も明るい」 → 印刷時に値の順序が逆転して読める。 SSDSE-B の高齢化率を jet で示し白黒コピーすると「高齢化率 30% と 18% が同じ色に見える」事故が起きる。 viridis なら明度が単調増加なので白黒でも順序が保たれる。
この補遺では、 SSDSE-B-2026 (47 都道府県の社会統計) を題材に、 散布図・ヒストグラム・箱ひげ図といった代表的な可視化で「どのカラースケールを選ぶか」を、 入力データ・コード・実行結果・読み方の 4 点セットで丁寧にたどる。 ねらいは、 単に「viridis を使え」と覚えるのではなく、 「データの型」と「読み手に伝えたい順序関係」からカラースケールを逆算で選べる目を養うことにある。 本文中に挿入した 3 枚の図 (散布図・ヒストグラム・箱ひげ図) は html/glossary/figures/ の実画像であり、 同じ SSDSE-B-2026 から作られているため、 章全体で一貫した題材になっている。
下図は、 47 都道府県の 「総人口 (A1101)」と「65歳以上人口 (A1303)」を点でプロットしたもの。 ここでカラースケールが効くのは、 ① 単一色の濃淡で「3 つ目の変数 (例: 高齢化率)」を重ねる ② 8 地方区分 (北海道, 東北, 関東, …) で「カテゴリ別の塊」を見せる、 の 2 つの場面である。 ① は 逐次的 (sequential) なスケール、 ② は 定性的 (qualitative) なスケールを使う。

図 R447-1: 散布図の基本形。 横軸=総人口 (千人)、 縦軸=65歳以上人口 (千人)。 右上の外れ値は東京都。
読み方: 総人口が多い県ほど高齢者数も多く、 強い正の相関 (r ≒ 0.99) が見える。 ここに 「高齢化率」を色 (sequential) で重ねると、 「人口の割に高齢者比率が高い県 (=高齢化率が高い)」が黄色で浮かび上がり、 サイズ (人口) と比率 (高齢化率) を 1 枚で読み分けられる。 逆に 地方区分を色 (qualitative) で重ねると、 「関東 1 都 6 県が右上、 四国・九州が左下」というカテゴリの塊が見える。
このコードでやること: SSDSE-B-2026 から 2023 年の 47 都道府県の「総人口 (A1101)」「65歳以上人口 (A1303)」を読み込み、 高齢化率を派生させて、 散布図の点の色を viridis で「高齢化率」にマップする。
📥 入力データ (SSDSE-B-2026 の 2023 年分。 人口は人。 高齢化率=A1303÷A1101×100 で派生):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023].copy() # 最新年 (2023) の 47 都道府県 df['高齢化率'] = df['A1303'] / df['A1101'] * 100 # 65歳以上人口 ÷ 総人口 fig, ax = plt.subplots(figsize=(7, 5)) sc = ax.scatter(df['A1101'] / 1000, df['A1303'] / 1000, c=df['高齢化率'], cmap='viridis', s=80, edgecolor='white') plt.colorbar(sc, label='高齢化率 (%)') ax.set_xlabel('総人口 (千人)') ax.set_ylabel('65歳以上人口 (千人)') ax.set_title('SSDSE-B-2026: 人口×高齢者数×高齢化率 (viridis)') plt.tight_layout(); plt.savefig('out_r447_scatter.png', dpi=150) print(df[['A1101', 'A1303', '高齢化率']].describe().round(1)) |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 散布図の点は viridis (青紫 → 黄) で「高齢化率が低い → 紫、 高い → 黄」となる。 右上の東京 (人口・高齢者数とも最大) は紫寄り (高齢化率 22.8%)、 左下に並ぶ秋田・高知などは黄色 (36% 超)。 色の順序と数値の順序が一致するため、 読み手は colorbar を見ずとも「黄=高齢」と直感で理解できる。 これが sequential スケールの基本機能である。 なお高齢化率の最小は沖縄ではなく東京 (22.8%) で、 都市部ほど若い構造がそのまま色に出る。
下図は、 47 都道府県の 「高齢化率」のヒストグラム。 ヒストグラムは 1 変数の分布を見るためのもので、 通常は 単一色 (青や灰色) で塗ればよい。 カラースケールが効くのは「2 群以上を重ねて比較する」場合だけで、 1 群のヒストグラムを虹色で塗るのは情報量ゼロのデコレーションになりやすい (NG パターン)。

図 R447-2: ヒストグラムの基本形。 横軸=高齢化率 (%)、 縦軸=都道府県の数。 ピークは 30〜34%。
読み方: 高齢化率は 22.8% (東京) 〜 39.1% (秋田) の範囲に広がり、 中央値 31.8% 付近に山がある。 全体的に左裾が長い分布で、 「全国平均より上の県が東北・四国に集中」という構造を後段の地図で確認したくなる。 ここでカラースケールを増やすとしたら、 「高齢化率の区分 (低: 緑、 中: 黄、 高: 赤)」を YlOrRd で重ねて choropleth に展開する用途のみが妥当である。
このコードでやること: SSDSE-B-2026 の高齢化率を 「人口 200 万人以上の県 (大規模)」と「200 万人未満 (中小規模)」の 2 群に分け、 ヒストグラムを 2 色 (青・橙) で重ねる。 これが qualitative スケールが真に必要な場面である。
📥 入力データ (SSDSE-B-2026 2023 年から A1101=総人口・高齢化率を抽出):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023].copy() df['高齢化率'] = df['A1303'] / df['A1101'] * 100 big = df[df['A1101'] >= 2000000]['高齢化率'] # 総人口 200 万人以上 small = df[df['A1101'] < 2000000]['高齢化率'] # 200 万人未満 fig, ax = plt.subplots(figsize=(7, 4.5)) ax.hist(big, bins=12, alpha=0.6, color='#1f77b4', label=f'大規模 (n={len(big)})') ax.hist(small, bins=12, alpha=0.6, color='#ff7f0e', label=f'中小規模 (n={len(small)})') ax.set_xlabel('高齢化率 (%)'); ax.set_ylabel('県の数') ax.set_title('SSDSE-B-2026: 高齢化率の分布 (大規模 vs 中小規模)') ax.legend() plt.tight_layout(); plt.savefig('out_r447_hist.png', dpi=150) print('大規模:', big.describe().round(1).to_dict()) print('中小規模:', small.describe().round(1).to_dict()) |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 大規模県 (青) は平均 29.1%、 中小規模県 (橙) は平均 32.9% で、 約 3.8 ポイントの差。 ヒストグラムが 橙の山が右側 (高齢) に寄っているのが見える → 「人口が少ない県ほど高齢化が進む」という関係が分布レベルで確認できる。 ここで色を 7 色や虹色にすると 2 群の対比がぼやけるため、 qualitative は「群の数だけの色」しか使わないのが鉄則。
下図は、 47 都道府県を 8 地方区分 (北海道, 東北, 関東, 中部, 近畿, 中国, 四国, 九州・沖縄)に分けて、 各地方の高齢化率の分布を箱ひげで示したもの。 ここで使うのは qualitative なカラースケール (tab10 や Set2) で、 「順序のないカテゴリ」を視覚的に分離するのが目的。

図 R447-3: 地方区分ごとの箱ひげ。 中央線=中央値、 箱=IQR、 ひげ=1.5 IQR。
読み方: 関東は箱が低い位置 (24〜28%) で東京・神奈川などの都市部を反映。 四国は箱が高い位置 (32〜37%) で高齢化が顕著。 ここで色を viridis にしてしまうと「地方の名前に順序がある」と読み手に誤解させてしまう (北海道 < 東北 < 関東 という順序は存在しない) ため、 必ず qualitative パレットを使う。
このコードでやること: SSDSE-B-2026 に「地方区分」列を作り、 各地方の高齢化率を箱ひげで示す。 色は Set2 (qualitative・CVD 安全) を使う。
📥 入力データ (都道府県コードから地方区分を派生):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | # ── この抜粋で使うデータを用意します(英字の項目コードで読み込み)── import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=0) df = df[df['Code'].astype(str).str.match(r'^R\d{5}$', na=False)].copy() for _c in df.columns[3:]: df[_c] = pd.to_numeric(df[_c], errors='coerce') df = df[df['SSDSE-B-2026'] == df['SSDSE-B-2026'].max()] import pandas as pd import matplotlib.pyplot as plt df['高齢化率'] = df['A1303'] / df['A1101'] * 100 region_map = { '北海道':'北海道', '青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北','山形県':'東北','福島県':'東北', '茨城県':'関東','栃木県':'関東','群馬県':'関東','埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東', '新潟県':'中部','富山県':'中部','石川県':'中部','福井県':'中部','山梨県':'中部','長野県':'中部','岐阜県':'中部','静岡県':'中部','愛知県':'中部', '三重県':'近畿','滋賀県':'近畿','京都府':'近畿','大阪府':'近畿','兵庫県':'近畿','奈良県':'近畿','和歌山県':'近畿', '鳥取県':'中国','島根県':'中国','岡山県':'中国','広島県':'中国','山口県':'中国', '徳島県':'四国','香川県':'四国','愛媛県':'四国','高知県':'四国', '福岡県':'九州・沖縄','佐賀県':'九州・沖縄','長崎県':'九州・沖縄','熊本県':'九州・沖縄', '大分県':'九州・沖縄','宮崎県':'九州・沖縄','鹿児島県':'九州・沖縄','沖縄県':'九州・沖縄', } df['地方'] = df['Prefecture'].map(region_map) order = ['北海道','東北','関東','中部','近畿','中国','四国','九州・沖縄'] data = [df[df['地方']==r]['高齢化率'].dropna().values for r in order] cmap = plt.get_cmap('Set2') colors = [cmap(i) for i in range(len(order))] fig, ax = plt.subplots(figsize=(8, 4.5)) bp = ax.boxplot(data, tick_labels=order, patch_artist=True) for box, c in zip(bp['boxes'], colors): box.set_facecolor(c); box.set_alpha(0.75) ax.set_ylabel('高齢化率 (%)') ax.set_title('SSDSE-B-2026: 地方区分ごとの高齢化率 (Set2 / qualitative)') plt.xticks(rotation=20) plt.tight_layout(); plt.savefig('out_r447_box.png', dpi=150) print(df.groupby('地方')['高齢化率'].agg(['median','mean','std']).round(2)) |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 関東の中央値が 28.1% と最も低く、 東北の中央値が 35.1% と最も高い (四国も 34.8% と高い)。 8 地方の差が 色 (qualitative) ではなく 箱の高さで読めるのが正しい設計。 もしここで色に高齢化率を sequential で重ねてしまうと、 色と位置の二重符号化になって読みづらくなる。 「位置に意味のある変数」と「色に意味のある変数」は別物に割り当てるのが鉄則。 なお groupby の既定出力は地方名の文字コード順に並ぶ。
| 類型 | 特徴 | 代表 cmap | SSDSE-B-2026 適用例 |
|---|---|---|---|
| 逐次 (sequential) | 0 から単調に増加する量 | viridis, plasma, YlOrRd | 総人口、 出生数、 高齢化率、 婚姻件数 |
| 発散 (diverging) | 中央値からの偏差 (正負がある) | RdBu_r, coolwarm, PiYG | 「全国平均との差」: 高齢化率 - 31.6 / 出生数 - 全国平均 |
| 定性 (qualitative) | 順序のないカテゴリ | tab10, Set2, Pastel1 | 8 地方区分、 産業大分類、 政令市/その他 |
類型ミスの典型: ① 高齢化率 (sequential) に tab10 を当ててしまい順序が読めない、 ② 8 地方 (qualitative) に viridis を当てて「北海道 < 沖縄」と誤解させる、 ③ 全国平均との差 (diverging) に YlOrRd を当てて「正の差も負の差も同じ赤」になる、 の 3 つ。 いずれも読み手の認知負荷が跳ね上がる。
| 確認項目 | 合格基準 | 不合格時の対処 |
|---|---|---|
| deuteranopia 変換後も色順序が保たれるか | 最大値・最小値が肉眼で識別可能 | cmap を viridis や cividis に差し替え |
| グレースケール変換後も明度が単調か | 印刷物で値の順序が読める | jet/rainbow を捨て viridis 系へ |
| color のみでカテゴリを判別していないか | マーカー形状・線種でも区別できる | marker='o','s','^' など二重符号化 |
| colorbar に単位・範囲が明記されているか | 「(%)」「(千人)」が読み取れる | label='高齢化率 (%)' を追加 |
| 階級数が読み手に過剰でないか | 連続: 8 階級以内 / カテゴリ: 8 色以内 | 分位点で 5〜7 ビン化、 9 色以上は分割 |
| 凡例が本文の用語と一致しているか | 「大規模/中小規模」「関東」など | 本文と図の用語を必ず揃える |
vmin=-10, vmax=+10 のように中央値が白になるよう対称化しないと、 正負の符号が色で読めなくなる。 SSDSE-B-2026 の高齢化率差は ±8% 程度なので vmin=-8, vmax=+8 が妥当。label='高齢化率 (%)' のように単位を入れる。plt.set_cmap('viridis') や imshow(..., cmap='viridis') に置換する。SSDSE-B-2026 の 47 都道府県データを題材にすると、 カラースケールの「3 大類型 (逐次・発散・定性)」が、 散布図・ヒストグラム・箱ひげ図といった頻出図でどう機能するかが具体的に見える。 重要なのは、 「色は数値の大きさ」「位置は別の数値」「形は別のカテゴリ」のように、 視覚チャネルを 1 つの変数につき 1 つだけ割り当てること。 二重符号化 (色 + 位置で同じ変数を示す) は冗長で読み手を疲れさせ、 逆に「色だけで全カテゴリ識別」を狙うと CVD 不適合や階級数過多のリスクが急上昇する。 SSDSE-B-2026 を毎回開いて「この列は連続か離散か / 順序はあるか / 0 中心か」と問う癖をつけると、 カラースケール選択は迷わなくなる。
関連: 棒グラフ、 円グラフ、 ヒストグラム、 箱ひげ図、 相関、 単変量可視化。
SSDSE-B-2026 から取り出した変数を可視化するとき、 「どのカラースケールを選ぶか」で迷う場面は非常に多い。 ここでは 「データの種類 → スケールの種類 → 推奨カラーマップ」の 3 段階で判断するフローチャートを示し、 47 都道府県データの具体例と組み合わせて、 選択ミスを未然に防ぐ。 ポイントは「変数が連続値なのか順序付きカテゴリなのか定性カテゴリなのか」と「ゼロ・中央値・基準値の有無」を最初に確認することで、 ここを誤ると以降の表現が一気に破綻する。
この 4 問だけで、 SSDSE-B-2026 のどの列に対しても「どのカラースケールが妥当か」をほぼ機械的に決められる。 とくに [Q2] の「基準点があるか」は見落としやすく、 たとえば「人口増減率」を Blues (逐次) で塗ると、 マイナス (人口減少) とプラス (人口増加) の境界が見えなくなるという致命的な誤りに繋がる。
| 列 (代表例) | 変数の性質 | 基準点 | 分布の桁差 | 推奨カラーマップ | 注意点 |
|---|---|---|---|---|---|
| 総人口 (A1101) | 連続・正 | なし | 54 万〜1409 万 (26 倍) | viridis + LogNorm | 線形だと東京以外が同色になる |
| 人口増減率 (%) | 連続・正負 | 0 (横ばい) | -1.7 〜 +0.3 程度 | RdBu_r (vmin=-2, vmax=2) | 0 が白になるよう vcenter=0 を必ず指定 |
| 高齢化率 (A1303/A1101) | 連続・正 | 全国平均 (約 31.6%) | 22.8〜39.1% (1.7 倍) | YlOrRd または PuOr (中央 = 平均) | 平均からの偏差で発散型にしても良い |
| 第 1 次産業就業者割合 | 連続・正 | なし | 0.4〜13% | YlGn 逐次型 | 0 近辺の県が多いので等間隔より分位 |
| 地方区分 (8 区分) | 定性・順序なし | ― | ― | Set2 (8 色) | 近隣同士で類似色にならないよう確認 |
| 都市規模 (政令市/中核市/その他) | 順序付きカテゴリ | ― | ― | Blues 3 段階 | 濃淡で順序を示す |
このコードでやること: SSDSE-B-2026 の 1 列を渡すと、 上記フローチャートに従って「推奨カラーマップ名」と「LogNorm の要否」を返す簡易判定関数を実装する。 dtype と min/max/zero 跨ぎから機械的に判定する。
📥 入力 (SSDSE-B-2026 抜粋, df.head() 想定):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | import pandas as pd import numpy as np def recommend_cmap(s: pd.Series) -> dict: """SSDSE 列に対し、 カラーマップと正規化方式を推奨する。""" s = s.dropna() # Q1: 数値かカテゴリか if not pd.api.types.is_numeric_dtype(s): n_unique = s.nunique() if n_unique <= 10: return {"cmap": "Set2", "norm": "categorical", "reason": "定性 ≤10 カテゴリ"} return {"cmap": "Set3", "norm": "categorical", "reason": "定性 多カテゴリ (要集約)"} vmin, vmax = s.min(), s.max() # Q2: 基準点 0 を跨ぐか if vmin < 0 and vmax > 0: return {"cmap": "RdBu_r", "norm": "TwoSlopeNorm(vcenter=0)", "reason": "正負を跨ぐ発散型"} # Q3: 桁差が 10 倍以上か ratio = vmax / max(vmin, 1e-9) if ratio >= 10: return {"cmap": "viridis", "norm": "LogNorm", "reason": f"桁差 {ratio:.1f}倍 → 対数"} return {"cmap": "viridis", "norm": "Normalize", "reason": "通常の逐次型"} # SSDSE-B-2026 を読み込み、 最新年 (2023) と前年 (2022) を用意 df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) latest = df[df['SSDSE-B-2026'] == 2023].set_index('Prefecture') prev = df[df['SSDSE-B-2026'] == 2022].set_index('Prefecture') cols = { '総人口': latest['A1101'], # A1101 '高齢化率': latest['A1303'] / latest['A1101'] * 100, # 派生 '人口増減率': (latest['A1101'] - prev['A1101']) / prev['A1101'] * 100, '年平均気温': latest['B4101'], # B4101 } for name, s in cols.items(): print(name, '→', recommend_cmap(s)) |
📤 実行すると次の出力が得られる:
💬 機械的判定は 7 割の場面で妥当だが、 高齢化率のように「全国平均を基準にしたい」場合は発散型にしたくなる。 そういう「意味的な基準点」は人間が補正する必要があるため、 関数の判定はあくまで第 1 案として使い、 最終決定は分析者が文脈と合わせて行う。
df[col].hist(log=True) で形を確認する。下のヒートマップは SSDSE-B-2026 (2023年) の 47 都道府県「高齢化率 (A1303÷A1101×100, %)」を Python で実測転記した実データです (合成データではありません)。 47 セルを都道府県コード順に 8 列で並べています。 カラースケールを切り替えると、 同じ数値でも「読み手が受け取る印象」がまるで変わることを体感できます。 とくに レインボー (jet) を選ぶと、 実際には連続的に変化しているはずの高齢化率に 偽の境界が生まれ、 色の明るさ (知覚) が数値の順序と一致しなくなる様子が分かります。 2型色覚シミュレーションを ON にすると、 レインボーが特に判別困難になることも確認できます。
🍰 まずはやさしく
数字を色に変える計算式のことです。
どの数字にどの色を割り当てるか決めます。
買い物の金額を色の濃さで分ける時に使います。
色を決めるための数式と仕組みについて読みます。
値 $v$ を $[0, 1]$ に正規化し、 カラーマップ関数 $\text{cmap}$ で RGB に変換する。 cmap は連続写像で、 知覚的等差性 (perceptually uniform) を持つことが望ましい。
数式に出てくる記号の意味を 1 つずつ確認しましょう。
カラースケールを「自分で設計したい」場合、 色空間 (color space) の理解が不可欠です。 主要 4 つの色空間をまとめます。
| 色空間 | 軸 | 用途 | 知覚均等性 |
|---|---|---|---|
| RGB | 赤・緑・青の加法混色 [0, 255] | ディスプレイ表示 | なし |
| HSL / HSV | 色相 H・彩度 S・明度 L (V) | 人間の直感操作 | 不完全 |
| CIELAB (Lab) | 明度 L*・緑赤軸 a*・青黄軸 b* | 印刷・科学可視化 | 高い (ΔE76 で評価) |
| OKLab | L・a・b (CIELAB 改良版、 2020 提案) | 最新 Web/UI | 最高ランク |
CIE76 の色差は CIELAB 空間でのユークリッド距離:
$$\Delta E_{76} = \sqrt{(L_1^* - L_2^*)^2 + (a_1^* - a_2^*)^2 + (b_1^* - b_2^*)^2}$$
$\Delta E \approx 1$ が「ぎりぎり弁別可能」、 $\Delta E \approx 5$ が「明確に違うと感じる」目安。 良いカラースケールは、 データ範囲 $[v_{\min}, v_{\max}]$ を 256 段階に分けたとき、 隣接段階の $\Delta E$ が一定 (約 1.0 〜 1.5) であるよう設計されます。
このコードでやること: matplotlib の viridis と jet について、 隣接段階の ΔE を計算し、 ばらつきを比較する。
📥 入力データ: matplotlib 内蔵 cmap オブジェクト (256 段階の RGB を返す関数)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import matplotlib.pyplot as plt import numpy as np from skimage.color import rgb2lab def delta_e_sequence(cmap_name, steps=256): cmap = plt.get_cmap(cmap_name) xs = np.linspace(0, 1, steps) rgbs = cmap(xs)[:, :3] labs = rgb2lab(rgbs.reshape(1, -1, 3))[0] deltas = np.linalg.norm(np.diff(labs, axis=0), axis=1) return deltas for name in ['viridis', 'plasma', 'jet', 'cividis']: d = delta_e_sequence(name) print(f'{name:10s} ΔE mean={d.mean():.2f} std={d.std():.2f} max={d.max():.2f}') |
📤 実行例:
企業ダッシュボードでは「コーポレートカラー」を強制される場面が多い。 例えば青系のブランドなら、 「青→薄青→白」の sequential を作る必要があります。 matplotlib では LinearSegmentedColormap.from_list() で 2-3 色から補間したカラースケールを作れます。
このコードでやること: SSDSE-B-2026 の人口データに対し、 ブランドカラー (#003366 → #FFFFFF) の自作 sequential cmap を作って描画する。
📥 入力データ: SSDSE-B-2026 の総人口 (47 値)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import pandas as pd import matplotlib.pyplot as plt from matplotlib.colors import LinearSegmentedColormap df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023].sort_values('A1101', ascending=False) # コーポレート色: 小さい値=白 → 大きい値=濃紺 の 2 色補間で cmap 化 brand_cmap = LinearSegmentedColormap.from_list( 'brand_navy', ['#FFFFFF', '#003366'], N=256) norm = plt.Normalize(df['A1101'].min(), df['A1101'].max()) colors = brand_cmap(norm(df['A1101'])) fig, ax = plt.subplots(figsize=(10, 6)) ax.barh(df['Prefecture'], df['A1101'], color=colors, edgecolor='#003366') ax.invert_yaxis() ax.set_xlabel('総人口 (人)') plt.tight_layout() plt.savefig('brand_color.png', dpi=150) |
📤 実行例 (上位 5 県の RGB 値):
| ツール | 内蔵 cmap 数 | CUD 推奨 | カスタム自作 | 対話的編集 |
|---|---|---|---|---|
| matplotlib | 170+ | viridis/cividis 標準 | ○ (LinearSegmentedColormap) | × |
| seaborn | matplotlib 継承 + 独自 | ○ | ○ (color_palette) | × |
| plotly | 100+ (continuous) | Viridis 標準 | ○ (colorscale list) | ○ |
| bokeh | 50+ (palettes) | Viridis、 Cividis | ○ | ○ |
| Tableau | 20+ (内蔵 palette) | Blue-Teal 推奨 | ○ (xml) | ◎ |
| Power BI | limited | 未対応 (要手動) | ○ (theme JSON) | ◎ |
| Excel | ~10 (conditional format) | × | △ (3 色まで) | ○ |
| D3.js | d3-scale-chromatic 全カバー | ○ | ◎ (interpolate) | ○ |
2 色 $c_0, c_1$ の間を $t \in [0, 1]$ で補間する場合、 補間する色空間によって結果が変わります。 RGB 線形補間と HCL 補間を比較すると:
$$c_{\text{RGB}}(t) = (1-t) \cdot c_0 + t \cdot c_1, \quad c_{\text{HCL}}(t) = \text{HCL}^{-1}\bigl((1-t) \cdot \text{HCL}(c_0) + t \cdot \text{HCL}(c_1)\bigr)$$
RGB 補間は「数値的に簡単」だが、 中間色がくすんだ灰色になりがち (例: 赤+緑 → 茶)。 HCL/OKLab 補間なら明度・彩度が滑らかに変化し、 鮮やかな中間色が得られます。 D3.js の d3.interpolateHcl や Python の colorspacious ライブラリが対応しています。
最近は LLM や AutoML 系ツールが「データに最適な cmap」を自動推薦するサービスも登場しています。 ただし、 最終判断は人間がすべき領域 ── データの意味、 受け手の認知特性、 媒体 (Web/印刷) を総合的に考えるのは AI には難しい部分があります。
このコードでやること: scikit-image でデータ統計 (歪度・尖度) を計算し、 経験則に基づき cmap タイプを推薦する小関数を書く。
📥 入力データ: SSDSE-B-2026 の総人口 (47 値)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import pandas as pd from scipy.stats import skew, kurtosis def suggest_cmap(values, center=None): s = skew(values) k = kurtosis(values) if center is not None: return 'RdBu_r', 'diverging (中央あり)' if abs(s) > 2: return 'viridis (LogNorm 推奨)', f'歪度 |s|={abs(s):.2f} 大 → 対数スケール推奨' if abs(s) > 0.5: return 'plasma', f'歪度 |s|={abs(s):.2f} 中 → 強コントラスト' return 'cividis', '対称分布 → CUD 安全' df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) cmap, reason = suggest_cmap(df['A1101'].values) print(f'推奨 cmap: {cmap}') print(f'理由: {reason}') |
📤 実行例:
Plotly Express の px.choropleth は color_continuous_scale 引数を省略すると Viridis をデフォルトに使用します。 これは「PEP 8 のスタイルガイド」と同じく、 デフォルトで CUD 対応・知覚均等なものが採用されているため、 大半のケースで安全です。 ただし、 ブランドカラー指定がある場合や、 diverging が適切な場合は明示的に指定する必要があります。
プレゼン直前・論文投稿前に、 以下のチェックリストで自分の図を点検しましょう。
Q1: jet がそんなにダメなら、 なぜ古い論文・ソフトでは標準だったのですか?
1990 年代、 ディスプレイの色再現性が低い時代に「明度差が少なくても色相差で見せられる」rainbow 系が好まれました。 また matplotlib も Ver 1.x までは jet がデフォルトでした (2.0 で viridis に変更)。 単に「歴史的経緯による慣習」で残っているだけで、 現代の研究倫理基準では非推奨です。
Q2: 真っ赤で「やばい」と煽る天気予報のテロップは悪なのですか?
「警報レベル = 赤」は感情的に伝えるための工夫として正当な選択です。 ただし「赤の濃度差で値を比較せよ」という用途には不適 ── 警報の有無を二値で示すなら赤・無色の使い分けで十分です。 連続的に値を読み取らせるなら sequential cmap を別個に用意するのが鉄則。
Q3: cividis と viridis、 どちらを使うべき?
既定値 (デフォルト) は viridis で問題なし。 ただし「明確に P/D/T 全色覚タイプに最適化したい」「印刷物 (グレースケール変換含む)」では cividis のほうがやや安全。 viridis は中央付近に緑が入るため D 型でわずかに弁別性が落ちます。 cividis は青-黄軸のみで設計され、 すべての色覚タイプで均一です。
Q4: SSDSE-B-2026 の地方区分 (8 地方) は何色で塗り分ければいい?
8 カテゴリなら Okabe-Ito 8 色がベスト。 これは「8 色すべてが色覚バリアフリーで弁別可能」と保証されているため、 公的データ可視化のデファクト標準。 9 種以上が必要なら 2 つの色相環を組み合わせるか、 同色相の濃淡 2 段階で表現するのが定石です。
Q5: 散布図の点の色を「第 3 変数」で塗ると分かりやすい?
条件付きで Yes。 第 3 変数が連続 (例: 高齢化率) なら sequential cmap で OK。 ただし、 点が重なる場合は alpha=0.6 で透過させると色の重なりがブレンドして本当の濃度が見えます。 第 3 変数がカテゴリ (例: 地方区分) なら、 色 + マーカー形状を組み合わせると CUD 対応も両立します。
| 年代 | 出来事 | 意義 |
|---|---|---|
| 1976 | CIELAB 色空間制定 | 知覚的に均等な色差計算の標準確立 |
| 2002 | ColorBrewer 1.0 公開 (Cynthia Brewer) | 地理可視化向け配色集の事実上の標準 |
| 2008 | Okabe & Ito CUD 8 色提案 | 色覚バリアフリー定性パレットの普及 |
| 2015 | viridis 提案 (matplotlib 2.0) | 知覚均等 + CUD 対応の sequential |
| 2018 | cividis 提案 (Nuñez et al.) | 3 タイプ色覚すべてに最適化 |
| 2020 | OKLab 提案 (Björn Ottosson) | CIELAB より高精度な知覚均等空間 |
| 2023 | CSS Color Module Level 4 (W3C) | Web で OKLab 直接記述が可能に |
2026 年現在、 学術可視化のスタンダードは viridis / cividis、 Web/UI は OKLab ベースのカスタム配色、 ビジネスダッシュボードは ブランドカラー + CUD 互換のハイブリッド ── と棲み分けが進んでいます。 SSDSE のような公的統計データを再現する場面では、 viridis/cividis を選んでおけば学術査読・公的発表どちらでも通用します。
良いカラースケールを設計するには、 人間視覚の特性も理解しておく必要があります。 主要な認知制約を 4 つにまとめます。
隣接する色によって、 同じ色でも見え方が変わる現象。 灰色の四角を白背景に置くか黒背景に置くかで明度知覚が変わるのが典型。 ヒートマップで「同じセル色」でも周囲のセル色次第で読み取り値が ±10% ずれる実験報告があります。 対策: カラーバーを大きく、 隣接する位置に置くこと。
「刺激強度の対数に比例して知覚が変化する」という古典心理物理学の法則。 明度差 ΔL の知覚は元の明度 L に対する比 ΔL/L で決まる ── つまり対数スケールが「人間の感覚に素直」です。 sequential cmap で LogNorm を使うと、 視覚的に自然な等差表現になります。
$$\Delta P = k \cdot \frac{\Delta L}{L} \quad (\text{知覚強度差} \propto \text{相対変化})$$
中心視 (黄斑) は色覚に強く、 周辺視は明度差に強い。 大画面プレゼンで色だけに頼ると、 注意が中心に向いていないと細部が見えません。 対策: 色だけでなく形・サイズ・配置でも情報を冗長化すること (アクセシビリティの基本)。
水晶体は加齢とともに黄ばみ、 60 代以降は青色が暗く見える傾向 ── 「白背景上の薄青文字は読めない」 苦情を年配視聴者から受けやすい。 SSDSE のような幅広い年齢層に届ける可視化では、 青背景に黄文字は避け、 黒文字 + 中明度背景を選ぶのが安全。
→ 棒の長さで人口を表現する場合、 色は「グループ識別 (地方区分)」に使う。 Okabe-Ito 8 色を地方区分に割り当て、 棒の長さ + 色の二重符号化で誰でも判別可能に。
→ 固定スケールを必ず使う (毎フレーム max を変えない)。 視聴者が「赤=危険」と覚えた瞬間、 翌日も同じ意味であってほしい。 cmap は viridis、 vmin=0, vmax=全期間 99%ile に固定。
→ 値域 [-1, 1]、 中央 0 がある diverging。 coolwarm or RdBu_r + center=0, vmin=-1, vmax=1 固定。 アノテーションに小数点 2 桁の値を併記すると印刷で潰れても読める。
→ 第 3 変数が連続 → viridis、 点を alpha=0.65, s=60 で重なりを和らげる。 カテゴリ → Set2 + マーカー形状の二重符号化。
→ viridis または cividis、 Jenks natural breaks で 5-7 階級に離散化。 巨大値 (東京) は別途数値ラベル併記、 cmap だけに頼らない。
このコードでやること: 上記レシピ A-E を 1 つのスクリプトでまとめて実行し、 5 種類の図を一気に生成する。
📥 入力データ: SSDSE-B-2026 (47 県全データ)、 計算済の相関行列 (4×4)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import pandas as pd import matplotlib.pyplot as plt import seaborn as sns df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) # レシピ A: 地方区分付き棒グラフ region_map = {'01':'北海道', '02':'東北', '13':'関東', '23':'中部'} okabe = ['#E69F00','#56B4E9','#009E73','#F0E442', '#0072B2','#D55E00','#CC79A7','#000000'] # レシピ C: 相関行列ヒートマップ cols = ['A1101', 'A4101', 'A1303', 'B4101'] corr = df[cols].corr() fig, ax = plt.subplots(figsize=(6, 5)) sns.heatmap(corr, cmap='coolwarm', center=0, vmin=-1, vmax=1, annot=True, fmt='.2f', ax=ax) plt.savefig('corr_recipe_c.png', dpi=150) |
📤 実行例 (相関行列出力):
| cmap | 型 | 色相変化 | 明度変化 | CUD | グレー印刷 | 主な用途 |
|---|---|---|---|---|---|---|
| viridis | seq | 紫→青→緑→黄 | 単調増加 | ◎ | ◎ | 汎用デフォルト |
| plasma | seq | 紺→桃→黄 | 単調増加 | ◯ | ◎ | プレゼン向け |
| inferno | seq | 黒→赤→黄 | 単調増加 | ◯ | ◎ | 熱画像、 dark mode |
| magma | seq | 黒→紫→白 | 単調増加 | ◯ | ◎ | 天文画像 |
| cividis | seq | 青→黄 | 完全線形 | ◎◎ | ◎ | 論文、 公的資料 |
| Blues | seq | 薄青→濃青 | 単調減少 | ◎ | ◎ | 水・寒色テーマ |
| Reds | seq | 薄赤→濃赤 | 単調減少 | ◯ | ◎ | 警報・熱・強調 |
| YlOrRd | seq | 黄→橙→赤 | 単調減少 | △ | ◎ | 気温・密度 |
| RdBu_r | div | 青→白→赤 | V 字 | ◎ | ○ | 気温偏差、 相関 |
| coolwarm | div | 青→白→赤 | V 字 | ◎ | ○ | 統計、 物理 |
| BrBG | div | 茶→白→青緑 | V 字 | ◎ | ○ | 地形・湿潤度 |
| tab10 | qual | 10 色相 | 均一 | △ | × | カテゴリ汎用 |
| Set2 | qual | 8 色相パステル | 高均一 | ◯ | × | 柔らかい配色 |
| Okabe-Ito | qual | 8 CUD 色 | 均一 | ◎◎ | ○ | 論文用、 CUD 必須 |
| jet (非推奨) | seq | 紫→青→緑→黄→赤 | 不均一 | × | × | 古い慣習のみ |
カラースケールを選ぶ最大の理由のひとつは「色覚多様性 (Color Vision Deficiency, CVD) を持つ読み手にも情報を届けること」である。 日本人男性のおよそ 5%、 女性の 0.2% が何らかの CVD を持つとされ (Deutan, Protan, Tritan の 3 型が代表)、 47 都道府県データを使った地図やヒートマップを公開する以上、 「CVD ユーザでも読める色か」を必ず検証すべきである。 ここでは colorspacious や matplotlib を使って実際に CVD シミュレーションを行い、 viridis / plasma / cividis / jet の 4 つを比較する手順を紹介する。
| 型 | 原因 | 影響を受ける色対 | SSDSE 可視化での具体例 |
|---|---|---|---|
| Deutan (D 型) | 緑錐体の感度低下 | 赤と緑、 黄緑とオレンジ | RdYlGn ヒートマップで「人口減少 (赤)」と「増加 (緑)」が区別しにくい |
| Protan (P 型) | 赤錐体の感度低下 | 赤と黒、 紫と青 | jet の赤端が暗く沈んで「最大値」が読み取れない |
| Tritan (T 型) | 青錐体の感度低下 (稀) | 青と緑、 黄と紫 | plasma の中間域 (青紫〜紫) でグラデーションが見えにくい |
このコードでやること: SSDSE-B-2026 の総人口を 4 つのカラーマップ (viridis, plasma, cividis, jet) で塗り、 さらに Deutan/Protan/Tritan の 3 型 CVD シミュレーションを colorspacious で適用して、 12 通りの見え方を並べる。
📥 入力データ (SSDSE-B-2026, 47 行):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | import pandas as pd import numpy as np import matplotlib.pyplot as plt from matplotlib.colors import LogNorm from colorspacious import cspace_convert df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) pop = df[df['SSDSE-B-2026'] == 2023]['A1101'].values # 2023 年の総人口 cmaps = ['viridis', 'plasma', 'cividis', 'jet'] cvds = [('Normal', None), ('Deutan', {"name":"sRGB1+CVD", "cvd_type":"deuteranomaly", "severity":100}), ('Protan', {"name":"sRGB1+CVD", "cvd_type":"protanomaly", "severity":100}), ('Tritan', {"name":"sRGB1+CVD", "cvd_type":"tritanomaly", "severity":100})] fig, axes = plt.subplots(len(cvds), len(cmaps), figsize=(14, 12)) for r, (cvd_name, cvd_space) in enumerate(cvds): for c, cm in enumerate(cmaps): cmap = plt.get_cmap(cm) colors = cmap(LogNorm(vmin=pop.min(), vmax=pop.max())(pop))[:, :3] if cvd_space is not None: colors = cspace_convert(colors, cvd_space, "sRGB1").clip(0, 1) axes[r, c].imshow(colors[np.newaxis, :, :], aspect='auto') axes[r, c].set_title(f'{cm} / {cvd_name}', fontsize=9) axes[r, c].axis('off') plt.tight_layout() plt.savefig('color_cvd_comparison.png', dpi=120) |
📤 実行すると 4×4 のグリッド画像が生成され、 次の傾向が一目で分かる:
💬 結論: cividis > viridis > plasma >> jet の順で CVD 安全性が高い。 47 都道府県データを Web 公開する場合は cividis または viridis を第一選択とし、 jet/rainbow 系は使わない。 とくに政府・自治体の公開資料、 学会発表、 教科書用図版では、 cividis を選んでおけばまず誤読は起きない。
「知覚的均一」とは、 カラースケール上の 等距離が等しい知覚差に対応すること。 viridis / cividis は CIELAB の L* (明度) が直線的に増加するよう設計されており、 dataValue 0.3→0.4 と 0.7→0.8 が同じくらい「違って見える」。 一方 jet は明度が一旦上がって下がる U 字型なので、 「両端の色が中央より暗い」となり、 数値の順序と知覚順序が一致しない。
| cmap | L* 単調性 | ΔE (隣接サンプル平均) | 知覚順序の保存 | SSDSE 適性 |
|---|---|---|---|---|
| viridis | 単調増加 | 2.1 ± 0.3 | ○ | ◎ |
| plasma | 単調増加 | 2.3 ± 0.4 | ○ | ◎ |
| cividis | 単調増加 (CVD最適化) | 2.0 ± 0.2 | ◎ | ◎ |
| magma | 単調増加 | 2.2 ± 0.3 | ○ | ◎ |
| jet | 非単調 (U 字) | 4.5 ± 2.1 | × | × |
| rainbow | 非単調 | 4.1 ± 1.8 | × | × |
ΔE は CIEDE2000 色差。 「2 以下」が均一、 「4 以上」は不均一とされる。 jet の「4.5 ± 2.1」は隣接サンプル間で 2 倍以上ばらつくことを意味し、 グラデーションを目視で読み取ったときに「中央付近は変化が見えないのに、 両端だけ急に色が変わる」という現象が起きる。
SSDSE-B-2026 の A1101 (総人口) を 47 都道府県分用意し、 matplotlib でカラースケールを切り替えて可視化する具体例。
A1101 の 47 値
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])。 A1101 列 (2023 年) を取り出すと最小値 鳥取県 537,000、 最大値 東京都 14,086,000、 中央値 約 1,550,000。 約 26 倍のレンジ。norm = (x - x.min()) / (x.max() - x.min())。 東京 → 1.000、 鳥取 → 0.000、 大阪 (876 万) → 0.607、 北海道 (509 万) → 0.336。 ただし対数変換 (np.log10) も検討推奨 (人口分布は右に長い裾、 log で知覚均等)。viridis (連続: 人口の大小)、 RdBu_r (発散: A1303/A1101 高齢化率の中央値 (31.8%) からの偏差)、 tab10 (質的: 8 地方区分 = 北海道/東北/関東/中部/近畿/中国/四国/九州沖縄)。 連続値に jet を使うと正解。plt.colorbar(label='総人口 [万人]')。 ticks は 100, 500, 1000, 1400 と log 風に切るのが理解しやすい (人口の対数分布のため)。 D 型色覚 (約 5% 男性) も viridis なら判別可能。SSDSE-B-2026 の 47 都道府県データに対し、 同じ「総人口」を 4 種類のカラースケール (viridis / plasma / jet / Reds) で表示し、 知覚的均等性の差を体感しましょう。
このコードでやること: SSDSE-B-2026 を読み込み、 都道府県別の総人口を 4 種類の cmap で比較プロットする。
📥 入力データ (SSDSE-B-2026 の総人口を上位 5 件抜粋):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import pandas as pd import matplotlib.pyplot as plt import numpy as np df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023] # 最新年の 47 都道府県 d = df.sort_values('A1101', ascending=False) pop = d['A1101'].values labels = d['Prefecture'].values cmaps = ['viridis', 'plasma', 'jet', 'Reds'] fig, axes = plt.subplots(1, 4, figsize=(18, 10)) for ax, cmap in zip(axes, cmaps): norm = plt.Normalize(pop.min(), pop.max()) colors = plt.get_cmap(cmap)(norm(pop)) ax.barh(range(47), pop, color=colors) ax.set_yticks(range(47)) ax.set_yticklabels(labels, fontsize=7) ax.set_title(f'cmap = {cmap}') ax.invert_yaxis() plt.tight_layout() plt.savefig('color_compare.png', dpi=150) |
📤 実行例 (各 cmap での知覚距離テスト出力):
SSDSE-B-2026 の総人口は、 東京都 1408 万人 vs 鳥取県 54 万人と 約 26 倍の幅があります。 線形カラーマッピング $c(v) = \text{cmap}\left(\dfrac{v - v_{\min}}{v_{\max} - v_{\min}}\right)$ では、 中下位の県がほぼ同じ色になり判別不能です。 そこで 対数スケールを導入します:
$$c(v) = \text{cmap}\left(\frac{\log v - \log v_{\min}}{\log v_{\max} - \log v_{\min}}\right)$$
この変換で「2 倍違うか」が一定の色差で表され、 鳥取県 54 万人 vs 島根県 65 万人の差も視覚的に判別できます。 matplotlib では norm=matplotlib.colors.LogNorm() を渡すだけで実装できます。
日本人男性の約 5%、 白人男性の約 8% が何らかの色覚特性を持っています。 教育用・公的データ可視化では、 色覚タイプに依存しない配色 ── Color Universal Design (CUD) が事実上のデフォルトです。
| 色覚タイプ | 弁別が困難な色組 | 推奨パレット |
|---|---|---|
| P 型 (1 型、 赤系錐体欠損) | 赤 ↔ 緑、 オレンジ ↔ 黄緑 | cividis、 viridis、 Okabe-Ito 8 色 |
| D 型 (2 型、 緑系錐体欠損) | 赤 ↔ 緑、 茶色 ↔ 緑 | cividis、 viridis、 BrBG |
| T 型 (3 型、 青系錐体欠損) | 青 ↔ 緑、 黄 ↔ 紫 | cividis (青-黄軸で設計済)、 RdBu |
このコードでやること: matplotlib の colormap を CUD シミュレータで P 型・D 型に変換し、 viridis と jet の見え方の違いを定量化する。
📥 入力データ: SSDSE-B-2026 47 都道府県の総人口 (上記と同じ)。
1 2 3 4 5 6 7 8 9 10 | import matplotlib.pyplot as plt from matplotlib.colors import LinearSegmentedColormap # Okabe-Ito 8 色 (CUD 推奨パレット, qualitative) okabe_ito = ['#E69F00', '#56B4E9', '#009E73', '#F0E442', '#0072B2', '#D55E00', '#CC79A7', '#000000'] for i, color in enumerate(okabe_ito): plt.bar(i, 1, color=color) plt.title('Okabe-Ito 8 色 (CUD 8 色)') plt.savefig('okabe_ito.png', dpi=150) |
📤 実行例 (CUD シミュレータでの判別性スコア):
カラースケールの最も有名な応用が コロプレス地図 (choropleth map) です。 地域を値に応じた色で塗り分け、 一目で空間分布が掴めます。 plotly を使えば SSDSE-B-2026 の県別データをわずか 10 行で描けます。
このコードでやること: SSDSE-B-2026 の総人口を都道府県コードに基づき、 plotly の geojson 地図上にカラースケールで描画する。
📥 入力データ (Prefecture コード「R01000」を都道府県名「北海道」にマッピング):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import pandas as pd import plotly.express as px import json, urllib.request df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df['pref_code'] = df['Code'].str[1:3].astype(int) url = 'https://raw.githubusercontent.com/dataofjapan/land/master/japan.geojson' with urllib.request.urlopen(url) as r: geojson = json.load(r) fig = px.choropleth( df, geojson=geojson, locations='pref_code', featureidkey='properties.id', color='A1101', color_continuous_scale='Viridis', range_color=(df['A1101'].min(), df['A1101'].max()), labels={'A1101': '総人口 (人)'} ) fig.update_geos(fitbounds='locations', visible=False) fig.write_html('japan_pop_viridis.html') |
📤 実行例 (出力ファイルと地図上の色分布):
SSDSE-B-2026 の複数指標 (総人口 A1101・出生数 A4101・65歳以上人口 A1303・年平均気温 B4101) を都道府県 × 指標のヒートマップにして、 カラースケールの選び方が伝わるメッセージにどう影響するか比較します。
このコードでやること: 4 指標を z-score 標準化してから seaborn の heatmap で diverging カラースケール (coolwarm) を使い、 47 都道府県の特徴を一気に俯瞰する。
📥 入力データ (標準化前の生データ抜粋):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import pandas as pd import seaborn as sns import matplotlib.pyplot as plt df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) cols = ['A1101', 'A4101', 'A1303', 'B4101'] sub = df[['Prefecture'] + cols].set_index('Prefecture') # z-score 標準化 (各指標平均0 標準偏差1) z = (sub - sub.mean()) / sub.std() z.columns = ['総人口', '出生数', '高齢人口', '年平均気温'] fig, ax = plt.subplots(figsize=(8, 14)) sns.heatmap(z, cmap='coolwarm', center=0, vmin=-3, vmax=3, annot=True, fmt='.1f', cbar_kws={'label': 'z-score'}, ax=ax) plt.tight_layout() plt.savefig('heatmap_z.png', dpi=150) |
📤 実行例 (主要県の z-score 出力):
A1303 は「高齢者の絶対数」なので東京が赤になる点に注意 (「高齢化率」は別途 A1303/A1101 で計算)。 sequential cmap (viridis) でも描けますが、 「平均より上/下」を即座に判別したい場合 diverging が圧倒的に強力です。
合成データで値を [0,1] の色強度に正規化する。
| i | x_i | (x-min)/(max-min) |
|---|---|---|
| 1 | 20 | 0.000 |
| 2 | 35 | 0.250 |
| 3 | 50 | 0.500 |
| 4 | 65 | 0.750 |
| 5 | 80 | 1.000 |
1 2 3 4 | import numpy as np x = np.array([20, 35, 50, 65, 80]) xn = (x - x.min()) / (x.max() - x.min()) print(f"正規化: {xn}") |
💬 手計算 (Step 2) 0.25 と Python 出力が完全一致。
最小実装の例。 SSDSE のような実データに対して、 まずはコピペで動かしてみるのが理解の早道です。
1 2 3 4 5 6 7 8 | import matplotlib.pyplot as plt, pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023].copy() df['高齢化率'] = df['A1303'] / df['A1101'] * 100 # 65歳以上人口 ÷ 総人口 fig, ax = plt.subplots() sc = ax.scatter(df['A1101'], df['A4101'], c=df['高齢化率'], cmap='viridis') # 総人口×出生数 plt.colorbar(sc, label='高齢化率 (%)') plt.show() |
この用語を使うときに陥りがちな失敗パターン。 経験者ほどここに 1 度はハマっています。
matplotlib.use('pgf') や PDF/X-1a 出力で対処する。連続 → sequential、 中央あり → diverging、 カテゴリ → qualitative。 これを間違えると、 たとえ美しい配色でも誤読を招きます。
viridis、 cividis、 Okabe-Ito を「迷ったらこれ」のデフォルト集として記憶。 jet、 rainbow、 RdYlGn を捨てるだけで読み手の負担が激減します。
色の意味を読み取る手がかりがないと、 どんな良いカラースケールも無意味。 vmin/vmax 固定 + 単位明記 + cmap 名キャプション記載が三点セットです。
この 3 原則を守れば、 学術論文・ビジネスダッシュボード・公的データ可視化、 どの場面でも信頼性の高いカラースケール選択ができます。 SSDSE-B-2026 のような実データを使って、 ぜひ自分の手で複数の cmap を試し比べてみてください。
「カラースケール」を中心に、 連続スケール (viridis/cividis)・カテゴリスケール・発散スケール (RdBu)・jet/rainbow の不適切さ・コントラスト比 4.5:1 の WCAG 基準を結びつけた俯瞰図。 色覚多様性配慮を含めて選択する地図。
上の概念マップは「カラースケール」を中心に、 viridis / cividis (知覚均等で色覚多様性に配慮) と「jet/rainbow は使うな」(視覚誤誘導の典型) を対比した俯瞰図である。 単に綺麗な色を選ぶのではなく、 「順序データには連続スケール、 カテゴリにはカテゴリカルスケール、 ゼロを境に正負があるなら発散スケール」という 3 種の使い分けが本質である。
「カラースケール」は数値 → 色の写像で、 上流のデータ範囲・分布の把握 (例: 歪み) と下流の色覚多様性配慮を組まないと、 平均的読者と色弱読者で別解釈が生まれる事故になる。
上流でデータ範囲とスケール (順序/発散/質的) を判断し、 並列の matplotlib/Plotly cmap を比較し、 下流で color blindness simulator により可読性を検証すれば、 SSDSE 都道府県マップを誰でも正しく読めるよう仕上げられる。
「カラースケール」を実際に使うとき、 何をどう選ぶかを順に判断する。 上から順に答えていくと、 使うべき手法と評価の仕方が決まる。
center=0。 順序の無いカテゴリなら色相で区別する。vmin/vmax を全図で固定する。 固定しないと、 同じ色が図ごとに違う値を意味してしまう。カラースケールは「どの値を基準に、 どちらへ濃くするか」を決める作業。 既定のまま使うと、 データではなくライブラリの都合が図の印象を決めてしまう。
本ページ本文は「viridis か jet か」というパレット(色の質)の議論を厚く扱った。 だが読み手が地図やヒートマップから最初に受け取る印象を決めているのは、 実は色そのものではなく 数値を [0,1] に畳み込む写像関数 のほうだ。 カラースケールは常に二段構えで動く ── まず 値 → [0,1](正規化 normalize)、 次に [0,1] → 色(パレット colormap)。 パレットをどれだけ丁寧に選んでも、 一段目の写像が歪んでいれば、 47 都道府県の大半が同じ色に潰れてしまう。 逆に言えば、 同じ viridis のままでも 写像を線形から対数・分位へ切り替えるだけで、 見える情報量は激変する。 だから批判的な読み手は「何色を使ったか」より先に「どんな関数で値を色に割り当てたか」を疑う。
本文の落とし穴節は「jet の知覚非均等」「階級数の過多」「vcenter=0 の指定漏れ」を扱ったが、 未定量だったのが 右に歪んだ分布を線形正規化したときの色帯の崩壊だ。 SSDSE-B-2026 の A1101 総人口(2023年・47都道府県) で実測すると、 最小の 鳥取県 537 千人 から最大の 東京都 14,086 千人 まで 26.2 倍の開きがある(2 位は神奈川県 9,229、 3 位は大阪府 8,763)。 この列をそのまま線形正規化 (x − min)/(max − min) にかけると、 東京都だけが 1.0 を独占し、 残りは下端に押し潰される。 実測した内訳が次の表だ(すべて data/raw/SSDSE-B-2026.csv からの計算値)。
| 写像関数 | 正規化値<0.1 の県数 | 正規化値<0.2 の県数 | 解釈 |
|---|---|---|---|
線形 (x−min)/(max−min) | 28 / 47 | 37 / 47 | 下位 10% の色帯に半数超が集中し、暗色で判別不能 |
対数 log10(x) を正規化 | 5 / 47 | 12 / 47 | 桁差を吸収し、色が全域に散る |
線形では 47 県中 28 県が色帯の下位 10% に、 37 県が下位 20% に潰れる。 パレットが viridis だろうと jet だろうと、 この 28 県はほぼ同じ暗色になり、 地図上で読み分けられない。 対数正規化に替えるだけで下位 10% は 5 県まで減り、 45 都道府県が識別可能な色差を持つ。 同じ問題は離散化(階級分け)でも起きる。 5 階級のコロプレスを作るとき、 等間隔(equal interval)で切ると階級人数は {最下位:37, 4, 3, 2, 最上位:1} ── 最下位階級に 37 県が押し込まれ、 最上位は東京 1 県のみ。 一方、 分位(quantile)で切れば {10, 9, 9, 9, 10} と各階級ほぼ均等になる(いずれも実測値)。 「どのパレットか」ではなく「どの写像・どの階級境界か」を明示しない図は、 都合の良い結論を無自覚に演出する。 これが色による嘘の最頻出パターンだ。
歪んだ実データに対する写像の選択は、 結局外れ値(この例では東京都)をどう遇するかの問題に帰着する。 実務では次の 3 手を使い分ける。 (1) 対数正規化(LogNorm) ── 桁差そのものが意味を持つ量(人口・所得・面積)に。 東京を含めた全県を 1 枚の連続スケールに収めたいとき。 (2) ロバスト・クリッピング ── 下位 2%・上位 98% パーセンタイルで vmin/vmax を固定し、 東京は「振り切れ(≥上限)」として別色または数値ラベルで併記。 中間層の解像度を最大化したいとき。 (3) 分位・Jenks 離散化 ── 連続色をやめ、 各階級に等人数(または自然分断)で県を割り当てる。 「順位グループ」を伝えたいとき。 いずれの場合も、 凡例に写像方式(linear/log/quantile)と境界値を明記するのが誠実な作法だ。 なお発散(diverging)スケールを「全国平均との差」に使うときは、 この写像問題に加えて中心値の固定(本文の vcenter=0)も同時に効くので、 正規化と中心固定は別々の設定であることを意識したい。
vmin/vmax を共有するか行・列ごとに正規化するかで、 本節と同じ「写像の崩壊」が起きる。