STEP 26:特徴量の変換とエンコーディング

🔄 STEP 26: 特徴量の変換とエンコーディング

数値データのスケーリングとカテゴリカルデータの変換方法を習得

📋 このステップで学ぶこと

  • なぜ特徴量の変換が必要か
  • 数値特徴量のスケーリング(StandardScaler、MinMaxScaler、RobustScaler)
  • カテゴリカル特徴量のエンコーディング(Label Encoding、One-Hot Encoding)
  • Target Encoding(ターゲットエンコーディング)
  • ColumnTransformerで異なる変換をまとめる

演習問題: 7問

🎯 1. なぜ特徴量の変換が必要か

機械学習モデルに渡すデータは、そのままでは使えないことがよくあります。特徴量の変換が必要な理由を理解しましょう。

問題1:スケールの違い

⚠️ スケールが異なるとどうなる?

例えば「年齢(20〜80)」と「年収(200万〜2000万)」を一緒に使う場合、
年収の方が数値が大きいため、モデルが年収を過度に重視してしまいます。

解決策:スケーリングで全ての特徴量を同じ尺度に揃える

問題2:カテゴリカルデータ

⚠️ 文字列データはそのまま使えない

「性別(男/女)」「都道府県(東京/大阪/…)」などの文字列データは、
機械学習モデルに直接渡すことができません。

解決策:エンコーディングで数値に変換する

スケーリングが必要なアルゴリズム

スケーリング アルゴリズム 理由
必要 ロジスティック回帰、SVM、KNN、ニューラルネット、PCA 距離や勾配を計算するため、スケールの影響を受ける
不要 決定木、ランダムフォレスト、XGBoost 木の分岐はスケールに依存しない

📊 2. 数値特徴量のスケーリング

数値データを適切な範囲に変換するスケーリング手法を3つ学びます。

StandardScaler(標準化)

📊 StandardScalerとは

データを平均0、標準偏差1に変換します。
計算式:(x - 平均) / 標準偏差

使いどころ:最も一般的。正規分布に近いデータに適している。

StandardScalerの使い方を順番に見ていきましょう。
  • fit():データから平均と標準偏差を計算(学習)
  • transform():計算した統計量を使ってデータを変換
  • fit_transform():fitとtransformを一度に実行
# StandardScalerの使い方 # ライブラリをインポート from sklearn.preprocessing import StandardScaler import numpy as np # サンプルデータを作成 # 2つの特徴量:年齢(20〜50)と年収(200〜800万) X = np.array([ [25, 300], # 25歳、年収300万 [30, 400], # 30歳、年収400万 [35, 500], # 35歳、年収500万 [40, 600], # 40歳、年収600万 [50, 800] # 50歳、年収800万 ]) print(“変換前のデータ:”) print(X) print(f”年齢の範囲: {X[:, 0].min()} 〜 {X[:, 0].max()}”) print(f”年収の範囲: {X[:, 1].min()} 〜 {X[:, 1].max()}”)
# StandardScalerでスケーリング # スケーラーを作成 scaler = StandardScaler() # fit_transform()でデータを変換 # fit(): 平均と標準偏差を計算 # transform(): その統計量を使って変換 X_scaled = scaler.fit_transform(X) print(“\n変換後のデータ(StandardScaler):”) print(X_scaled.round(2)) # 変換後の統計を確認 print(f”\n変換後の平均: {X_scaled.mean(axis=0).round(4)}”) # ほぼ0 print(f”変換後の標準偏差: {X_scaled.std(axis=0).round(4)}”) # ほぼ1
変換前のデータ: [[ 25 300] [ 30 400] [ 35 500] [ 40 600] [ 50 800]] 年齢の範囲: 25 〜 50 年収の範囲: 300 〜 800 変換後のデータ(StandardScaler): [[-1.34 -1.34] [-0.8 -0.8 ] [-0.27 -0.27] [ 0.27 0.27] [ 1.34 1.34]] 変換後の平均: [0. 0.] 変換後の標準偏差: [1. 1.]

MinMaxScaler(正規化)

📊 MinMaxScalerとは

データを0〜1の範囲に変換します。
計算式:(x - 最小値) / (最大値 - 最小値)

使いどころ:0〜1の範囲が必要な場合(画像データなど)

# MinMaxScalerの使い方 from sklearn.preprocessing import MinMaxScaler # スケーラーを作成 minmax_scaler = MinMaxScaler() # データを変換 X_minmax = minmax_scaler.fit_transform(X) print(“変換後のデータ(MinMaxScaler):”) print(X_minmax.round(2)) # 範囲を確認 print(f”\n変換後の最小値: {X_minmax.min(axis=0)}”) # 0 print(f”変換後の最大値: {X_minmax.max(axis=0)}”) # 1
変換後のデータ(MinMaxScaler): [[0. 0. ] [0.2 0.2 ] [0.4 0.4 ] [0.6 0.6 ] [1. 1. ]] 変換後の最小値: [0. 0.] 変換後の最大値: [1. 1.]

RobustScaler(外れ値に強い)

📊 RobustScalerとは

中央値と四分位範囲(IQR)を使ってスケーリングします。
計算式:(x - 中央値) / IQR

使いどころ:外れ値が多いデータ(外れ値の影響を受けにくい)

# RobustScalerの使い方(外れ値がある場合) from sklearn.preprocessing import RobustScaler # 外れ値を含むデータを作成 X_with_outlier = np.array([ [25, 300], [30, 400], [35, 500], [40, 600], [100, 5000] # 外れ値! ]) print(“外れ値を含むデータ:”) print(X_with_outlier)
# StandardScalerとRobustScalerを比較 standard_scaler = StandardScaler() robust_scaler = RobustScaler() X_standard = standard_scaler.fit_transform(X_with_outlier) X_robust = robust_scaler.fit_transform(X_with_outlier) print(“\nStandardScaler(外れ値の影響を受ける):”) print(X_standard.round(2)) print(“\nRobustScaler(外れ値の影響を受けにくい):”) print(X_robust.round(2))
外れ値を含むデータ: [[ 25 300] [ 30 400] [ 35 500] [ 40 600] [ 100 5000]] StandardScaler(外れ値の影響を受ける): [[-0.73 -0.64] [-0.55 -0.58] [-0.37 -0.52] [-0.18 -0.46] [ 1.83 2.2 ]] RobustScaler(外れ値の影響を受けにくい): [[-1. -1. ] [-0.5 -0.5 ] [ 0. 0. ] [ 0.5 0.5 ] [ 6.5 22.5 ]]
✅ スケーラーの選び方まとめ
StandardScaler一般的な場合、正規分布に近いデータ
MinMaxScaler0〜1の範囲が必要な場合
RobustScaler外れ値が多いデータ

🏷️ 3. カテゴリカル特徴量のエンコーディング

文字列(カテゴリカル)データを数値に変換する方法を学びます。

Label Encoding(ラベルエンコーディング)

📊 Label Encodingとは

カテゴリを0, 1, 2, …の整数に変換します。

使いどころ:順序があるカテゴリ(小 < 中 < 大)、目的変数の変換
注意:順序がないカテゴリに使うと、モデルが誤った順序関係を学習する可能性あり

Label Encodingの使い方:
  • fit():カテゴリの種類を学習
  • transform():カテゴリを数値に変換
  • inverse_transform():数値を元のカテゴリに戻す
# Label Encodingの使い方 from sklearn.preprocessing import LabelEncoder import pandas as pd # サンプルデータを作成 df = pd.DataFrame({ ‘サイズ’: [‘S’, ‘M’, ‘L’, ‘M’, ‘S’, ‘L’], ‘色’: [‘赤’, ‘青’, ‘赤’, ‘緑’, ‘青’, ‘赤’] }) print(“元のデータ:”) print(df)
# サイズ列をLabel Encoding # サイズには順序がある(S < M < L)ので適している le_size = LabelEncoder() # fit_transform()でエンコード df['サイズ_encoded'] = le_size.fit_transform(df['サイズ']) print("\nLabel Encoding後:") print(df) # どのカテゴリがどの数値に対応するか確認 print(f"\nカテゴリと数値の対応: {dict(zip(le_size.classes_, range(len(le_size.classes_))))}")
元のデータ: サイズ 色 0 S 赤 1 M 青 2 L 赤 3 M 緑 4 S 青 5 L 赤 Label Encoding後: サイズ 色 サイズ_encoded 0 S 赤 2 1 M 青 1 2 L 赤 0 3 M 緑 1 4 S 青 2 5 L 赤 0 カテゴリと数値の対応: {‘L’: 0, ‘M’: 1, ‘S’: 2}
⚠️ Label Encodingの注意点

上の例では「L=0, M=1, S=2」となり、サイズの大小関係が逆になっています。
これはアルファベット順でエンコードされるためです。

順序を指定したい場合は、手動でマッピングを定義する方が安全です。

One-Hot Encoding(ワンホットエンコーディング)

📊 One-Hot Encodingとは

各カテゴリを0と1だけの列に変換します。
該当するカテゴリの列だけが1、それ以外は0になります。

使いどころ:順序がないカテゴリ(色、国など)
注意:カテゴリ数が多いと列が増えすぎる

# One-Hot Encodingの使い方(pandas.get_dummies) # 色列をOne-Hot Encoding # 色には順序がない(赤 > 青 とは言えない)ので、One-Hotが適切 df_onehot = pd.get_dummies(df, columns=[‘色’]) print(“One-Hot Encoding後:”) print(df_onehot)
One-Hot Encoding後: サイズ サイズ_encoded 色_緑 色_赤 色_青 0 S 2 0 1 0 1 M 1 0 0 1 2 L 0 0 1 0 3 M 1 1 0 0 4 S 2 0 0 1 5 L 0 0 1 0
One-Hot Encodingの読み方:

行0を見ると「色_赤=1」なので、この行は「赤」です。
行3を見ると「色_緑=1」なので、この行は「緑」です。
各行で1つだけが1になります。

# sklearn.OneHotEncoderを使う方法(機械学習パイプラインで推奨) from sklearn.preprocessing import OneHotEncoder # OneHotEncoderを作成 # sparse_output=False: 密な配列を返す(見やすい) # handle_unknown=’ignore’: 未知のカテゴリを無視 ohe = OneHotEncoder(sparse_output=False, handle_unknown=’ignore’) # 色のデータを2次元配列に変換して渡す colors = df[[‘色’]] # One-Hot Encoding colors_encoded = ohe.fit_transform(colors) print(“OneHotEncoder使用後:”) print(colors_encoded) print(f”\nカテゴリ: {ohe.categories_[0]}”)
OneHotEncoder使用後: [[0. 1. 0.] [0. 0. 1.] [0. 1. 0.] [1. 0. 0.] [0. 0. 1.] [0. 1. 0.]] カテゴリ: [‘緑’ ‘赤’ ‘青’]
エンコーディング 変換方法 使いどころ 注意点
Label Encoding カテゴリ→整数 順序があるカテゴリ、目的変数 順序がないと誤学習の可能性
One-Hot Encoding カテゴリ→0/1の複数列 順序がないカテゴリ カテゴリ数が多いと列が増える

🎯 4. Target Encoding(ターゲットエンコーディング)

📊 Target Encodingとは

カテゴリを、そのカテゴリにおける目的変数の平均値に置き換えます。

例:「東京」カテゴリの平均年収が500万なら、「東京」→500に変換

メリット:カテゴリ数が多くても1列で済む
注意:データリークのリスクがある(交差検証での対策が必要)

基本的なTarget Encoding(問題あり)

# Target Encodingの実装例(基本版 – データリークあり) import pandas as pd import numpy as np # サンプルデータを作成 df = pd.DataFrame({ ‘都市’: [‘東京’, ‘大阪’, ‘東京’, ‘名古屋’, ‘大阪’, ‘東京’, ‘名古屋’, ‘大阪’], ‘年収’: [600, 400, 550, 450, 380, 620, 420, 410] }) print(“元のデータ:”) print(df) # 都市ごとの年収の平均を計算 city_mean = df.groupby(‘都市’)[‘年収’].mean() print(f”\n都市ごとの平均年収:\n{city_mean}”)
# Target Encodingを適用 # map()で都市を平均年収に置き換え df[‘都市_target_encoded’] = df[‘都市’].map(city_mean) print(“\nTarget Encoding後:”) print(df)
元のデータ: 都市 年収 0 東京 600 1 大阪 400 2 東京 550 3 名古屋 450 4 大阪 380 5 東京 620 6 名古屋 420 7 大阪 410 都市ごとの平均年収: 都市 名古屋 435.000000 大阪 396.666667 東京 590.000000 Name: 年収, dtype: float64 Target Encoding後: 都市 年収 都市_target_encoded 0 東京 600 590.000000 1 大阪 400 396.666667 2 東京 550 590.000000 3 名古屋 450 435.000000 4 大阪 380 396.666667 5 東京 620 590.000000 6 名古屋 420 435.000000 7 大阪 410 396.666667
⚠️ データリーク問題

上のコードでは、変換対象のデータ自身を使って平均を計算しています。
これはデータリーク(本来知りえない情報を使うこと)になります。

例えば、「東京」の平均年収590万を計算する際に、予測したい「東京」のデータ自身も含まれています。これは「カンニング」と同じです。

データリークを防ぐTarget Encoding

交差検証の各フォールドで、訓練データのみから平均を計算します。

# データリークを防ぐTarget Encoding from sklearn.model_selection import KFold import pandas as pd import numpy as np # サンプルデータ df = pd.DataFrame({ ‘都市’: [‘東京’, ‘大阪’, ‘東京’, ‘名古屋’, ‘大阪’, ‘東京’, ‘名古屋’, ‘大阪’], ‘年収’: [600, 400, 550, 450, 380, 620, 420, 410] }) # 結果を格納する列を初期化 df[‘都市_target_safe’] = 0.0 # K-Foldで分割 kf = KFold(n_splits=4, shuffle=True, random_state=42) for train_idx, val_idx in kf.split(df): # 訓練データのみから平均を計算(データリーク防止) train_data = df.iloc[train_idx] mean_map = train_data.groupby(‘都市’)[‘年収’].mean() # 検証データに適用 df.loc[val_idx, ‘都市_target_safe’] = df.loc[val_idx, ‘都市’].map(mean_map) # 未知のカテゴリは全体平均で補完 df.loc[val_idx, ‘都市_target_safe’] = df.loc[val_idx, ‘都市_target_safe’].fillna( train_data[‘年収’].mean() ) print(“データリーク対策済みTarget Encoding:”) print(df[[‘都市’, ‘年収’, ‘都市_target_safe’]])
✅ データリーク対策のポイント
  • 各フォールドで、検証データには含まれない訓練データのみから平均を計算
  • これにより、予測時に「未知のデータ」として扱われる
  • 未知のカテゴリ(訓練データにない都市)は全体平均で補完

🔧 5. ColumnTransformerで複数の変換をまとめる

実際のデータでは、数値列とカテゴリカル列が混在しています。ColumnTransformerを使うと、列ごとに異なる変換を一度に適用できます。

なぜColumnTransformerを使うのか?

📊 ColumnTransformerのメリット
  • 一括処理:数値列にはスケーリング、カテゴリ列にはエンコーディングを同時に適用
  • Pipeline統合:前処理とモデルを1つのパイプラインにまとめられる
  • データリーク防止:交差検証で各フォールドの訓練データのみから統計量を計算
  • 再現性:本番環境でも同じ前処理を適用できる

ColumnTransformerの構造を理解しよう

ColumnTransformerの主な引数:

  • transformers:変換のリスト。各要素は(名前, 変換器, 対象列)のタプル
  • remainder='drop':指定外の列の扱い
    • 'drop':削除(デフォルト)
    • 'passthrough':そのまま残す
# ColumnTransformerの使い方 from sklearn.compose import ColumnTransformer from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.pipeline import Pipeline from sklearn.linear_model import LogisticRegression import pandas as pd import numpy as np # 数値列とカテゴリカル列が混在するデータを作成 df = pd.DataFrame({ ‘年齢’: [25, 30, 35, 40, 45], ‘年収’: [300, 450, 500, 600, 700], ‘性別’: [‘男’, ‘女’, ‘男’, ‘女’, ‘男’], ‘職種’: [‘営業’, ‘技術’, ‘技術’, ‘営業’, ‘管理’], ‘購入’: [0, 1, 1, 0, 1] # 目的変数 }) print(“混在データ:”) print(df) # 列の分類(どの列が数値で、どの列がカテゴリか明示) numerical_cols = [‘年齢’, ‘年収’] # 数値列 categorical_cols = [‘性別’, ‘職種’] # カテゴリカル列
# ColumnTransformerを作成 # transformers引数に (名前, 変換器, 対象列) のリストを渡す preprocessor = ColumnTransformer( transformers=[ # 数値列にはStandardScalerを適用 (‘num’, StandardScaler(), numerical_cols), # カテゴリ列にはOneHotEncoderを適用 # handle_unknown=’ignore’: 未知のカテゴリを無視(エラーにしない) (‘cat’, OneHotEncoder(handle_unknown=’ignore’), categorical_cols) ], remainder=’drop’ # 指定外の列は削除(’passthrough’だと残す) ) # 特徴量と目的変数に分割 X = df[numerical_cols + categorical_cols] y = df[‘購入’] # 変換を実行 X_transformed = preprocessor.fit_transform(X) print(“\n変換後のデータ:”) print(X_transformed.round(2)) print(f”\n変換後の形状: {X_transformed.shape}”) # 数値2列 + 性別2カテゴリ + 職種3カテゴリ = 7列

パイプラインと組み合わせる

ColumnTransformerをPipelineに組み込むと、前処理とモデルを一括管理できます。

# ColumnTransformerをPipelineに組み込む # 前処理とモデルを1つのパイプラインにまとめる pipeline = Pipeline([ (‘preprocessor’, preprocessor), # 前処理(ColumnTransformer) (‘classifier’, LogisticRegression()) # モデル ]) # これで、生データを直接fit()に渡せる! # パイプライン内部で自動的に前処理が適用される pipeline.fit(X, y) # 予測も生データを直接渡せる predictions = pipeline.predict(X) print(“予測結果:”, predictions) print(“正解: “, y.values)
✅ パイプラインのメリット
  • 前処理とモデルを一貫して管理できる
  • 交差検証でもデータリークを防げる(CVの各フォールドで前処理が独立)
  • 本番環境でも同じ前処理が適用される

📝 練習問題

問題1 やさしい

スケーリングの目的

特徴量のスケーリングが必要な主な理由は何ですか?

理由:特徴量間のスケールの違いを揃えるため。スケールが大きい特徴量がモデルに過度に影響することを防ぎます。
問題2 やさしい

スケーラーの選択

外れ値が多いデータにはどのスケーラーが適していますか?

正解:RobustScaler

RobustScalerは中央値と四分位範囲を使うため、外れ値の影響を受けにくいです。

問題3 ふつう

エンコーディングの選択

「血液型(A, B, O, AB)」のエンコーディングには、Label EncodingとOne-Hot Encodingのどちらが適切ですか?理由も説明してください。

正解:One-Hot Encoding

血液型には順序がありません(A > B とは言えない)。Label Encodingを使うと、モデルが「AはBより小さい」という誤った関係を学習する可能性があります。

問題4 ふつう

StandardScalerの実装

データをStandardScalerでスケーリングするコードを書いてください。

from sklearn.preprocessing import StandardScaler import numpy as np # サンプルデータ X = np.array([[100, 0.1], [200, 0.2], [300, 0.3]]) # スケーラーを作成 scaler = StandardScaler() # fit_transform()で変換 X_scaled = scaler.fit_transform(X) print(“変換後:”, X_scaled)
問題5 むずかしい

ColumnTransformerの実装

数値列にStandardScaler、カテゴリ列にOneHotEncoderを適用するColumnTransformerを作成してください。

from sklearn.compose import ColumnTransformer from sklearn.preprocessing import StandardScaler, OneHotEncoder # 列の定義 numerical_cols = [‘age’, ‘income’] categorical_cols = [‘gender’, ‘city’] # ColumnTransformerを作成 preprocessor = ColumnTransformer( transformers=[ (‘num’, StandardScaler(), numerical_cols), (‘cat’, OneHotEncoder(handle_unknown=’ignore’), categorical_cols) ] ) # 使用例 X_transformed = preprocessor.fit_transform(df)
問題6 むずかしい

Target Encodingの実装

以下のデータで、カテゴリ列「血液型」を目的変数「購入額」の平均でTarget Encodingしてください。

df = pd.DataFrame({ ‘血液型’: [‘A’, ‘B’, ‘A’, ‘O’, ‘AB’, ‘A’, ‘B’, ‘O’], ‘購入額’: [1000, 1500, 1200, 800, 2000, 900, 1600, 750] })
import pandas as pd df = pd.DataFrame({ ‘血液型’: [‘A’, ‘B’, ‘A’, ‘O’, ‘AB’, ‘A’, ‘B’, ‘O’], ‘購入額’: [1000, 1500, 1200, 800, 2000, 900, 1600, 750] }) # 血液型ごとの購入額の平均を計算 blood_mean = df.groupby(‘血液型’)[‘購入額’].mean() print(“血液型ごとの平均購入額:”) print(blood_mean) # Target Encodingを適用 df[‘血液型_encoded’] = df[‘血液型’].map(blood_mean) print(“\nTarget Encoding後:”) print(df) # 結果: # 血液型 購入額 血液型_encoded # A 1000 1033.33 (A型の平均) # B 1500 1550.00 (B型の平均) # …

注意:本番では交差検証でデータリークを防ぐ必要があります。

問題7 むずかしい

データリークを防ぐTarget Encoding

K-Foldを使って、データリークを防いだTarget Encodingを実装してください。

from sklearn.model_selection import KFold import pandas as pd import numpy as np df = pd.DataFrame({ ‘血液型’: [‘A’, ‘B’, ‘A’, ‘O’, ‘AB’, ‘A’, ‘B’, ‘O’], ‘購入額’: [1000, 1500, 1200, 800, 2000, 900, 1600, 750] }) # 結果を格納する列を初期化 df[‘血液型_safe_encoded’] = 0.0 # K-Foldで分割(4分割) kf = KFold(n_splits=4, shuffle=True, random_state=42) for train_idx, val_idx in kf.split(df): # 訓練データのみから平均を計算(データリーク防止) train_data = df.iloc[train_idx] mean_map = train_data.groupby(‘血液型’)[‘購入額’].mean() # 検証データに適用 df.loc[val_idx, ‘血液型_safe_encoded’] = df.loc[val_idx, ‘血液型’].map(mean_map) # 訓練データにないカテゴリは全体平均で補完 global_mean = train_data[‘購入額’].mean() df.loc[val_idx, ‘血液型_safe_encoded’] = df.loc[val_idx, ‘血液型_safe_encoded’].fillna(global_mean) print(“データリーク対策済みTarget Encoding:”) print(df[[‘血液型’, ‘購入額’, ‘血液型_safe_encoded’]])

ポイント:

  • 各フォールドで、検証データには含まれない訓練データのみから平均を計算
  • 未知のカテゴリは全体平均で補完
  • これにより、本番環境でも正しく動作する

📝 STEP 26 のまとめ

✅ このステップで学んだこと
  • スケーリングの必要性:特徴量のスケールを揃えてモデルの学習を安定化
  • StandardScaler:平均0、標準偏差1に変換(最も一般的)
  • MinMaxScaler:0〜1の範囲に変換
  • RobustScaler:外れ値に強い変換
  • Label Encoding:順序があるカテゴリを整数に変換
  • One-Hot Encoding:順序がないカテゴリを0/1の列に変換
  • Target Encoding:目的変数の平均で変換(データリーク対策が必要)
  • ColumnTransformer:異なる変換を列ごとにまとめて適用

❓ よくある質問

Q1. テストデータのスケーリングはどうする?
訓練データでfit()したスケーラーを使って、テストデータはtransform()のみ実行します。テストデータでfit()してはいけません(データリークになる)。
Q2. One-Hotでカテゴリが増えすぎたら?
Target EncodingやFrequency Encoding(出現頻度でエンコード)を検討します。また、稀なカテゴリを「その他」にまとめる方法もあります。
📝

学習メモ

機械学習入門 - Step 26

📋 過去のメモ一覧
#artnasekai #学習メモ
LINE