論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説
📚 用語解説
カテゴリ変数
Categorical Variable
基礎統計

🔖 キーワード索引

カテゴリ変数質的変数名義順序エンコーディングOneHot

categorical variable」は統計データ分析の文脈で扱う重要概念のひとつ。 本ページでは「categorical variable」を取り巻く中核キーワードを以下にチップで一覧化する。 各キーワードは関連する概念・手法・道具立てを含み、 文献検索や学習計画の起点になる。

categorical variable統計分析SSDSE-B-2026前提条件適用範囲落とし穴関連手法Python 実装検証方法

これらのキーワードは「categorical variable の理解 → 適用 → 検証」のプロセスを構成する。 各章で詳しく解説する。

💡 30秒で分かる結論

🍰 まずはやさしく

グループ分けをするためのデータです。

データの種類を整理するために使います。

血液型や都道府県などが例です。

この章ではカテゴリ変数の基本を学びます。

カテゴリ変数 ── カテゴリ値を取る変数(質的変数の一種)

📍 文脈 ── どこで出会うか

🍰 まずはやさしく

アンケートなどでよく見るデータです。

正しく数値に変えて分析に使うためです。

スマホの利用状況などの調査で出会います。

定義から実装までの流れを解説します。

質問紙、 アンケート、 顧客属性、 商品分類など、 実データはカテゴリだらけ。 正しく数値化しないと機械学習モデルが動きません。

本ページでは「categorical variable」を扱う。 統計データ分析コンペティション (2026) の教材で、 SSDSE-B-2026 (47 都道府県 × 複数年 × 100 超列) の実データを使った再現可能な学習を目指す。

「categorical variable」は統計・データサイエンスの体系における重要概念のひとつ。 本ページは「定義・直感・数式・実装・落とし穴・関連手法」の 6 視点で構成され、 各視点は独立して読めるが順序通り読むと体系的な理解が得られる。

🎨 直感で掴む

🍰 まずはやさしく

ラベルのようなデータのことです。

計算ができる数値と区別するために使います。

学年や満足度の5段階評価が例です。

数値への変換方法を具体的に学びます。

3種類の量的データ vs カテゴリ変数:

尺度大小比較差の計算
名義血液型, 性別××
順序満足度1-5×
間隔温度(℃)○(比は×)
比例身長, 所得

もう一つの直感は「データ型は数値の見た目より、 数値演算が意味を持つかで決まる」。 都道府県コード "13" (東京) と "27" (大阪) は数字に見えても、 13+27=40 や (13+27)/2=20 (山梨県) という演算は無意味。 これがカテゴリカル変数で、 Pandas では dtype=object/category、 R では factor として扱う。 一方、 人口 1000 人と 2000 人なら和も平均も意味があり、 これが数値変数。

本ページではカテゴリカル変数を、 名義 (順序なし: 都道府県、 性別) と 順序 (順序あり: 満足度 1-5、 学年) の 2 タイプに分け、 (1) データ型の宣言、 (2) 集計と可視化、 (3) エンコーディング (one-hot、 ordinal、 target)、 (4) モデル投入の 4 段階で具体化する。 数値変数とは要約統計量 (平均ではなく最頻値)、 可視化 (棒グラフ vs ヒストグラム)、 モデル投入方法のすべてが異なる。

具体例として、 SSDSE-B-2026 の「都道府県」(47 水準の名義)、 「地方区分」(8 水準の名義)、 「人口規模区分」(3 水準の順序) を pandas.Categorical で扱い、 .value_counts() による集計、 sns.countplot による可視化、 pd.get_dummies による one-hot 化、 ordinal_encoder による順序エンコードを次節以降に示す。 「都道府県コードを int として学習器に投入」が招くバグも、 実例で示す。

🎮 触って理解する

カテゴリ変数の 度数分布 を、 スライダー、 または棒グラフの棒を直接ドラッグ(タッチ対応)して動かそう。 最頻値・度数・割合 がリアルタイムで更新される。 名義尺度(順序なし=血液型)と 順序尺度(順序あり=満足度)を切り替えると、 「許される集計・操作」がどう変わるかを体感できる。

可視化:
棒グラフでは棒を上下にドラッグして度数を変えられます

🧠 直感:カテゴリで「分類」する変数

カテゴリ変数は値そのものが「仲間分けのラベル」。 上の表で分かるとおり、 意味を持つ集計は 各カテゴリに何件あるか(度数)全体に占める割合、 そして いちばん多いカテゴリ(最頻値) だ。 名義尺度でも順序尺度でも、 この 3 つは常に計算できる。 詳しくは 名義尺度順序尺度最頻値 を参照。

⚠️ よくある落とし穴:名義尺度を数値扱いして平均を取る

血液型に A=1, B=2, O=3, AB=4 と番号を振って平均すると「平均血液型 2.3」という無意味な値が出る。 上のツールで 名義尺度 に切り替えると、 中央値・平均は「定義できない」 と表示されるのはこのためだ。 順序尺度(満足度)では 中央値 は取れるが、 目盛りの間隔が等しい保証はないので「順位の平均」もあくまで参考値。 可視化でも注意が必要で、 順序尺度を 円グラフ にすると順序情報が読み取りにくくなる(順序尺度は 棒グラフ 推奨)。

🚀 発展:エンコーディングとカイ二乗検定

機械学習にカテゴリ変数を投入するには数値化(エンコーディング)が要る。 名義尺度は順序を作らない One-Hot エンコーディング、 順序尺度は順位を保つ Ordinal エンコーディングが基本。 また 2 つのカテゴリ変数の関連は度数のクロス表に対する カイ二乗検定 や Cramér V で評価する(本ページ下部の数式節を参照)。 いずれも出発点は、 このツールで見た「カテゴリごとの度数」だ。

📐 定義/数式

🍰 まずはやさしく

文字を数字に置き換えるルールです。

コンピュータが計算できるようにするためです。

好きな色を0と1の列で表します。

数値化する数式や手法について読みます。

OneHotエンコーディング(K個のカテゴリ → K個の0/1列):

【OneHot 例】
色 = "赤" → [1, 0, 0]
色 = "青" → [0, 1, 0]
色 = "緑" → [0, 0, 1]

回帰では K-1個 に減らす(ダミー変数の罠回避)。 sklearn なら drop='first'

📐 エンコーディングの数式と Cramer's V

① OneHot Encoding

カテゴリ変数 $X \in \{c_1, c_2, \dots, c_K\}$ に対して、 $K$ 次元のインジケータベクトル:

$$ \mathbf{e}(X=c_k) = (0, \dots, 1, \dots, 0)^\top \quad (k\text{ 番目だけ }1) $$

線形回帰では多重共線性回避のため 1 列を基準カテゴリとして落とす:

$$ \mathbf{e}^{\text{drop}}(X=c_k) = (0, \dots, 1, \dots, 0)^\top \in \{0,1\}^{K-1} $$

② Target (Mean) Encoding

目的変数 $Y$ をカテゴリごとに平均で置換:

$$ \phi_{\text{target}}(c_k) = \frac{1}{|S_k|} \sum_{i \in S_k} y_i, \quad S_k = \{i : x_i = c_k\} $$

小サンプルカテゴリには 平滑化(smoothing)を適用:

$$ \phi_{\text{smooth}}(c_k) = \frac{|S_k|\,\bar{y}_k + m\,\bar{y}}{|S_k| + m} $$

$m$ はハイパーパラメータ(疑似サンプル数)。 $|S_k|$ が小さいと全体平均 $\bar{y}$ に強く引きつけられ、 過学習を抑制する。

③ Cramer's V — カテゴリ変数同士の関連強度

2 つのカテゴリ変数 $X, Y$ のクロス表に対する $\chi^2$ 統計量から:

$$ V = \sqrt{\frac{\chi^2}{n \cdot \min(r-1, c-1)}} $$

$r, c$ はクロス表の行数・列数、 $n$ はサンプル数。 $V \in [0, 1]$ で 0 が独立、 1 が完全関連。 相関係数のカテゴリ版と考えればよい。

④ 数式を言葉で読み解く

OneHot $\mathbf{e}$
「このサンプルは $k$ 番目のカテゴリです」を $K$ 個のスイッチで表す
Target $\phi$
「このカテゴリに属するサンプルの目的変数平均は X 円」と置き換える
平滑化 $m$
「1 件しかないカテゴリの平均は信用しないで全体平均に寄せる」を制御するパラメータ
Cramer's V
$\chi^2$ をサンプル数とテーブル形状で正規化し、 「強さ」が 0-1 に収まるようにした指標

📐 補足: カテゴリ変数の数式を言葉で読み解く — エントロピーと連関係数

カテゴリ変数の「ばらつき」は分散ではなく エントロピーGini 不純度 で測る。 また 2 つのカテゴリ変数の関連は Cramér V で評価する。

数式

$$ H(X) = -\sum_{i=1}^{k} p_i \log_2 p_i \quad,\quad V = \sqrt{\frac{\chi^2/n}{\min(r-1, c-1)}} $$

🔬 数式を言葉で読み解く

🧮 実値で計算 — SSDSE-B-2026 都道府県を地域区分でカテゴリ化

47 都道府県を 8 地域 (北海道 / 東北 / 関東 / 中部 / 近畿 / 中国 / 四国 / 九州) に分けると、内訳は北海道 1, 東北 6, 関東 7, 中部 9, 近畿 7, 中国 5, 四国 4, 九州 8。エントロピーは次の通り。

H = -(1/47·log2(1/47) + 6/47·log2(6/47) + 7/47·log2(7/47) + 9/47·log2(9/47) + 7/47·log2(7/47) + 5/47·log2(5/47) + 4/47·log2(4/47) + 8/47·log2(8/47)) = 2.853 bits 最大エントロピー log2(8) = 3.000 bits 均等度 = 2.853 / 3.000 = 0.951 (ほぼ均等)

🐍 Python 実装 — SSDSE-B-2026 で地域カテゴリのエントロピーと Cramér V を計算

🎯 このコードでやること: SSDSE-B-2026 を地域区分でカテゴリ化し、 エントロピーと、 「人口規模カテゴリ × 地域」の連関を Cramér V で評価する。

📥 入力データ (SSDSE-B-2026 抜粋):

Code Prefecture A1101(総人口, 2023) R01000 北海道 5092000 R13000 東京都 14086000 R27000 大阪府 8763000 ... ... ...
 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
import pandas as pd
import numpy as np
from scipy.stats import chi2_contingency

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023].copy()   # 2023 年・47 都道府県

# 地域コード (R01000〜R47000) から 8 地域ブロックを導出
code = df['Code'].str[1:3].astype(int)
df['region'] = pd.cut(code,
                      bins=[0, 1, 7, 14, 23, 30, 35, 39, 47],
                      labels=['北海道','東北','関東','中部','近畿','中国','四国','九州沖縄'])

# エントロピー
p = df['region'].value_counts(normalize=True)
H = -(p * np.log2(p)).sum()
print(f'エントロピー H = {H:.3f} bits')

# 人口カテゴリ × 地域 で Cramér V
df['pop_cat'] = pd.qcut(df['A1101'], 3, labels=['小','中','大'])
tbl = pd.crosstab(df['pop_cat'], df['region'])
chi2, p_val, _, _ = chi2_contingency(tbl)
n = tbl.sum().sum()
V = np.sqrt(chi2 / (n * (min(tbl.shape)-1)))
print(f'Cramér V = {V:.3f}, p = {p_val:.4f}')

📤 実行例:

エントロピー H = 2.853 bits Cramér V = 0.425, p = 0.2563

💬 結果の読み方: エントロピー 2.853 bits は理論最大 3.000 にほぼ近く、 地域分布は比較的均等(手計算節の値とも一致)。 Cramér V=0.425 は中程度の関連の目安に相当し、 クロス表では関東に「大」が集中する傾向が見えるが、 p=0.256 のため n=47 では統計的有意には届かない(期待度数 5 未満のセルが多く検出力不足。 Fisher の正確検定やシミュレーション p 値での再確認が望ましい)。

⚠️ カテゴリ変数固有の落とし穴

🖼 補足: カテゴリ変数を「数値化する 4 つの戦略」

機械学習モデルの多くは数値入力しか受け付けないため、 カテゴリ変数を数値化(エンコーディング)する必要 がある。 しかし戦略の選び方を誤ると「順序のない名義変数に大小関係を学習させる」「次元爆発でモデルが過学習する」などの問題が生じる。 ここでは 3 枚の図と Python 実装で典型的なエンコーディングの効果を可視化する。

図 1: 棒グラフでカテゴリ別頻度を確認(hist_basic.png)

エンコーディング前に必ず「カテゴリごとの頻度分布」を確認する。 頻度が極端に偏っている場合(例: 47 都道府県中 1 件しか出ないカテゴリ)はレアカテゴリ統合・統計的安定化が必要。 棒グラフを描くだけで頻度バランスは一目瞭然になる。

カテゴリ頻度分布

図 2: 散布図でターゲットとの関係を可視化(scatter_basic.png)

Target Encoding を採用する場合、 各カテゴリの平均ターゲット値を縦軸に、 サンプル数を横軸にとった散布図で「サンプル数の少ないカテゴリは平均が極端な値になりがち」を確認できる。 ベイズ的スムージング(事前分布で正則化)の必要性を直感する。

ターゲット平均散布図

図 3: 箱ひげ図でカテゴリ別ターゲット分布(box_multigroup.png)

カテゴリ別に箱ひげ図を並べることで「カテゴリ間でターゲットの分散が大きく異なるか(異分散)」を確認できる。 異分散の場合は標準的な ANOVA や OLS の仮定が崩れるため、 ロバスト推定や Welch's ANOVA に切り替える判断材料となる。

カテゴリ別箱ひげ図

📊 表: 主要なカテゴリ変数エンコーディング戦略

手法次元適用場面弱点
One-HotK-1名義変数・K が小さい高カーディナリティで次元爆発
Label1順序変数・木モデル入力線形モデルで誤解釈
Target Encoding1高カーディナリティリークの危険
Ordinal1順序が明確等間隔仮定
Hash Encoding固定 d超高カーディナリティ衝突あり
Embeddingd (学習)深層学習サンプル多必要

🐍 Python 実装: SSDSE-B-2026 都道府県を OneHot と Target Encoding

このコードでやること: SSDSE-B-2026 の 47 都道府県を 8 地方ブロックに集約してカテゴリ変数とし、 OneHot と Target Encoding (平均総人口 A1101)を計算する。

📥 入力データ (SSDSE-B-2026 抜粋・2023 年):

SSDSE-B-2026 Code 都道府県 地方ブロック 総人口(A1101) 2023 R01000 北海道 北海道 5,092,000 2023 R02000 青森県 東北 1,184,000 2023 R13000 東京都 関東 14,086,000 2023 R47000 沖縄県 九州沖縄 1,468,000
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import 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()

# 都道府県コード(R01000→01 … R47000→47)から8地方ブロックを導出
code = df['Code'].str[1:3].astype(int)
bins   = [0, 1, 7, 14, 23, 30, 35, 39, 47]
labels = ['北海道', '東北', '関東', '中部', '近畿', '中国', '四国', '九州沖縄']
df['region'] = pd.cut(code, bins=bins, labels=labels)

# One-Hot エンコーディング(K=8 → 8列)
onehot = pd.get_dummies(df['region'], prefix='region').astype(int)
print('One-Hot 列:', list(onehot.columns))

# Target Encoding(地方ブロックごとの平均総人口 A1101)
te = df.groupby('region', observed=True)['A1101'].mean()
print(te.round(0).astype(int))

📤 実行すると次の出力が得られる:

One-Hot 列: ['region_北海道', 'region_東北', 'region_関東', 'region_中部', 'region_近畿', 'region_中国', 'region_四国', 'region_九州沖縄'] region 北海道 5092000 東北 1386333 関東 6218143 中部 2305444 近畿 3141429 中国 1414000 四国 894500 九州沖縄 1753625 Name: A1101, dtype: int64

💬 One-Hot は K=8(8 地方ブロック)→ 8 列に展開(次元増加)するが情報損失なし。 Target Encoding は 1 列に圧縮できる代わりに「平均総人口」という統計量に置き換わるため、 リーク防止のための K-fold target encoding が必須。 SSDSE のような小標本(n=47)では地方ブロック集約 + One-Hot を優先するのが安全。

✅ 理解度チェック (カテゴリ変数)

  1. 名義尺度・順序尺度・間隔尺度・比例尺度の違いを 1 行で言えますか? また、 平均値を計算してよい尺度はどれですか?
  2. One-Hot Encoding と Label Encoding を線形回帰モデルに投入したときの結果が異なる理由は?
  3. Target Encoding で「データリーク(leakage)」が発生する仕組みと、 それを防ぐ K-fold target encoding の手順は?
  4. SSDSE-B-2026 の都道府県を One-Hot した場合、 多重共線性(ダミー変数の罠)を避けるには何列をドロップすべきですか?
  5. カーディナリティ K=1000 のカテゴリ変数を機械学習モデルに入れたい場合、 どのエンコーディングが第一候補ですか? 理由とともに。

⚠️ カテゴリ変数を実務で使うときの落とし穴 (3 件)

📜 カテゴリ変数の理論的背景: スティーブンスの尺度水準

カテゴリ変数の概念は 1946 年に心理学者 S. S. Stevens が論文 "On the Theory of Scales of Measurement" で体系化した「尺度水準(scale of measurement)」に深く関連する。 Stevens は測定スケールを 4 段階に分類した: (1) 名義尺度(nominal scale): 単にラベル付けされた区別。 例: 血液型 {A, B, O, AB}、 都道府県名。 平均や順位の計算は無意味で、 計算できるのは度数とモードのみ。 (2) 順序尺度(ordinal scale): 順序はあるが間隔は不明。 例: 学年 {小, 中, 高, 大}、 顧客満足度 {1=非常に不満, 2=不満, 3=普通, 4=満足, 5=非常に満足}。 中央値・順位相関(スピアマン)は計算可だが平均値は厳密には不適切。 (3) 間隔尺度(interval scale): 等間隔だが絶対零点なし。 例: 摂氏温度(10℃ と 20℃ の差 = 20℃ と 30℃ の差だが、 「20℃ は 10℃ の 2 倍暑い」とは言えない)、 西暦(西暦 1000 年と 2000 年の差はあるが、 「西暦 2000 年は西暦 1000 年の 2 倍」ではない)。 平均・標準偏差は計算可。 (4) 比例尺度(ratio scale): 絶対零点あり。 例: 身長、 体重、 年収、 都道府県人口。 平均・標準偏差・比率すべて計算可。 カテゴリ変数(質的変数)は厳密には (1)+(2) を指すが、 機械学習の文脈では (1) を「カテゴリ変数 / 名義変数」、 (2) を「順序変数」と呼び分けることが多い。

🧪 高カーディナリティ変数への実践的アプローチ

カテゴリ変数のカーディナリティ(取りうる値の種類数)が大きいケース(K ≥ 100)への対応策: (1) Frequency Encoding: 各カテゴリを「そのカテゴリの出現頻度」で置換する。 シンプルでリーク発生しにくい。 (2) Target Encoding with smoothing: ベイズ的平滑化で (n_cat·mean_cat + α·mean_global) / (n_cat + α) を使い、 サンプル数の少ないカテゴリは全体平均に引き戻す。 α は通常 10-20 程度。 (3) K-fold target encoding: K-fold で「自分のサンプル以外」の平均で encoding し、 リークを防ぐ。 K=5 が標準。 (4) Hash Encoding: ハッシュ関数で固定次元 d(例: 64)に圧縮。 大規模データで高速だが衝突あり。 (5) Entity Embedding: NN の埋め込み層で d 次元(例: 8-64)に学習。 サンプル数が多い場合に有効。 (6) CatBoost の Ordered Target Encoding: 訓練データの順序を活用した自動 encoding。 リークを構造的に避ける。 SSDSE-B-2026 のような小標本(n=47)では Target Encoding は過学習リスクが高く、 One-Hot か Frequency Encoding を優先する判断が一般的である。

💡 SSDSE-B-2026 を用いたカテゴリ変数分析のケーススタディ

SSDSE-B-2026 では「都道府県」「地方ブロック」などの典型的なカテゴリ変数が含まれており、 様々なエンコーディング戦略を比較するのに適した教材データである。 たとえば「都道府県(47 カテゴリ)」を直接 One-Hot Encoding すると、 列数が 47 になり、 n=47 のデータでは完全な多重共線性が生じてしまう。 そのため (1) 「地方ブロック(8 区分: 北海道・東北・関東・中部・近畿・中国・四国・九州・沖縄)」に集約して One-Hot Encoding する、 (2) Frequency Encoding で「同じ地方の県の数」を特徴量化する、 (3) 総人口(A1101)で Target Encoding をかける(K-fold で慎重に)、 などの選択肢が考えられる。 各戦略の長所と短所を実データで比較すると、 (a) 地方ブロック集約は最も解釈しやすく、 多重共線性も避けられる、 (b) Frequency Encoding は単純だが情報量が少ない、 (c) Target Encoding はリーク防止が必須だが予測性能は高い、 という結論が得られる。 こうした比較を実装し可視化することで、 学習者は「カテゴリ変数のエンコーディングはモデル性能の鍵を握る」という実感を得られる。 さらに「血液型」「学歴」「業種」など SSDSE 以外のデータでも、 同じ戦略を応用できるよう汎化的に学ぶことが推奨される。

🔬 カテゴリ変数の統計検定手法

カテゴリ変数を分析する統計検定手法: (1) χ² 検定(カイ二乗検定): 2 つのカテゴリ変数の独立性を検定。 期待度数 ≥ 5 が前提。 SSDSE-B-2026 で「地方ブロックと産業特化の独立性」を検定するときに使う。 (2) Fisher の正確検定: 期待度数 < 5 のセルがある小さなクロス表で使う。 計算量は大きいが正確な p 値が得られる。 (3) マクネマー検定: 対応のある二値カテゴリの変化を検定。 「政策実施前後の支持率変化」など。 (4) Cochran-Mantel-Haenszel 検定: 層別したクロス表の独立性検定。 「年齢層別に職業と所得の関係」を分析するときに使う。 (5) 多項検定 / G 検定(尤度比検定): χ² の代替で、 期待度数が小さくても比較的頑健。 (6) 残差分析: χ² 検定で有意になったあと、 どのセルが期待値から大きく外れているかを調べる。 標準化残差の絶対値が 2 以上のセルが有意。 (7) Cramér の V: カテゴリ変数間の関連の強さを 0〜1 で表現する効果量。 これらの検定は SciPy(scipy.stats.chi2_contingency, fisher_exact, mcnemar)や R で実装されており、 SSDSE のような小標本でも使いやすい。

🌐 カテゴリ変数の前処理パイプライン設計

機械学習プロジェクトでカテゴリ変数を扱うときの標準的な前処理パイプライン: (1) EDA フェーズ: 全カテゴリ変数のカーディナリティ、 各カテゴリの出現頻度、 欠損率、 ターゲットとの関連性を確認する。 棒グラフ・箱ひげ図・モザイクプロットを多用。 (2) 欠損処理: 欠損率に応じて Unknown 化 / 最頻値置換 / 列削除を判断。 (3) レアカテゴリ集約: 出現数が閾値(例: 30)未満のカテゴリを「Other」に統合。 (4) エンコーディング選択: モデルの種類とカーディナリティに応じて One-Hot / Target / Frequency / Hash / Embedding を選択。 (5) リーク防止: Target Encoding を使う場合は必ず K-fold で計算。 訓練データ全体で計算すると Validation データへのリークが発生する。 (6) パイプライン化: scikit-learn の ColumnTransformer や Pipeline で前処理をモデルに組み込み、 訓練時とテスト時で同じ前処理が適用されるよう設計する。 (7) 新カテゴリ対応: 本番運用では訓練時に見なかった新カテゴリが現れる可能性を想定し、 OneHotEncoder の handle_unknown='ignore' などの設定を必ず使う。 (8) ドリフト監視: 本番でカテゴリ分布が変わっていないかを定期的にチェックし、 変化が大きければ再訓練を実施する。 これらを系統的に実装することで、 カテゴリ変数によるモデル性能の劣化を最小化できる。

📋 カテゴリ変数の可視化技法

カテゴリ変数の可視化には用途に応じた多様な手法がある。 (1) 棒グラフ(Bar chart): 最も基本的。 カテゴリ別の度数や平均値を表示する。 横棒グラフはカテゴリ名が長いときに使う。 (2) 積み上げ棒グラフ(Stacked bar): 2 つのカテゴリ変数の関係を 1 グラフで表現。 構成比に着目する場合は 100% 積み上げが便利。 (3) モザイクプロット(Mosaic plot): クロス表を面積で可視化。 セル面積が頻度に比例し、 χ² 検定の残差分析を色で重ねることもある。 (4) 箱ひげ図(Box plot): カテゴリ別に数値変数の分布を比較。 中央値・四分位範囲・外れ値が一目で分かる。 (5) バイオリンプロット(Violin plot): 箱ひげ図に密度推定を組み合わせ。 分布の形状まで見える。 (6) スウォームプロット(Swarm plot): 個別のデータ点をカテゴリ別に並べる。 サンプル数が少ない場合に有効。 (7) ヒートマップ: クロス表を色濃度で可視化。 大きなカテゴリ × カテゴリ表に向く。 (8) サンキーダイアグラム(Sankey diagram): カテゴリ間の遷移を流れとして表現。 顧客行動分析や教育パスウェイ分析に使う。 これらを使い分けることで、 カテゴリ変数の構造を多面的に把握できる。 一般に「カテゴリ数が少ない(≤5)→ 棒・箱ひげ・バイオリン」「カテゴリ数が多い(≥10)→ ヒートマップ・サンキー」と覚えるとよい。

🎓 教育的視点: カテゴリ変数学習のステップ

カテゴリ変数を体系的に学ぶための教育的なステップ: (1) まず尺度水準(名義 / 順序 / 間隔 / 比例)の違いを理解し、 「どの尺度で何が計算できるか」を整理する。 たとえば名義尺度で平均を計算すると無意味、 順序尺度で平均は不適切(中央値が無難)、 という基本ルールを身につける。 (2) 次に基本的な統計量(度数、 モード、 構成比、 累積構成比)の計算と解釈を練習する。 SSDSE-B-2026 で都道府県別の人口・面積の度数分布を計算するなど。 (3) χ² 検定や Fisher 検定など、 カテゴリ変数に特化した統計検定の使い方を学ぶ。 仮説の立て方、 期待度数の計算、 残差分析の解釈まで一通り体験する。 (4) 機械学習に進む前に、 ロジスティック回帰のようなカテゴリ予測モデルでカテゴリ変数を扱う場面を経験する。 (5) その後 One-Hot Encoding、 Target Encoding、 Embedding などの実装的な側面を学ぶ。 (6) 最後に本番運用での課題(新カテゴリ、 ドリフト、 リーク)を実践プロジェクトで扱う。 この段階的な学習設計により、 単なる「データ加工テクニック」ではなく「カテゴリデータの数理的・統計的・実装的な理解」が身につく。 SSDSE-B-2026 はこのプロセスの全段階に対応できる豊富な教材を含むため、 教育用途に適している。

🔧 実務上のチェックリスト

カテゴリ変数を扱う実務プロジェクトで毎回確認すべきチェックリスト: (1) すべてのカテゴリ変数のカーディナリティ(取りうる値の種類数)をリストアップしたか? K=2 から K=10000+ まで様々なケースがあり、 それぞれ最適なエンコーディングが異なる。 (2) 各カテゴリの出現頻度を確認し、 上位 5 カテゴリで全体の何 % をカバーするか把握したか? パレートの 80-20 則が成立するなら下位カテゴリは「Other」に集約してもよい場合が多い。 (3) 欠損率を確認し、 欠損自体が予測に重要な情報を含むかを検討したか? MCAR / MAR / MNAR の判別が重要。 (4) ターゲット変数との関連性を可視化し、 「特に予測力のあるカテゴリ」「予測に貢献しないカテゴリ」を特定したか? (5) Target Encoding を使う場合、 K-fold で実装してリークを防いだか? (6) One-Hot Encoding を使う場合、 多重共線性(ダミー変数の罠)に注意したか? (7) 訓練データとテストデータでカテゴリの集合が一致するかを確認したか? (8) 本番運用での新カテゴリ出現に対応する仕組みを設計したか? こうしたチェックリストを毎回踏むことで、 カテゴリ変数の処理ミスによるモデル性能の劣化を最小化できる。

🔬 数式を言葉で読み解く

名義尺度
大小なし。 例:地域、 性別、 ジャンル
順序尺度
大小あり、 間隔不明。 例:金銀銅、 学年
OneHot
カテゴリ毎に0/1列を作る
Label Encoding
0,1,2,…と整数化。 順序ありに使う
Target Encoding
カテゴリ毎に目的変数の平均で置換。 リーケージ注意

🔬 主要エンコーダの比較表

エンコーダ 出力次元 情報損失 適性 代表ライブラリ
OneHotK-1なし低 K, 順序なしsklearn, pandas
Label / Ordinal1順序情報のみ保持順序あり, 木モデルsklearn, category_encoders
Target (Mean)1y との相関のみ保持高 K, 強力category_encoders
Frequency1出現頻度のみ高 K, 安全手書きで一瞬
Hashing指定 d衝突あり超高 K, 高速sklearn FeatureHasher
Embeddingd (8-64)学習で復元深層学習PyTorch/Keras Embedding
CatBoost内部自動最小何でもcatboost (cat_features=)

🔄 SSDSE-B-2026 でカテゴリ → 回帰モデルまでフルパイプライン

🎯 このコードでやること: SSDSE-B-2026 で「都道府県(カテゴリ)+ 年平均気温(連続)」から「合計特殊出生率」を予測する線形モデルを作り、 OneHot 適用→ pipeline → 学習 → 係数解釈までを一気通貫で実施。

📥 入力: SSDSE-B-2026 の 2023 年 47 行、 列 = Prefecture, B4101 (年平均気温), A4103 (合計特殊出生率)。

 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
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023].dropna(subset=['B4101', 'A4103']).copy()

X = df[['Prefecture', 'B4101']]   # カテゴリ + 連続
y = df['A4103']                # 合計特殊出生率

pre = ColumnTransformer([
    ('cat', OneHotEncoder(drop='first', sparse_output=False), ['Prefecture']),
    ('num', 'passthrough', ['B4101']),
])
model = Pipeline([('pre', pre), ('reg', LinearRegression())])
model.fit(X, y)

# 「年平均気温」の係数だけ取り出して解釈
beta_temp = model.named_steps['reg'].coef_[-1]
intercept = model.named_steps['reg'].intercept_
print(f'年平均気温 1℃上昇あたり 出生率 +{beta_temp:.4f}')
print(f'切片(基準=三重県、 気温0℃): {intercept:.4f}')
print(f'訓練 R^2: {model.score(X, y):.3f}')

📤 実行結果(参考値):

年平均気温 1℃上昇あたり 出生率 +0.0295 切片(基準=三重県、 気温0℃): 0.7766 訓練 R^2: 1.000

💬 結果の読み方: R^2 = 1.000 は過学習の典型 ── 47 行に対して 46 + 1 = 47 個の特徴量で「完全フィット」しているだけ。 これは p ≧ n 問題と呼ばれ、 サンプル数が特徴量数を下回ると線形回帰は形式的に正解できてしまう。 実用では (1) Ridge / Lasso などの正則化、 (2) 地域ブロック集約、 (3) Target Encoding、 (4) 階層モデル(県をランダム効果として扱う)が必要。 気温係数 +0.0295/℃ は「気温が高いと出生率がわずかに高い」を示すが、 共線な県ダミーが多いため独立した解釈は危険。 なお OneHotEncoder(drop='first') が落とす基準カテゴリは文字コード順で先頭の「三重県」である点にも注意。

🧪 もうひとつの実値ケース:複数カテゴリ列の同時 OneHot

SSDSE-B-2026 から「都道府県 × 高齢化レベル × 出生率レベル」3 つのカテゴリを同時 OneHot 化する場合のメモリ・列数を実測する。 これにより、 多変量カテゴリを安易に OneHot すると 列数が掛け算で増える ことを体感できる。

🎯 このコードでやること: 3 つのカテゴリ列を作成し、 個別 OneHot と交互作用 OneHot の列数・メモリを比較する。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) A4103(合計特殊出生率) 北海道 5,092,000 1,681,000 1.06 東京都 14,086,000 3,205,000 0.99 沖縄県 1,468,000 350,000 1.6 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import 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
df['高齢化レベル'] = pd.qcut(df['高齢化率'], 3, labels=['低','中','高'])
df['出生率レベル'] = pd.qcut(df['A4103'],    3, labels=['低','中','高'])

# ① 個別 OneHot:列数は K_pref + K_age + K_birth - 3
d1 = pd.get_dummies(df[['Prefecture','高齢化レベル','出生率レベル']], drop_first=True)
print(f'個別 OneHot: 列数 {d1.shape[1]}')
print(f'メモリ: {d1.memory_usage(deep=True).sum()/1024:.1f} KB')

# ② 交互作用 OneHot(連結カテゴリ):列数は最大 K_pref × K_age × K_birth
df['concat_cat'] = (df['Prefecture'].astype(str) + '_' +
                     df['高齢化レベル'].astype(str) + '_' +
                     df['出生率レベル'].astype(str))
d2 = pd.get_dummies(df['concat_cat'], drop_first=True)
print(f'交互作用 OneHot: 列数 {d2.shape[1]}')
print(f'メモリ: {d2.memory_usage(deep=True).sum()/1024:.1f} KB')

📤 実行結果(参考値):

個別 OneHot: 列数 50 メモリ: 2.7 KB 交互作用 OneHot: 列数 46 メモリ: 2.5 KB

💬 結果の読み方: SSDSE-B-2026 の 47 行では「都道府県 × 高齢化 × 出生率」の組み合わせは実質 47 種(各県につき 1 通り)なので、 交互作用列数 = 46 と個別 OneHot 列数 50 は近接。 一方で「全国 1700 市町村 × 6 業種 × 4 年代」のような大規模データでは個別 OneHot が 1700+6+4-3=1707 列に対し、 交互作用 OneHot は 1700×6×4=40800 列に膨れる。 サンプル数を超える特徴量を作る前に、 必要性を吟味すべし。

📊 カテゴリ別ストラティファイド CV — 分割の正しいやり方

「クラス分類でカテゴリ目的変数が不均衡な時」「カテゴリ特徴量がクロス間で偏ると検証精度がブレる時」には StratifiedKFold か GroupKFold を使う。 SSDSE-B-2026 で 47 都道府県を 5 fold 分割するとき、 偶然 4 都市集中の県だけが test に偏らない工夫が必要。

🎯 このコードでやること: 高齢化レベル(3 階級)で stratify した KFold と、 都道府県(47 グループ)を group とした GroupKFold を比較する。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) 北海道 5,092,000 1,681,000 東京都 14,086,000 3,205,000 沖縄県 1,468,000 350,000 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import pandas as pd
from sklearn.model_selection import StratifiedKFold, GroupKFold, KFold

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
df['高齢化レベル'] = pd.qcut(df['高齢化率'], 3, labels=['低','中','高'])

# ① 通常 KFold(カテゴリ無視)
kf = KFold(n_splits=5, shuffle=True, random_state=42)
for i, (tr, va) in enumerate(kf.split(df)):
    print(f'KFold {i}: test 内 高齢化分布 = {df.iloc[va]["高齢化レベル"].value_counts().to_dict()}')

# ② StratifiedKFold(高齢化レベルで層化)
sk = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
for i, (tr, va) in enumerate(sk.split(df, df['高齢化レベル'])):
    print(f'Stratified {i}: test 内 高齢化分布 = {df.iloc[va]["高齢化レベル"].value_counts().to_dict()}')

📤 実行結果(参考値):

KFold 0: test 内 高齢化分布 = {'低': 6, '中': 2, '高': 2} KFold 1: test 内 高齢化分布 = {'中': 5, '低': 4, '高': 1} KFold 2: test 内 高齢化分布 = {'高': 5, '低': 2, '中': 2} KFold 3: test 内 高齢化分布 = {'高': 6, '中': 2, '低': 1} KFold 4: test 内 高齢化分布 = {'中': 4, '低': 3, '高': 2} Stratified 0: test 内 高齢化分布 = {'高': 4, '低': 3, '中': 3} Stratified 1: test 内 高齢化分布 = {'低': 4, '中': 3, '高': 3} Stratified 2: test 内 高齢化分布 = {'低': 3, '中': 3, '高': 3} Stratified 3: test 内 高齢化分布 = {'低': 3, '中': 3, '高': 3} Stratified 4: test 内 高齢化分布 = {'低': 3, '中': 3, '高': 3}

💬 結果の読み方: 通常 KFold では fold 0 が「低 6 対 高 2」、 fold 3 が「高 6 対 低 1」と偏り、 seed によっては特定階級が test に 1 件も入らない fold も起こり得る。 StratifiedKFold は各 fold で 3 階級がほぼ均等(3〜4 件ずつ)に含まれ、 評価が安定する。 都道府県でクラスタリングしたい場合は GroupKFold(同じ県を train/test に分ける)。

🚀 CatBoost にカテゴリをそのまま投げる

CatBoost は「カテゴリ列を文字列のまま受け取り、 内部で Ordered Target Encoding を実行する」設計。 SSDSE-B-2026 で都道府県をエンコードせずそのまま投げて出生率を予測する例。

🎯 このコードでやること: cat_features パラメータで Prefecture を明示し、 自前 OneHot/Label せずに CatBoost で予測する。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A4103(合計特殊出生率) B4101(年平均気温) 北海道 1.06 11.0 東京都 0.99 17.6 沖縄県 1.6 23.8 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import pandas as pd
from catboost import CatBoostRegressor
from sklearn.model_selection import KFold

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023].dropna(subset=['B4101','A4103']).copy()

X = df[['Prefecture', 'B4101']].copy()  # カテゴリ列を文字列で渡す
y = df['A4103']

cv_scores = []
for tr, va in KFold(n_splits=5, shuffle=True, random_state=42).split(X):
    model = CatBoostRegressor(
        iterations=200, depth=4, learning_rate=0.1,
        cat_features=['Prefecture'],  # これだけ
        verbose=0
    )
    model.fit(X.iloc[tr], y.iloc[tr])
    cv_scores.append(model.score(X.iloc[va], y.iloc[va]))
print(f'CatBoost 5-fold CV R^2 平均: {sum(cv_scores)/len(cv_scores):.3f}')
print(f'各 fold スコア: {[f"{s:.3f}" for s in cv_scores]}')

📤 実行結果(参考値):

CatBoost 5-fold CV R^2 平均: 0.412 各 fold スコア: ['0.380', '0.526', '0.358', '0.401', '0.395']

💬 結果の読み方: CatBoost はカテゴリ列を encoder なしで受け取り、 内部で Ordered TS を使って自動エンコード。 R^2 = 0.412 は「気温と都道府県だけで出生率の 41% を説明」── 線形回帰の過学習 R^2 = 1.0 に対し、 CV で測ると現実的な値が出る。 これがちゃんとした「汎化性能の評価」。

🧮 実値で計算してみる

都道府県(47カテゴリ)の扱い方:

状況により使い分け。 高次元すぎる場合は次元削減か集約を検討。

🧮 SSDSE-B-2026 で 47 都道府県をエンコードしてみる

ケース 1:都道府県を OneHot 化、 ダミー変数の罠を実演

SSDSE-B-2026 の 2023 年データ(47 行)で、 都道府県を OneHot 化すると 46 列(K-1)のダミーが生成される。 線形回帰の基準カテゴリは「三重県(文字コード順で先頭)」となり、 残りの 46 県の係数は「基準県との差」として解釈される。

📥 入力: data/raw/SSDSE-B-2026.csv(年度=2023, 47 行)の Prefecture 列をカテゴリとして使用。

🐍 Python 実装(このコードでやること: 47 都道府県を OneHot 化し、 ダミー変数の罠を体感する):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 47 都道府県を OneHot エンコード
import 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()
print(f'行数: {len(df)} / Prefecture ユニーク数: {df["Prefecture"].nunique()}')

# ① drop_first=False (全 47 列 → ダミー変数の罠)
dummies_all = pd.get_dummies(df['Prefecture'], prefix='pref', drop_first=False)
print(f'drop_first=False → 列数 {dummies_all.shape[1]}')  # 47 列

# ② drop_first=True (46 列 → 線形回帰で使える形)
dummies_drop = pd.get_dummies(df['Prefecture'], prefix='pref', drop_first=True)
print(f'drop_first=True  → 列数 {dummies_drop.shape[1]}')  # 46 列
print(f'基準カテゴリ(落ちた列): {set(dummies_all.columns) - set(dummies_drop.columns)}')

📤 実行結果:

行数: 47 / Prefecture ユニーク数: 47 drop_first=False → 列数 47 drop_first=True → 列数 46 基準カテゴリ(落ちた列): {'pref_三重県'}

💬 結果の読み方: OneHot は 全 K 列を作ると 1 列が他列の線形結合になり、 線形回帰の係数が不定(多重共線性)になる。 sklearn の OneHotEncoder や pandas の drop_first=True で 1 列を落とすのが慣例。 基準は 三重県(get_dummies はカテゴリを文字コード順にソートするため、 データ行頭の北海道ではなく文字コード順先頭の三重県)が落とされる。

ケース 2:地域ブロック集約で次元削減(47 → 8)

47 都道府県を 8 地域ブロック(北海道・東北・関東・中部・近畿・中国・四国・九州沖縄)に集約すれば、 OneHot 列数は 47→8(drop 後 7)に減る。 サンプル数 47 件しかない状況では、 過学習を防ぐためにも集約が有効。

🎯 このコードでやること: SSDSE-B-2026 の Prefecture を 8 地域ブロックに集約し、 OneHot 列数を比較する。

📥 入力例(SSDSE-B-2026 全体:564 行 × 112 列 = 47 都道府県 × 2012〜2023 年) 年度 地域コード 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) … 2023 R01000 北海道 5,092,000 1,681,000 24,430 … 2023 R13000 東京都 14,086,000 3,205,000 86,348 … 2023 R47000 沖縄県 1,468,000 350,000 12,549 … …(残り 112 列は住宅・家計・教育・医療など)
 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
import 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()

REGION = {
  '北海道': '北海道',
  '青森県': '東北', '岩手県': '東北', '宮城県': '東北',
  '秋田県': '東北', '山形県': '東北', '福島県': '東北',
  '茨城県': '関東', '栃木県': '関東', '群馬県': '関東',
  '埼玉県': '関東', '千葉県': '関東', '東京都': '関東', '神奈川県': '関東',
  '新潟県': '中部', '富山県': '中部', '石川県': '中部', '福井県': '中部',
  '山梨県': '中部', '長野県': '中部', '岐阜県': '中部',
  '静岡県': '中部', '愛知県': '中部',
  '三重県': '近畿', '滋賀県': '近畿', '京都府': '近畿',
  '大阪府': '近畿', '兵庫県': '近畿', '奈良県': '近畿', '和歌山県': '近畿',
  '鳥取県': '中国', '島根県': '中国', '岡山県': '中国',
  '広島県': '中国', '山口県': '中国',
  '徳島県': '四国', '香川県': '四国', '愛媛県': '四国', '高知県': '四国',
  '福岡県': '九州沖縄', '佐賀県': '九州沖縄', '長崎県': '九州沖縄',
  '熊本県': '九州沖縄', '大分県': '九州沖縄', '宮崎県': '九州沖縄',
  '鹿児島県': '九州沖縄', '沖縄県': '九州沖縄',
}
df['region'] = df['Prefecture'].map(REGION)
print(df['region'].value_counts())

d_pref   = pd.get_dummies(df['Prefecture'], drop_first=True)
d_region = pd.get_dummies(df['region'],     drop_first=True)
print(f'Prefecture OneHot 列数: {d_pref.shape[1]}')
print(f'Region     OneHot 列数: {d_region.shape[1]}')

📤 実行結果:

関東 7 中部 9 近畿 7 東北 6 九州沖縄 8 中国 5 四国 4 北海道 1 Name: region, dtype: int64 Prefecture OneHot 列数: 46 Region OneHot 列数: 7

💬 結果の読み方: 47 → 8 地域に集約することで、 OneHot 列数は 46 → 7 と 約 1/7 に圧縮された。 サンプル 47 件・列 46 という「列数 ≈ サンプル数」の危険な状況から、 サンプル 47 件・列 7 という健全な比率に改善。 ただし「東京特有」「沖縄特有」の効果は失われる ── 「分析の解像度」と「過学習リスク」のトレードオフ。

ケース 3:Cramer's V で「都道府県 × 高齢化率カテゴリ」の関連を測る

SSDSE-B-2026 の連続変数「高齢化率」(A1303 = 65 歳以上人口 ÷ 総人口)を 3 段階のカテゴリ(低/中/高)に変換し、 「地域 × 高齢化レベル」のクロス表で Cramer's V を計算してみる。

🎯 このコードでやること: SSDSE-B-2026 の連続変数を 3 分位カテゴリに変換し、 地域との関連強度を Cramer's V で測定する。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) 北海道 5,092,000 1,681,000 東京都 14,086,000 3,205,000 沖縄県 1,468,000 350,000 …(全 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
28
29
30
31
32
33
34
import pandas as pd
import numpy as np
from scipy.stats import chi2_contingency

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023].copy()

# 高齢化率を連続値から 3 分位カテゴリに変換
df['高齢化率'] = df['A1303'] / df['A1101'] * 100
df['高齢化レベル'] = pd.qcut(df['高齢化率'], 3, labels=['低', '中', '高'])

_BLOCKS = {   # 8 地域ブロック(47 都道府県すべてを割り当てる)
  '北海道': ['北海道'],
  '東北':   ['青森県', '岩手県', '宮城県', '秋田県', '山形県', '福島県'],
  '関東':   ['茨城県', '栃木県', '群馬県', '埼玉県', '千葉県', '東京都', '神奈川県'],
  '中部':   ['新潟県', '富山県', '石川県', '福井県', '山梨県', '長野県',
             '岐阜県', '静岡県', '愛知県'],
  '近畿':   ['三重県', '滋賀県', '京都府', '大阪府', '兵庫県', '奈良県', '和歌山県'],
  '中国':   ['鳥取県', '島根県', '岡山県', '広島県', '山口県'],
  '四国':   ['徳島県', '香川県', '愛媛県', '高知県'],
  '九州沖縄': ['福岡県', '佐賀県', '長崎県', '熊本県', '大分県', '宮崎県',
               '鹿児島県', '沖縄県'],
}
REGION = {p: b for b, ps in _BLOCKS.items() for p in ps}
df['region'] = df['Prefecture'].map(REGION)

ct = pd.crosstab(df['region'], df['高齢化レベル'])
print('クロス表:'); print(ct)

chi2, p, dof, exp = chi2_contingency(ct)
n = ct.values.sum()
v = np.sqrt(chi2 / (n * (min(ct.shape) - 1)))
print(f'χ² = {chi2:.3f}, p = {p:.4f}, dof = {dof}')
print(f"Cramer's V = {v:.3f}")

📤 実行結果(実データ実行例):

クロス表: 高齢化レベル 低 中 高 region 中国 1 1 3 中部 2 6 1 九州沖縄 2 2 4 北海道 0 1 0 四国 0 1 3 東北 1 1 4 近畿 4 2 1 関東 6 1 0 χ² = 26.428, p = 0.0228, dof = 14 Cramer's V = 0.530

💬 結果の読み方: Cramer's V = 0.530 は中〜強程度の関連を示す。 関東・近畿は「低/中」が多く、 東北・四国は「高」が多い ── 地域と高齢化率には明らかな結びつきがある。 p = 0.023 で独立仮説は棄却されるが、 サンプル 47・期待度数 5 未満のセル多数のためFisher の正確検定 / シミュレーション p 値での再確認が推奨される。

🧮 数式に値を入れて手で計算する: カテゴリ変数の構成比とモード

合成データで血液型 10 件の頻度・構成比・モードを計算する。

Step 1: 観測データと頻度

カテゴリ頻度構成比
A40.40
O30.30
B20.20
AB10.10

Step 2: 検算

合計 = 4+3+2+1 = 10 A 比 = 4/10 = 0.40 モード = A (最頻値)

🐍 Python で再現

1
2
3
4
5
6
7
import pandas as pd
x = pd.Series(['A','A','A','A','O','O','O','B','B','AB'])
freq = x.value_counts()
ratio = freq / len(x)
print(f"頻度:\n{freq}")
print(f"構成比:\n{ratio}")
print(f"モード: {x.mode()[0]}")

📤 実行結果

頻度: A 4 O 3 B 2 AB 1 構成比: A 0.4 O 0.3 B 0.2 AB 0.1 モード: A

💬 手計算 (Step 2) モード A / 構成比 0.40 と Python 出力が完全一致。

🐍 Python 実装

最小限のスニペットで動作確認できる例。 公的データ(SSDSE 等)を想定しています。

📥 入力例(SSDSE-B-2026 全体:564 行 × 112 列 = 47 都道府県 × 2012〜2023 年) 年度 地域コード 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) … 2023 R01000 北海道 5,092,000 1,681,000 24,430 … 2023 R13000 東京都 14,086,000 3,205,000 86,348 … 2023 R47000 沖縄県 1,468,000 350,000 12,549 … …(残り 112 列は住宅・家計・教育・医療など)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1)

# pandas での OneHot(基準1列削除)
dummies = pd.get_dummies(df['地域コード'], drop_first=True, prefix='region')
df2 = pd.concat([df.drop('地域コード', axis=1), dummies], axis=1)

# sklearn 版
from sklearn.preprocessing import OneHotEncoder
enc = OneHotEncoder(drop='first', sparse_output=False)
encoded = enc.fit_transform(df[['地域コード']])
print(encoded[:3])
📤 実行例(実測) [[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]

⚠️ よくある落とし穴

❌ 1. 順序のないカテゴリにLabelEncoding
0,1,2に距離があるかのように学習される
❌ 2. OneHotで列爆発
商品ID 10万件をOneHotすると列爆発。 Embedding等を検討
❌ 3. ダミー変数の罠
基準カテゴリを落とさず全て入れると多重共線性で係数が不定
❌ 4. Target Encodingでのリーケージ
全データでaggregateすると目的変数の情報がfeatureに混じる。 CV内で計算する
❌ 5. 未知カテゴリの扱い
本番で訓練時にないカテゴリが来た時の挙動を設計

⚠️ Target Encoding のリーケージを実演

Target Encoding は強力だが、 全データで一気に集計 すると目的変数の情報が特徴量に漏れ、 訓練データで完璧な精度・本番データで崩壊するという典型的な過学習を起こす。 SSDSE-B-2026 で実演する。

🎯 このコードでやること: 都道府県カテゴリを「合計特殊出生率(目的変数)」の平均で Target Encoding し、 train/test 分割の前後で適用した場合の差を比較する。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A4103(合計特殊出生率) 北海道 1.06 東京都 0.99 沖縄県 1.6 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import pandas as pd
from sklearn.model_selection import KFold

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023].copy()
y = df['A4103']  # 合計特殊出生率(目的変数)

# NG: 全データで Target Encoding (リーケージあり)
df['pref_te_leak'] = df.groupby('Prefecture')['A4103'].transform('mean')
print('NG リーク版: 各県の TE = その県自身の y そのもの')
print(df[['Prefecture', 'A4103', 'pref_te_leak']].head(5))

# OK: K-Fold で fold 外平均のみを使う (リーケージなし)
df['pref_te_kfold'] = 0.0
kf = KFold(n_splits=5, shuffle=True, random_state=42)
for tr, va in kf.split(df):
    mean_map = df.iloc[tr].groupby('Prefecture')['A4103'].mean()
    global_mean = df.iloc[tr]['A4103'].mean()
    df.iloc[va, df.columns.get_loc('pref_te_kfold')] = (
        df.iloc[va]['Prefecture'].map(mean_map).fillna(global_mean)
    )
print('OK K-Fold 版: fold 外平均で計算、 自分自身の y は使わない')
print(df[['Prefecture', 'A4103', 'pref_te_leak', 'pref_te_kfold']].head(5))

📤 実行結果:

NG リーク版: 各県の TE = その県自身の y そのもの Prefecture A4103 pref_te_leak 0 北海道 1.06 1.060 1 青森県 1.23 1.230 2 岩手県 1.16 1.160 3 宮城県 1.07 1.070 4 秋田県 1.10 1.100 OK K-Fold 版: fold 外平均で計算、 自分自身の y は使わない Prefecture A4103 pref_te_leak pref_te_kfold 0 北海道 1.06 1.060 1.289 1 青森県 1.23 1.230 1.293 2 岩手県 1.16 1.160 1.293 3 宮城県 1.07 1.070 1.292 4 秋田県 1.10 1.100 1.306

💬 結果の読み方: NG 版では pref_te_leak == A4103(完全に y そのもの)になっている ── これを特徴量に入れるとモデルは「この特徴量を見ろ」と覚えてしまい、 訓練精度 100%・本番精度ガタ落ち。 OK 版では fold 外平均(全国平均 1.293 に近似。 fold ごとに 1.289〜1.306 と僅かに揺れる)が入り、 リークが回避される。 サンプル数 47 では各 fold に各県が 1 件しかなく、 fold 外には自分の県の情報が無いため fallback として全体平均が入る。

💼 現場でのカテゴリ変数あるある

ケース A:「未知カテゴリ」が本番で出現

訓練時には存在しなかった商品 ID・店舗コード・郵便番号が本番で来ると、 sklearn の OneHotEncoder は handle_unknown='error' のデフォルトで例外を吐く。 対応策は handle_unknown='ignore'(全 0 ベクトルにする)か、 「OTHER」「未知」というプレースホルダカテゴリを訓練時に意図的に混ぜておく方法。

ケース B:順序ありなのに OneHot にしてしまう

「学歴:中学/高校/大学/大学院」「満足度:1〜5」「Tシャツサイズ:S/M/L/XL」など順序が明確なカテゴリを OneHot にすると、 順序情報が消失し、 線形モデルでは「高校 → 大学」と「中学 → 大学院」が等価扱い。 これは Label Encoding(整数化)または Ordered Categories(pandas の CategoricalDtype(ordered=True))を使うべき。

ケース C:高カーディナリティに OneHot

郵便番号(10 万件)・商品 ID(100 万件)を OneHot 化すると、 メモリ・計算時間が爆発。 対策は (1) Target / Frequency Encoding、 (2) 上位 N カテゴリのみ OneHot で残りを「OTHER」、 (3) ハッシング(FeatureHasher)、 (4) Entity Embedding(深層学習)。

ケース D:スパース表記 vs Dense 表記

OneHot 結果は大半が 0 なので scipy.sparse 行列 で保持するとメモリ激減。 sklearn の OneHotEncoder は sparse_output=True(デフォルト)でスパース返却。 XGBoost/LightGBM はスパース対応。 statsmodels や一部の旧ライブラリは Dense(.toarray())が必要。

ケース E:カテゴリ × カテゴリの交互作用

「都道府県 × 業種」の交互作用を線形回帰でモデル化する場合、 単純な足し算では捉えられない。 sklearn の PolynomialFeatures や、 「pref_業種」と連結した新規カテゴリを作る、 木モデル(自動で交互作用を学ぶ)を使うなどの手段がある。

⚠️ 落とし穴ディープダイブ 7 連発

❌ 1. 「都道府県コード」を Label Encoding して順序ありと誤認
SSDSE-B-2026 の Code 列は R01000, R02000, … の文字列ID。 これを 1, 2, …, 47 に LabelEncoding し線形回帰に投入すると、 「北海道(1) と沖縄(47) は最も離れている」「東京(13) と京都(26) は等距離」と誤解釈される。 → OneHot か Target Encoding を使う。
❌ 2. 「年度」を連続値で扱う vs カテゴリ化
SSDSE-B-2026 の SSDSE-B-2026 列は 2018-2023 の年度。 連続値(差は等間隔)として扱える一方、 「コロナ前/後」のような構造変化を学ばせたいならカテゴリ化して年度ダミーを入れる。 用途で使い分ける判断が必要。
❌ 3. pandas dtype を category にせずメモリ浪費
100 万行 × 都道府県カラム(文字列)は約 60 MB だが、 df['Prefecture'] = df['Prefecture'].astype('category') にすると約 1 MB に圧縮。 大規模データでは必須テクニック。
❌ 4. train/test 分割前に OneHot
分割前に全データで OneHot すると、 train にしかないカテゴリの列が test にも作られ、 test での未知カテゴリ問題が表面化しない。 sklearn の Pipeline を使い、 fit は train だけ、 transform は両方というルールを守る。
❌ 5. カテゴリ × カテゴリの交互作用を線形モデルで省略
「業種 × 地域」の交互効果が重要なのに、 ベースの OneHot だけ入れて満足してしまう。 PolynomialFeatures(degree=2) で交互項追加、 もしくは木モデルへの移行を検討。
❌ 6. Target Encoding に正則化を入れ忘れる
出現 1 件のカテゴリでも「そのカテゴリの y 平均」が符号化値になり、 完全リーク。 smoothing パラメータ m を 5-20 に設定し、 さらに K-Fold で fold 外平均を使う 2 段構えが標準。
❌ 7. Embedding の次元 d を適当に決める
Cheng Guo (2016) の経験則は $d = \min(50, \lceil K / 2 \rceil)$。 47 都道府県なら d=24 程度。 d を大きくすれば容量は増えるが、 学習データが少ないと過学習。

🛠 主要ライブラリの選択ガイド

ライブラリ 対応エンコーダ 特徴
pandasget_dummies, category dtype最も手軽、 列名がそのまま付く
sklearnOneHot, Ordinal, LabelEncoder, FeatureHasherPipeline 統合、 sparse 対応
category_encodersTarget, WoE, Helmert, James-Stein, M-Est, Backward Diff, Sum, Polynomial学術系エンコーダの宝庫
CatBoost内部 Ordered TS(自動)cat_features= 指定だけで完結
LightGBMcategorical_feature= 引数直接分岐、 Fisher's exact ベース
PyTorch/Kerasnn.Embedding深層学習で学習可能な埋め込み

🎓 まとめ:カテゴリ変数を扱うための 5 ステップ

  1. 尺度を判定 ── 名義か順序か、 ユニーク数 K はいくつか
  2. 欠損とノイズ処理 ── 「不明」カテゴリ追加 + 表記ゆれ統一(「東京都」「東京」を統合)
  3. エンコーダ選択 ── K と モデルとサンプル数の組み合わせで決定木に従う
  4. Pipeline で fit/transform 分離 ── train で fit、 test で transform。 リーケージ厳禁
  5. 結果解釈 ── 線形なら係数の符号と大きさ、 木なら feature_importances_ や SHAP

SSDSE-B-2026 のような47 行という小データでは、 OneHot で 46 列を作ると即過学習。 地域ブロック集約・正則化・CatBoost 系の自動処理を優先的に検討する。 大データならエンコーダ選択の自由度が増す ── が、 「Target Encoding は K-Fold 内で fit」の鉄則だけは規模を問わず必須。

🔢 「順序あり」を pandas で正しく宣言する

pandas の CategoricalDtype(ordered=True) を使うと、 「低 < 中 < 高」のような順序を型レベルで指定でき、 ソート・比較・groupby・カットオフが順序通りに動作する。 SSDSE-B-2026 で高齢化レベルを順序付きカテゴリとして宣言してみる。

🎯 このコードでやること: CategoricalDtype で順序を明示し、 順序付き比較・groupby 順序保持・並べ替えを確認する。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) A4103(合計特殊出生率) 北海道 5,092,000 1,681,000 1.06 東京都 14,086,000 3,205,000 0.99 沖縄県 1,468,000 350,000 1.6 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import pandas as pd
from pandas.api.types import CategoricalDtype

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

# 順序を明示
LEVEL = CategoricalDtype(categories=['低', '中', '高'], ordered=True)
df['高齢化レベル'] = pd.qcut(df['高齢化率'], 3, labels=['低','中','高']).astype(LEVEL)

# 順序付き比較が可能
print(f'高齢化レベル「中」以上の県数: {(df["高齢化レベル"] >= "中").sum()}')

# groupby で順序通りに集計
print(df.groupby('高齢化レベル', observed=True)['A4103'].mean().round(3))

# sort_values で順序通り
print(df.sort_values('高齢化レベル')[['Prefecture', '高齢化率', '高齢化レベル']].head(5))

📤 実行結果(参考値):

高齢化レベル「中」以上の県数: 31 高齢化レベル 低 1.229 中 1.313 高 1.337 Name: A4103, dtype: float64 Prefecture 高齢化率 高齢化レベル 46 沖縄県 23.8 低 25 京都府 29.7 低 26 大阪府 27.7 低 ...

💬 結果の読み方: 2023 年の実測では高齢化レベルが「高」の群ほど合計特殊出生率の平均が高い(1.23 → 1.34)── 「高齢化が進むと出生率が低い」という素朴な予想とは逆で、 高齢化率が低い群に東京など低出生率の大都市が集まるためである(生態学的相関の好例)。 ordered=True 指定により >= 比較・自動ソートが意味通りに動作する。 順序を持たない名義カテゴリには ordered=False(デフォルト)を使う。

📝 演習:3 シナリオで手を動かす

演習 1:SSDSE-B-2026 の Code 列を 47 県のラベルとして扱う

SSDSE-B-2026 の Code 列(R01000, R02000, …)は文字列。 これを「カテゴリ変数として扱う」「数値変換する」「数値部分を抽出して連続値扱いする」で何が起きるか比較。

  1. OneHot: 列数 47 → 46(drop_first)。 線形回帰の係数は「基準県(文字コード順先頭の三重県)との差」
  2. Label Encoding: 「R01000=0, R02000=1, ..., R47000=46」── 順序情報は意味なし
  3. 数値抽出: int(code[1:3]) → 1, 2, ..., 47。 これも順序意味なし、 NG

結論: 都道府県は「名義カテゴリ」なので OneHot 一択(線形)または CatBoost 内部処理(木)。

演習 2:気温帯(B4101)を 4 階級カテゴリに変換

連続値を「cut で等幅ビン」「qcut で等頻度ビン」「ドメイン知識で固定境界」の 3 方式で比較。 SSDSE-B-2026 の B4101(年平均気温)を 4 段階「寒冷/温和/温暖/亜熱帯」に分ける。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 B4101(年平均気温) 北海道 11.0 東京都 17.6 沖縄県 23.8 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023].dropna(subset=['B4101']).copy()

# 方式 A: 等幅ビン
df['気温_cut']  = pd.cut(df['B4101'], 4, labels=['寒冷','温和','温暖','亜熱帯'])
# 方式 B: 等頻度ビン
df['気温_qcut'] = pd.qcut(df['B4101'], 4, labels=['寒冷','温和'],duplicates='drop') if False else pd.qcut(df['B4101'], 4, labels=['寒冷','温和','温暖','亜熱帯'])
# 方式 C: ドメイン固定境界
df['気温_dom']  = pd.cut(df['B4101'], bins=[-10,10,14,18,30],
                            labels=['寒冷','温和','温暖','亜熱帯'])

for col in ['気温_cut','気温_qcut','気温_dom']:
    print(f'--- {col} ---')
    print(df[col].value_counts())

📤 実行結果(参考値):

--- 気温_cut --- 温和 20 温暖 20 寒冷 6 亜熱帯 1 --- 気温_qcut --- 温和 14 寒冷 12 亜熱帯 12 温暖 9 --- 気温_dom --- 温暖 32 亜熱帯 9 温和 6 寒冷 0

💬 結果の読み方: 等幅 (cut) は値域を均等分割するため、 分布が偏ると一部カテゴリにサンプル集中(亜熱帯は沖縄 1 県のみ)。 等頻度 (qcut) は件数がほぼ均等になる(同値の県があるため厳密には揃わない)。 ドメイン固定は「気候区分の意味」が保たれ最も解釈しやすいが、 境界決定にドメイン知識が必要(この境界例では 2023 年に 10℃未満の県が無く「寒冷」が 0 件になる ── 境界の再設計が必要という教訓も得られる)。 場面で使い分け。

演習 3:表記揺れの統合

実データには「東京都」「東京」「Tokyo」「とうきょう」のような表記揺れが残る。 「都」「府」「県」の suffix を統一処理する関数を書く。 SSDSE-B-2026 の Prefecture 列は綺麗だが、 実務では必須スキル。

📥 入力例(SSDSE-B-2026 全体:564 行 × 112 列 = 47 都道府県 × 2012〜2023 年) 年度 地域コード 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) … 2023 R01000 北海道 5,092,000 1,681,000 24,430 … 2023 R13000 東京都 14,086,000 3,205,000 86,348 … 2023 R47000 沖縄県 1,468,000 350,000 12,549 … …(残り 112 列は住宅・家計・教育・医療など)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import pandas as pd
import re

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])

# 模擬:表記ゆれを混入
sample = pd.DataFrame({'pref_raw':['東京都','東京','TOKYO','大阪府','大阪','OSAKA','神奈川','kanagawa']})

def normalize_pref(s):
    s = str(s).strip().lower()
    s = s.replace('tokyo','東京').replace('osaka','大阪').replace('kanagawa','神奈川')
    s = re.sub(r'(都|府|県)$', '', s)  # 末尾 suffix を一旦剥がす
    base = s
    # 正規形を再付与
    SUFFIX = {'東京':'都', '大阪':'府', '京都':'府', '北海道':''}
    return base + SUFFIX.get(base, '県')

sample['pref_norm'] = sample['pref_raw'].apply(normalize_pref)
print(sample)

📤 実行結果:

pref_raw pref_norm 0 東京都 東京都 1 東京 東京都 2 TOKYO 東京都 3 大阪府 大阪府 4 大阪 大阪府 5 OSAKA 大阪府 6 神奈川 神奈川県 7 kanagawa 神奈川県

💬 結果の読み方: 「東京都/東京/TOKYO」が「東京都」に統一された。 これがないと OneHot 列が 47 のはずが 100+ になる。 実務では正規化辞書 + fuzzy matching(rapidfuzz)+ 異常検知の 3 段構えが定番。

🧭 関連ページから深掘り

🏁 公開ベンチマークでのエンコーダ比較(参考)

Kaggle や公開ベンチマークでの「同条件下でのエンコーダ性能比較」報告例 (Pargent et al. 2022, "Regularized target encoding outperforms traditional methods" など) をまとめると、 概ね次の傾向:

エンコーダ 小カーディナリティ (K<10) 中 (K=10-100) 大 (K>100) 超大 (K>10000)
OneHot××
Target (+smoothing)
CatBoost 内部
Frequency×
Hashing×
Entity Embedding

SSDSE-B-2026 47 都道府県(K=47)は中カーディナリティに分類され、 Target Encoding + smoothing が無難。 ただしサンプル 47 行という極小データなので、 CatBoost 内部処理または「地域 8 ブロックに集約 → OneHot」が実用的。

🚀 これからのカテゴリ変数研究動向

いずれもまだ研究段階だが、 「カテゴリ変数を上手く扱える人」は実装・研究の両方で価値が高い。 本ページの基礎概念がそのままベースになる。

🗒 1 ページ要約 — チートシート

[判断フロー]
カテゴリ数 K と モデルとデータ量で決まる。

  K=2(二値)        → 0/1 だけ。 OneHot 不要。
  K=3-9, 順序なし  → OneHot(drop_first=True)
  K=3-9, 順序あり  → OrdinalEncoder or CategoricalDtype(ordered=True)
  K=10-100         → Target + smoothing + K-Fold / CatBoost cat_features
  K=100-10000      → Frequency / Target / CatBoost / Hashing
  K>10000          → Hashing / Embedding / LLM 埋め込み

[コードスニペット]
  pandas       : pd.get_dummies(df['col'], drop_first=True)
  sklearn      : OneHotEncoder(drop='first', sparse_output=False, handle_unknown='ignore')
  catboost     : CatBoostClassifier(cat_features=['col1', 'col2'])
  lightgbm     : LGBMClassifier().fit(X, y, categorical_feature=['col1'])
  category_encoders: TargetEncoder(smoothing=10).fit_transform(X, y)

[禁忌]
  ❌ 文字列 ID を Label Encoding して線形回帰
  ❌ 全データで Target Encoding を fit
  ❌ train で fit せず test 単独で fit
  ❌ 高カーディナリティに drop_first なしの OneHot
  

🗺 概念マップ

関連概念を視覚的に整理した概念マップ。

カテゴリ変数 OneHot Encoding Label Encoding Target Encoding Embedding CatBoost ダミー変数の罠

中心のカテゴリ変数から、 (上) 名義 vs 順序 vs 二値の分類、 (右上) one-hot エンコーディング (sklearn の OneHotEncoder)、 (右下) Target Encoding/Ordinal Encoding、 (下) CatBoost の Ordered Target Statistics (リーク回避型)、 (左) ダミー変数トラップとリファレンスカテゴリの選択、 へ放射状に接続している。 SSDSE-B-2026 の「47 都道府県」をモデルに投入する場合、 線形回帰では 1 つを基準カテゴリに置いた 46 個のダミー変数、 木モデルでは label encoding でも内部分割で対応、 高基数 (1000+ 水準) なら CatBoost の target encoding が省メモリかつリーク防止で実用的。

🔗 隣接手法への橋渡し

カテゴリ変数 (名義・順序) は数値演算が意味を持たない離散ラベルで、 集計・可視化・モデル投入の各段で数値変数とは別の処理が必要。

SSDSE-B-2026 の「都道府県」「地方区分」「政令指定都市の有無」はすべてカテゴリ変数で、 平均を計算してはいけない、 棒グラフ・分割表で集計、 線形モデルにはダミー化、 という処理を一貫して適用する。

🌳 「どのエンコーダを使うか」決定木

🌳 「どのエンコーダを使うか」決定木

カテゴリ変数あり
  ├── ユニーク数 K < 10?
  │     ├── Yes → 順序あり?
  │     │         ├── Yes → Label / Ordinal Encoding
  │     │         └── No  → OneHot Encoding (drop_first=True)
  │     └── No  → モデルは?
  │               ├── 線形 → Target Encoding (K-Fold + smoothing) or WoE
  │               ├── 木   → CatBoost (cat_features=) or Target / Frequency
  │               ├── 深層 → Entity Embedding (層を学習で獲得)
  │               └── 超高 K (>10000) → Hashing Encoding (固定 d 次元)
  ├── 欠損あり?
  │     └── 「不明」カテゴリ追加 → 上記分岐に進む
  └── 未知カテゴリが本番で出る可能性?
        └── handle_unknown='ignore' or 「OTHER」カテゴリを訓練に混ぜる
  

📜 歴史的経緯と研究動向

「カテゴリ変数」を扱う際の手法選択は、 状況に応じて以下のフローで判断すると迷いが減る。

  1. Step 1: 目的は記述か予測か?
    • 記述 (現状把握・要約) → 集計・可視化・要約統計量で全体像を掴む
    • 予測 (未知データへの推定) → モデル構築・検証フェーズへ移行
  2. Step 2: データの種類・規模は?
  3. Step 3: 結果の解釈・共有は?
    • 専門家向け → 数値指標・統計検定で精緻に評価
    • 非専門家向け → 可視化・自然言語での要約を重視

このフローに沿って判断することで、 「カテゴリ変数」を中核とした適切な手法選択ができる。

🔎 解説を深掘りする ── 連続変数を「カテゴリに変える」側から見る

本ページの他の節は「もとからカテゴリな変数(都道府県・地域区分)をどう数値化・集計するか」を扱った。 ここでは逆向き、 連続変数をカテゴリ変数へ変換する(ビニング/離散化)という、 実データ分析で最も事故が起きやすい操作を独自角度で深掘りする。 「小・中・大」のようなカテゴリは分析者が作り出したものであって、 データに最初から存在するわけではない、 という点が核心だ。

🎨 直感

SSDSE-B-2026 の総人口(列 A1101, 2023 年, 47 都道府県)は連続量だ。 実測では最小が鳥取県の 537,000 人、 最大が東京都の 14,086,000 人、 中央値は 1,549,000 人(実測値)。 この連続量を「人口規模カテゴリ(小・中・大)」という3 水準の順序尺度カテゴリ変数に畳み込むと、 扱いは一気に軽くなる(平均ではなく最頻値、 ヒストグラムではなく棒グラフ)。 だが軽くなる代わりに、 どこで線を引くかという自由度が新たに生まれる。 カテゴリ変数は「自然に与えられる」だけでなく「作る」ものでもある、 というのがこの節の直感だ。

⚠️ 落とし穴(重要)── 切り方でカテゴリの中身が激変する

同じ総人口を 3 グループに切っても、 等幅(equal-width)で切るか 等頻度(分位点 qcut)で切るかで、 各カテゴリの件数はまったく別物になる。 SSDSE-B-2026・2023 年・47 都道府県での実測を並べる。

切り方境界(実測, 人)
等幅(3等分)537,000 / 5,053,333 / 9,569,666 / 14,086,0003881
等頻度(分位点)537,000 / 1,170,000 / 1,979,666 / 14,086,000161516

等幅では「大」がたった 1 県(東京都)、 「小」が 38 県に偏る(実測 38 / 8 / 1)。 東京都という外れ値が横軸の幅を独り占めするためだ。 一方、 分位点なら 16 / 15 / 16 とほぼ均等になる(実測)。 どちらも「正しい」カテゴリ化だが、 後段の集計・クロス表・カイ二乗検定の結論はカテゴリの中身に引きずられて変わる。 「人口規模カテゴリで層別したら差が出た/出なかった」という主張は、 切り方を明示しない限り再現も反証もできない。 これがビニング最大の落とし穴で、 前段の カイ二乗検定 や Cramér V もこの恣意性を土台に載っていることを忘れてはいけない。

# SSDSE-B-2026, 2023, 47都道府県, 列A1101(総人口) 実測 pop.min()=537000 (鳥取県) pop.max()=14086000 (東京都) pop.median()=1549000 pd.cut(pop, bins=3) -> 小38 / 中8 / 大1 # 等幅: 東京都が幅を独占 pd.qcut(pop, q=3) -> 小16 / 中15 / 大16 # 等頻度: ほぼ均等

🚀 発展

ビニングで生まれるのは順序尺度のカテゴリ変数なので、 One-Hot ではなく順序を保つ Ordinal エンコーディングが基本(本ページ数式節の①②を参照)。 とはいえ連続変数をわざわざカテゴリ化すること自体が情報の損失であり、 木系モデル(決定木・勾配ブースティング)は連続量のまま最適な分割点を学習するので、 人手のビニングはしばしば不要かつ有害になる。 カテゴリ化が正当化されるのは、 (1) 非線形な閾値効果が既知(例: 法定の年齢区分)、 (2) 非専門家への提示で解釈性を優先、 (3) 外れ値の影響を頑健に丸めたい、 といった場合に限られる。 迷ったら「まず連続量のまま ヒストグラム で分布を見る → 本当にカテゴリが必要か問い直す」という順番を守るとよい。

🧭 関連ページ