カテゴリカル変数のエンコーディング
田中VPoE:「NetShop の顧客データには『会員ランク(ゴールド、シルバー、ブロンズ)』や『利用デバイス(PC、スマホ、タブレット)』のような文字列データがある。機械学習モデルは数値しか扱えないから、これらを数値に変換する必要がある。」
あなた:「単純に番号をつければいいんですか? ゴールド=3、シルバー=2、ブロンズ=1 とか。」
田中VPoE:「それだと『ゴールドはブロンズの3倍』という意味がモデルに伝わってしまう。正しいエンコーディング手法を選ばないと、モデルが誤った解釈をしてしまうんだ。」
カテゴリカル変数の種類
| 種類 | 特徴 | 例 |
|---|---|---|
| 名義変数(Nominal) | 順序なし | デバイス種別、都道府県、支払方法 |
| 順序変数(Ordinal) | 順序あり | 会員ランク、満足度評価、教育レベル |
One-hot Encoding
名義変数に最も一般的な手法です。各カテゴリを独立した列(0/1)に変換します。
import pandas as pd
df = pd.DataFrame({
'device': ['PC', 'スマホ', 'タブレット', 'PC', 'スマホ'],
'payment': ['クレジット', '銀行振込', 'コンビニ', 'クレジット', 'クレジット'],
})
# pandas の get_dummies
df_encoded = pd.get_dummies(df, columns=['device', 'payment'], drop_first=True)
print(df_encoded)
# scikit-learn の OneHotEncoder
from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder(sparse_output=False, drop='first')
encoded = encoder.fit_transform(df[['device', 'payment']])
print(f"\n変換後の列名: {encoder.get_feature_names_out()}")
注意点
drop_first=Trueで多重共線性を回避(ダミー変数トラップ防止)- カテゴリ数が多いと列数が爆発する(高カーディナリティ問題)
Label Encoding
順序変数に適した手法です。カテゴリに整数を割り当てます。
from sklearn.preprocessing import LabelEncoder, OrdinalEncoder
# LabelEncoder(目的変数用)
le = LabelEncoder()
df['membership_encoded'] = le.fit_transform(df['membership_rank'])
# OrdinalEncoder(順序付き特徴量用)
rank_order = [['ブロンズ', 'シルバー', 'ゴールド', 'プラチナ']]
oe = OrdinalEncoder(categories=rank_order)
df['rank_encoded'] = oe.fit_transform(df[['membership_rank']])
print(df[['membership_rank', 'rank_encoded']])
注意点
- 名義変数に Label Encoding を使うと、存在しない順序関係をモデルが学習してしまう
- 決定木系のモデルでは Label Encoding でも問題ない場合がある
Target Encoding
カテゴリごとの目的変数の平均値でエンコードする手法です。
# Target Encoding の実装
def target_encode(train_df, val_df, col, target, smoothing=10):
"""ターゲットエンコーディング(スムージング付き)"""
global_mean = train_df[target].mean()
agg = train_df.groupby(col)[target].agg(['mean', 'count'])
# スムージング: データが少ないカテゴリは全体平均に近づける
smooth = (agg['count'] * agg['mean'] + smoothing * global_mean) / (agg['count'] + smoothing)
train_encoded = train_df[col].map(smooth)
val_encoded = val_df[col].map(smooth).fillna(global_mean)
return train_encoded, val_encoded
# 使用例
# 都道府県ごとの離反率でエンコード
train_encoded, val_encoded = target_encode(
train_df, val_df, 'prefecture', 'is_churned'
)
注意点
- データリーケージ: 検証/テストデータの情報が漏れないよう、学習データのみで計算する
- スムージング: データ数が少ないカテゴリの過学習を防ぐ
- 交差検証と組み合わせて使うのがベストプラクティス
高カーディナリティ対策
カテゴリ数が多い(例: 商品ID が数千種類)場合の対策です。
# 方法1: 頻度の低いカテゴリをまとめる
threshold = 50
freq = df['product_category'].value_counts()
rare_categories = freq[freq < threshold].index
df['product_category_grouped'] = df['product_category'].replace(
rare_categories, 'その他'
)
# 方法2: 頻度エンコーディング
freq_map = df['product_category'].value_counts(normalize=True)
df['category_freq'] = df['product_category'].map(freq_map)
# 方法3: Target Encoding(上述)
# カテゴリ数に関わらず1列にエンコードできる
エンコーディング手法の選択ガイド
| 条件 | 推奨手法 |
|---|---|
| 名義変数、カテゴリ数が少ない(10未満) | One-hot Encoding |
| 順序変数 | Ordinal Encoding |
| カテゴリ数が多い | Target Encoding or 頻度 Encoding |
| 決定木系モデル | Label Encoding でも可 |
| 線形モデル | One-hot Encoding |
まとめ
- One-hot Encoding: 名義変数の標準手法、カテゴリ数が少ない場合に有効
- Label Encoding: 順序変数に適用、名義変数への使用は注意
- Target Encoding: 高カーディナリティに有効、データリーケージに注意
- モデルの種類と変数の性質に応じてエンコーディング手法を選択する
チェックリスト
- 名義変数と順序変数の違いを理解した
- One-hot Encoding のコードを書ける
- Target Encoding のデータリーケージリスクを理解した
- 高カーディナリティへの対処法を把握した
次のステップへ
次のレッスンでは、数値特徴量のスケーリングと正規化を学びます。特徴量のスケールがモデルに与える影響を理解しましょう。
推定読了時間: 30分