「categorical variable」は統計データ分析の文脈で扱う重要概念のひとつ。 本ページでは「categorical variable」を取り巻く中核キーワードを以下にチップで一覧化する。 各キーワードは関連する概念・手法・道具立てを含み、 文献検索や学習計画の起点になる。
これらのキーワードは「categorical variable の理解 → 適用 → 検証」のプロセスを構成する。 各章で詳しく解説する。
🍰 まずはやさしく
グループ分けをするためのデータです。
データの種類を整理するために使います。
血液型や都道府県などが例です。
この章ではカテゴリ変数の基本を学びます。
カテゴリ変数 ── カテゴリ値を取る変数(質的変数の一種)
🍰 まずはやさしく
アンケートなどでよく見るデータです。
正しく数値に変えて分析に使うためです。
スマホの利用状況などの調査で出会います。
定義から実装までの流れを解説します。
質問紙、 アンケート、 顧客属性、 商品分類など、 実データはカテゴリだらけ。 正しく数値化しないと機械学習モデルが動きません。
本ページでは「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列):
回帰では K-1個 に減らす(ダミー変数の罠回避)。 sklearn なら drop='first'。
カテゴリ変数 $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} $$目的変数 $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}$ に強く引きつけられ、 過学習を抑制する。
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 が完全関連。 相関係数のカテゴリ版と考えればよい。
カテゴリ変数の「ばらつき」は分散ではなく エントロピー や 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)}} $$
47 都道府県を 8 地域 (北海道 / 東北 / 関東 / 中部 / 近畿 / 中国 / 四国 / 九州) に分けると、内訳は北海道 1, 東北 6, 関東 7, 中部 9, 近畿 7, 中国 5, 四国 4, 九州 8。エントロピーは次の通り。
🎯 このコードでやること: SSDSE-B-2026 を地域区分でカテゴリ化し、 エントロピーと、 「人口規模カテゴリ × 地域」の連関を Cramér V で評価する。
📥 入力データ (SSDSE-B-2026 抜粋):
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}') |
📤 実行例:
💬 結果の読み方: エントロピー 2.853 bits は理論最大 3.000 にほぼ近く、 地域分布は比較的均等(手計算節の値とも一致)。 Cramér V=0.425 は中程度の関連の目安に相当し、 クロス表では関東に「大」が集中する傾向が見えるが、 p=0.256 のため n=47 では統計的有意には届かない(期待度数 5 未満のセルが多く検出力不足。 Fisher の正確検定やシミュレーション p 値での再確認が望ましい)。
機械学習モデルの多くは数値入力しか受け付けないため、 カテゴリ変数を数値化(エンコーディング)する必要 がある。 しかし戦略の選び方を誤ると「順序のない名義変数に大小関係を学習させる」「次元爆発でモデルが過学習する」などの問題が生じる。 ここでは 3 枚の図と Python 実装で典型的なエンコーディングの効果を可視化する。
エンコーディング前に必ず「カテゴリごとの頻度分布」を確認する。 頻度が極端に偏っている場合(例: 47 都道府県中 1 件しか出ないカテゴリ)はレアカテゴリ統合・統計的安定化が必要。 棒グラフを描くだけで頻度バランスは一目瞭然になる。
Target Encoding を採用する場合、 各カテゴリの平均ターゲット値を縦軸に、 サンプル数を横軸にとった散布図で「サンプル数の少ないカテゴリは平均が極端な値になりがち」を確認できる。 ベイズ的スムージング(事前分布で正則化)の必要性を直感する。
カテゴリ別に箱ひげ図を並べることで「カテゴリ間でターゲットの分散が大きく異なるか(異分散)」を確認できる。 異分散の場合は標準的な ANOVA や OLS の仮定が崩れるため、 ロバスト推定や Welch's ANOVA に切り替える判断材料となる。
| 手法 | 次元 | 適用場面 | 弱点 |
|---|---|---|---|
| One-Hot | K-1 | 名義変数・K が小さい | 高カーディナリティで次元爆発 |
| Label | 1 | 順序変数・木モデル入力 | 線形モデルで誤解釈 |
| Target Encoding | 1 | 高カーディナリティ | リークの危険 |
| Ordinal | 1 | 順序が明確 | 等間隔仮定 |
| Hash Encoding | 固定 d | 超高カーディナリティ | 衝突あり |
| Embedding | d (学習) | 深層学習 | サンプル多必要 |
このコードでやること: SSDSE-B-2026 の 47 都道府県を 8 地方ブロックに集約してカテゴリ変数とし、 OneHot と Target Encoding (平均総人口 A1101)を計算する。
📥 入力データ (SSDSE-B-2026 抜粋・2023 年):
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 は K=8(8 地方ブロック)→ 8 列に展開(次元増加)するが情報損失なし。 Target Encoding は 1 列に圧縮できる代わりに「平均総人口」という統計量に置き換わるため、 リーク防止のための K-fold target encoding が必須。 SSDSE のような小標本(n=47)では地方ブロック集約 + One-Hot を優先するのが安全。
handle_unknown='ignore' を指定する、 (b) 訓練時にレア カテゴリ(例: 出現数 < 10)を「Other」に集約する習慣を持つ、 (c) Embedding 層を使う場合は「<UNK> トークン」を事前学習データに含める、 のいずれかである。 本番運用ではドリフト検知 (cardinality drift) を CI に組み込み、 新カテゴリ出現率が閾値を超えたらアラートを上げる仕組みが理想的である。categorical_feature や CatBoost の自動処理)の方が、 大小関係の偽生成を完全に避けられるので推奨される。カテゴリ変数の概念は 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 では「都道府県」「地方ブロック」などの典型的なカテゴリ変数が含まれており、 様々なエンコーディング戦略を比較するのに適した教材データである。 たとえば「都道府県(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 | K-1 | なし | 低 K, 順序なし | sklearn, pandas |
| Label / Ordinal | 1 | 順序情報のみ保持 | 順序あり, 木モデル | sklearn, category_encoders |
| Target (Mean) | 1 | y との相関のみ保持 | 高 K, 強力 | category_encoders |
| Frequency | 1 | 出現頻度のみ | 高 K, 安全 | 手書きで一瞬 |
| Hashing | 指定 d | 衝突あり | 超高 K, 高速 | sklearn FeatureHasher |
| Embedding | d (8-64) | 学習で復元 | 深層学習 | PyTorch/Keras Embedding |
| CatBoost内部 | 自動 | 最小 | 何でも | catboost (cat_features=) |
🎯 このコードでやること: 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}') |
📤 実行結果(参考値):
💬 結果の読み方: R^2 = 1.000 は過学習の典型 ── 47 行に対して 46 + 1 = 47 個の特徴量で「完全フィット」しているだけ。 これは p ≧ n 問題と呼ばれ、 サンプル数が特徴量数を下回ると線形回帰は形式的に正解できてしまう。 実用では (1) Ridge / Lasso などの正則化、 (2) 地域ブロック集約、 (3) Target Encoding、 (4) 階層モデル(県をランダム効果として扱う)が必要。 気温係数 +0.0295/℃ は「気温が高いと出生率がわずかに高い」を示すが、 共線な県ダミーが多いため独立した解釈は危険。 なお OneHotEncoder(drop='first') が落とす基準カテゴリは文字コード順で先頭の「三重県」である点にも注意。
SSDSE-B-2026 から「都道府県 × 高齢化レベル × 出生率レベル」3 つのカテゴリを同時 OneHot 化する場合のメモリ・列数を実測する。 これにより、 多変量カテゴリを安易に OneHot すると 列数が掛け算で増える ことを体感できる。
🎯 このコードでやること: 3 つのカテゴリ列を作成し、 個別 OneHot と交互作用 OneHot の列数・メモリを比較する。
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') |
📤 実行結果(参考値):
💬 結果の読み方: SSDSE-B-2026 の 47 行では「都道府県 × 高齢化 × 出生率」の組み合わせは実質 47 種(各県につき 1 通り)なので、 交互作用列数 = 46 と個別 OneHot 列数 50 は近接。 一方で「全国 1700 市町村 × 6 業種 × 4 年代」のような大規模データでは個別 OneHot が 1700+6+4-3=1707 列に対し、 交互作用 OneHot は 1700×6×4=40800 列に膨れる。 サンプル数を超える特徴量を作る前に、 必要性を吟味すべし。
「クラス分類でカテゴリ目的変数が不均衡な時」「カテゴリ特徴量がクロス間で偏ると検証精度がブレる時」には StratifiedKFold か GroupKFold を使う。 SSDSE-B-2026 で 47 都道府県を 5 fold 分割するとき、 偶然 4 都市集中の県だけが test に偏らない工夫が必要。
🎯 このコードでやること: 高齢化レベル(3 階級)で stratify した KFold と、 都道府県(47 グループ)を group とした GroupKFold を比較する。
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 では fold 0 が「低 6 対 高 2」、 fold 3 が「高 6 対 低 1」と偏り、 seed によっては特定階級が test に 1 件も入らない fold も起こり得る。 StratifiedKFold は各 fold で 3 階級がほぼ均等(3〜4 件ずつ)に含まれ、 評価が安定する。 都道府県でクラスタリングしたい場合は GroupKFold(同じ県を train/test に分ける)。
CatBoost は「カテゴリ列を文字列のまま受け取り、 内部で Ordered Target Encoding を実行する」設計。 SSDSE-B-2026 で都道府県をエンコードせずそのまま投げて出生率を予測する例。
🎯 このコードでやること: cat_features パラメータで Prefecture を明示し、 自前 OneHot/Label せずに CatBoost で予測する。
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 はカテゴリ列を encoder なしで受け取り、 内部で Ordered TS を使って自動エンコード。 R^2 = 0.412 は「気温と都道府県だけで出生率の 41% を説明」── 線形回帰の過学習 R^2 = 1.0 に対し、 CV で測ると現実的な値が出る。 これがちゃんとした「汎化性能の評価」。
都道府県(47カテゴリ)の扱い方:
状況により使い分け。 高次元すぎる場合は次元削減か集約を検討。
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)}') |
📤 実行結果:
💬 結果の読み方: OneHot は 全 K 列を作ると 1 列が他列の線形結合になり、 線形回帰の係数が不定(多重共線性)になる。 sklearn の OneHotEncoder や pandas の drop_first=True で 1 列を落とすのが慣例。 基準は 三重県(get_dummies はカテゴリを文字コード順にソートするため、 データ行頭の北海道ではなく文字コード順先頭の三重県)が落とされる。
47 都道府県を 8 地域ブロック(北海道・東北・関東・中部・近畿・中国・四国・九州沖縄)に集約すれば、 OneHot 列数は 47→8(drop 後 7)に減る。 サンプル数 47 件しかない状況では、 過学習を防ぐためにも集約が有効。
🎯 このコードでやること: SSDSE-B-2026 の Prefecture を 8 地域ブロックに集約し、 OneHot 列数を比較する。
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]}') |
📤 実行結果:
💬 結果の読み方: 47 → 8 地域に集約することで、 OneHot 列数は 46 → 7 と 約 1/7 に圧縮された。 サンプル 47 件・列 46 という「列数 ≈ サンプル数」の危険な状況から、 サンプル 47 件・列 7 という健全な比率に改善。 ただし「東京特有」「沖縄特有」の効果は失われる ── 「分析の解像度」と「過学習リスク」のトレードオフ。
SSDSE-B-2026 の連続変数「高齢化率」(A1303 = 65 歳以上人口 ÷ 総人口)を 3 段階のカテゴリ(低/中/高)に変換し、 「地域 × 高齢化レベル」のクロス表で Cramer's V を計算してみる。
🎯 このコードでやること: SSDSE-B-2026 の連続変数を 3 分位カテゴリに変換し、 地域との関連強度を Cramer's V で測定する。
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}") |
📤 実行結果(実データ実行例):
💬 結果の読み方: Cramer's V = 0.530 は中〜強程度の関連を示す。 関東・近畿は「低/中」が多く、 東北・四国は「高」が多い ── 地域と高齢化率には明らかな結びつきがある。 p = 0.023 で独立仮説は棄却されるが、 サンプル 47・期待度数 5 未満のセル多数のためFisher の正確検定 / シミュレーション p 値での再確認が推奨される。
合成データで血液型 10 件の頻度・構成比・モードを計算する。
| カテゴリ | 頻度 | 構成比 |
|---|---|---|
| A | 4 | 0.40 |
| O | 3 | 0.30 |
| B | 2 | 0.20 |
| AB | 1 | 0.10 |
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]}") |
💬 手計算 (Step 2) モード A / 構成比 0.40 と Python 出力が完全一致。
最小限のスニペットで動作確認できる例。 公的データ(SSDSE 等)を想定しています。
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]) |
Target Encoding は強力だが、 全データで一気に集計 すると目的変数の情報が特徴量に漏れ、 訓練データで完璧な精度・本番データで崩壊するという典型的な過学習を起こす。 SSDSE-B-2026 で実演する。
🎯 このコードでやること: 都道府県カテゴリを「合計特殊出生率(目的変数)」の平均で Target Encoding し、 train/test 分割の前後で適用した場合の差を比較する。
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 版では pref_te_leak == A4103(完全に y そのもの)になっている ── これを特徴量に入れるとモデルは「この特徴量を見ろ」と覚えてしまい、 訓練精度 100%・本番精度ガタ落ち。 OK 版では fold 外平均(全国平均 1.293 に近似。 fold ごとに 1.289〜1.306 と僅かに揺れる)が入り、 リークが回避される。 サンプル数 47 では各 fold に各県が 1 件しかなく、 fold 外には自分の県の情報が無いため fallback として全体平均が入る。
訓練時には存在しなかった商品 ID・店舗コード・郵便番号が本番で来ると、 sklearn の OneHotEncoder は handle_unknown='error' のデフォルトで例外を吐く。 対応策は handle_unknown='ignore'(全 0 ベクトルにする)か、 「OTHER」「未知」というプレースホルダカテゴリを訓練時に意図的に混ぜておく方法。
「学歴:中学/高校/大学/大学院」「満足度:1〜5」「Tシャツサイズ:S/M/L/XL」など順序が明確なカテゴリを OneHot にすると、 順序情報が消失し、 線形モデルでは「高校 → 大学」と「中学 → 大学院」が等価扱い。 これは Label Encoding(整数化)または Ordered Categories(pandas の CategoricalDtype(ordered=True))を使うべき。
郵便番号(10 万件)・商品 ID(100 万件)を OneHot 化すると、 メモリ・計算時間が爆発。 対策は (1) Target / Frequency Encoding、 (2) 上位 N カテゴリのみ OneHot で残りを「OTHER」、 (3) ハッシング(FeatureHasher)、 (4) Entity Embedding(深層学習)。
OneHot 結果は大半が 0 なので scipy.sparse 行列 で保持するとメモリ激減。 sklearn の OneHotEncoder は sparse_output=True(デフォルト)でスパース返却。 XGBoost/LightGBM はスパース対応。 statsmodels や一部の旧ライブラリは Dense(.toarray())が必要。
「都道府県 × 業種」の交互作用を線形回帰でモデル化する場合、 単純な足し算では捉えられない。 sklearn の PolynomialFeatures や、 「pref_業種」と連結した新規カテゴリを作る、 木モデル(自動で交互作用を学ぶ)を使うなどの手段がある。
Code 列は R01000, R02000, … の文字列ID。 これを 1, 2, …, 47 に LabelEncoding し線形回帰に投入すると、 「北海道(1) と沖縄(47) は最も離れている」「東京(13) と京都(26) は等距離」と誤解釈される。 → OneHot か Target Encoding を使う。SSDSE-B-2026 列は 2018-2023 の年度。 連続値(差は等間隔)として扱える一方、 「コロナ前/後」のような構造変化を学ばせたいならカテゴリ化して年度ダミーを入れる。 用途で使い分ける判断が必要。df['Prefecture'] = df['Prefecture'].astype('category') にすると約 1 MB に圧縮。 大規模データでは必須テクニック。m を 5-20 に設定し、 さらに K-Fold で fold 外平均を使う 2 段構えが標準。| ライブラリ | 対応エンコーダ | 特徴 |
|---|---|---|
| pandas | get_dummies, category dtype | 最も手軽、 列名がそのまま付く |
| sklearn | OneHot, Ordinal, LabelEncoder, FeatureHasher | Pipeline 統合、 sparse 対応 |
| category_encoders | Target, WoE, Helmert, James-Stein, M-Est, Backward Diff, Sum, Polynomial | 学術系エンコーダの宝庫 |
| CatBoost | 内部 Ordered TS(自動) | cat_features= 指定だけで完結 |
| LightGBM | categorical_feature= 引数 | 直接分岐、 Fisher's exact ベース |
| PyTorch/Keras | nn.Embedding | 深層学習で学習可能な埋め込み |
SSDSE-B-2026 のような47 行という小データでは、 OneHot で 46 列を作ると即過学習。 地域ブロック集約・正則化・CatBoost 系の自動処理を優先的に検討する。 大データならエンコーダ選択の自由度が増す ── が、 「Target Encoding は K-Fold 内で fit」の鉄則だけは規模を問わず必須。
pandas の CategoricalDtype(ordered=True) を使うと、 「低 < 中 < 高」のような順序を型レベルで指定でき、 ソート・比較・groupby・カットオフが順序通りに動作する。 SSDSE-B-2026 で高齢化レベルを順序付きカテゴリとして宣言してみる。
🎯 このコードでやること: CategoricalDtype で順序を明示し、 順序付き比較・groupby 順序保持・並べ替えを確認する。
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)) |
📤 実行結果(参考値):
💬 結果の読み方: 2023 年の実測では高齢化レベルが「高」の群ほど合計特殊出生率の平均が高い(1.23 → 1.34)── 「高齢化が進むと出生率が低い」という素朴な予想とは逆で、 高齢化率が低い群に東京など低出生率の大都市が集まるためである(生態学的相関の好例)。 ordered=True 指定により >= 比較・自動ソートが意味通りに動作する。 順序を持たない名義カテゴリには ordered=False(デフォルト)を使う。
SSDSE-B-2026 の Code 列(R01000, R02000, …)は文字列。 これを「カテゴリ変数として扱う」「数値変換する」「数値部分を抽出して連続値扱いする」で何が起きるか比較。
int(code[1:3]) → 1, 2, ..., 47。 これも順序意味なし、 NG結論: 都道府県は「名義カテゴリ」なので OneHot 一択(線形)または CatBoost 内部処理(木)。
連続値を「cut で等幅ビン」「qcut で等頻度ビン」「ドメイン知識で固定境界」の 3 方式で比較。 SSDSE-B-2026 の B4101(年平均気温)を 4 段階「寒冷/温和/温暖/亜熱帯」に分ける。
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) は値域を均等分割するため、 分布が偏ると一部カテゴリにサンプル集中(亜熱帯は沖縄 1 県のみ)。 等頻度 (qcut) は件数がほぼ均等になる(同値の県があるため厳密には揃わない)。 ドメイン固定は「気候区分の意味」が保たれ最も解釈しやすいが、 境界決定にドメイン知識が必要(この境界例では 2023 年に 10℃未満の県が無く「寒冷」が 0 件になる ── 境界の再設計が必要という教訓も得られる)。 場面で使い分け。
実データには「東京都」「東京」「Tokyo」「とうきょう」のような表記揺れが残る。 「都」「府」「県」の suffix を統一処理する関数を書く。 SSDSE-B-2026 の Prefecture 列は綺麗だが、 実務では必須スキル。
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) |
📤 実行結果:
💬 結果の読み方: 「東京都/東京/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」が実用的。
いずれもまだ研究段階だが、 「カテゴリ変数を上手く扱える人」は実装・研究の両方で価値が高い。 本ページの基礎概念がそのままベースになる。
[判断フロー] カテゴリ数 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
関連概念を視覚的に整理した概念マップ。
中心のカテゴリ変数から、 (上) 名義 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」カテゴリを訓練に混ぜる
factor() 型はこの時代の遺産「カテゴリ変数」を扱う際の手法選択は、 状況に応じて以下のフローで判断すると迷いが減る。
このフローに沿って判断することで、 「カテゴリ変数」を中核とした適切な手法選択ができる。
本ページの他の節は「もとからカテゴリな変数(都道府県・地域区分)をどう数値化・集計するか」を扱った。 ここでは逆向き、 連続変数をカテゴリ変数へ変換する(ビニング/離散化)という、 実データ分析で最も事故が起きやすい操作を独自角度で深掘りする。 「小・中・大」のようなカテゴリは分析者が作り出したものであって、 データに最初から存在するわけではない、 という点が核心だ。
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,000 | 38 | 8 | 1 |
| 等頻度(分位点) | 537,000 / 1,170,000 / 1,979,666 / 14,086,000 | 16 | 15 | 16 |
等幅では「大」がたった 1 県(東京都)、 「小」が 38 県に偏る(実測 38 / 8 / 1)。 東京都という外れ値が横軸の幅を独り占めするためだ。 一方、 分位点なら 16 / 15 / 16 とほぼ均等になる(実測)。 どちらも「正しい」カテゴリ化だが、 後段の集計・クロス表・カイ二乗検定の結論はカテゴリの中身に引きずられて変わる。 「人口規模カテゴリで層別したら差が出た/出なかった」という主張は、 切り方を明示しない限り再現も反証もできない。 これがビニング最大の落とし穴で、 前段の カイ二乗検定 や Cramér V もこの恣意性を土台に載っていることを忘れてはいけない。
ビニングで生まれるのは順序尺度のカテゴリ変数なので、 One-Hot ではなく順序を保つ Ordinal エンコーディングが基本(本ページ数式節の①②を参照)。 とはいえ連続変数をわざわざカテゴリ化すること自体が情報の損失であり、 木系モデル(決定木・勾配ブースティング)は連続量のまま最適な分割点を学習するので、 人手のビニングはしばしば不要かつ有害になる。 カテゴリ化が正当化されるのは、 (1) 非線形な閾値効果が既知(例: 法定の年齢区分)、 (2) 非専門家への提示で解釈性を優先、 (3) 外れ値の影響を頑健に丸めたい、 といった場合に限られる。 迷ったら「まず連続量のまま ヒストグラム で分布を見る → 本当にカテゴリが必要か問い直す」という順番を守るとよい。