メインコンテンツへスキップ

5.5.3 特徴量の前処理

特徴量前処理のパイプライン図

この節の位置づけ

特徴量の前処理は、「すべての手法を全部かける」ことではありません。モデル、データ、タスクに応じて選ぶことが大切です。本当に重要なのは、各ステップをなぜ行うのか、いつ行うべきでないのか、そしてどうやってデータリークを防ぐのかを理解することです。

学習目標

  • 欠損値、外れ値、スケーリング、エンコードがそれぞれ何を解決するのか理解する
  • モデルごとに標準化が必要かどうか判断できる
  • One-Hot、Ordinal Encoding、Target Encoding の適用範囲を知る
  • データリークを避ける基本的な意識を身につける

まず全体の地図を作ろう

この図はあくまでよくある順番であって、固定の手順ではありません。たとえば木系モデルは通常、標準化にあまり依存しませんが、線形モデル、KNN、SVM、ニューラルネットワークはスケーリングがより重要になることが多いです。

ここからの例をそのまま実行できるように準備する

後半のコードを単独で動かせるように、まずは小さな混合型データセットを用意します。欠損値、数値列、カテゴリ列がすべて入っています。

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split

df = pd.DataFrame({
"age": [25, np.nan, 39, 51, 45, np.nan, 33, 60],
"income": [50000, 62000, np.nan, 120000, 85000, 76000, 54000, 200000],
"amount": [80, 95, 120, 10000, 110, 130, 70, 150],
"city": ["A", "B", "A", "C", None, "B", "D", "A"],
"gender": ["F", "M", "F", "M", "F", None, "M", "F"],
"target": [0, 1, 0, 1, 0, 1, 0, 1],
})

X = df[["age", "income", "amount", "city", "gender"]]
y = df["target"]
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=42, stratify=y
)

一、欠損値処理

欠損値は、汚れたデータである場合もあれば、それ自体が情報になっている場合もあります。たとえば「ユーザーが会社名を入力していない」ことは一般個人ユーザーを意味するかもしれませんし、「健診項目が欠けている」ことは単なるシステム入力ミスかもしれません。処理する前に、まず「なぜ欠損しているのか」を考えましょう。

import pandas as pd

missing_rate = df.isna().mean().sort_values(ascending=False)
print(missing_rate)

よくある方法には、欠損が多すぎる列を削除する、数値特徴量には平均値や中央値で補完する、カテゴリ特徴量には最頻値や「unknown」で補完する、欠損しているかどうかを表すフラグ列を追加する、などがあります。いきなり全部に dropna() を使うのは避けましょう。大量のサンプルを失う可能性があります。

二、外れ値処理

外れ値が必ずしも間違いとは限りません。金融詐欺、機器の故障、極端な購買行動などは、むしろモデルが最も注目すべきサンプルかもしれません。外れ値を扱うときは、業務上の意味と合わせて考える必要があります。

q1 = df["amount"].quantile(0.25)
q3 = df["amount"].quantile(0.75)
iqr = q3 - q1
lower = q1 - 1.5 * iqr
upper = q3 + 1.5 * iqr
outliers = df[(df["amount"] < lower) | (df["amount"] > upper)]
print(outliers.head())

外れ値が入力ミスなら、修正または削除できます。もし外れ値が本当に珍しい行動を表しているなら、残したうえで、頑健なモデルやビン分割で扱うことを考えましょう。

三、数値のスケーリング:標準化が必要なのはいつか

標準化は、特徴量ごとの単位や大きさの違いが大きすぎる問題を解決します。たとえば年齢は数十ですが、収入は数万かもしれません。モデルが距離や勾配に依存する場合、スケール差は学習に影響します。

モデルの種類通常、スケーリングは必要か理由
線形回帰 / ロジスティック回帰必要なことを推奨勾配と正則化項がスケールの影響を受ける
KNN / SVM通常必要距離計算がスケールの影響を受ける
ニューラルネットワーク通常必要学習を安定させやすい
決定木 / ランダムフォレスト / GBDT通常不要しきい値で分割するため、単調なスケーリングに敏感ではない
from sklearn.preprocessing import StandardScaler

numeric_cols = ["age", "income", "amount"]
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train[numeric_cols])
X_test_scaled = scaler.transform(X_test[numeric_cols])
print(X_train_scaled[:2])

注意: fit は必ず訓練集だけで行い、そのあとでテスト集に transform します。全データで scaler を fit すると、テスト集の情報が学習に漏れてしまいます。

四、カテゴリのエンコード

カテゴリ特徴量は、そのままでは多くの伝統的なモデルに入力できないため、エンコードが必要です。もっともよく使われるのは One-Hot Encoding で、都市、色、職業のような順序のないカテゴリに向いています。

from sklearn.preprocessing import OneHotEncoder

encoder = OneHotEncoder(handle_unknown="ignore")
X_train_cat = encoder.fit_transform(X_train[["city"]])
X_test_cat = encoder.transform(X_test[["city"]])
print(X_train_cat.shape, X_test_cat.shape)

順序があるカテゴリには Ordinal Encoding が使えます。たとえば学歴や S / M / L のサイズです。ただし、順序のないカテゴリを適当に 0、1、2 に変換してはいけません。モデルが「数値が大きいほど上位」と誤解する可能性があります。

Target Encoding は高カーディナリティのカテゴリに有効な場合がありますが、データリークが起きやすいです。たとえば「各都市の平均CVR」で都市をエンコードする場合、必ず訓練折だけで計算し、全データのラベルを直接使わないようにしましょう。

五、Pipeline を使ってリークを防ぐ

もっとも安全な方法は、前処理とモデルを同じ Pipeline にまとめることです。そうすると、交差検証の各 fold で、前処理器はその fold の訓練部分だけで fit されます。

from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.linear_model import LogisticRegression

num_features = ["age", "income", "amount"]
cat_features = ["city", "gender"]

preprocess = ColumnTransformer([
("num", Pipeline([
("imputer", SimpleImputer(strategy="median")),
("scaler", StandardScaler()),
]), num_features),
("cat", Pipeline([
("imputer", SimpleImputer(strategy="most_frequent")),
("onehot", OneHotEncoder(handle_unknown="ignore")),
]), cat_features),
])

model = Pipeline([
("preprocess", preprocess),
("clf", LogisticRegression(max_iter=1000)),
])

model.fit(X_train, y_train)
print(model.score(X_test, y_test))

特徴量前処理 Pipeline の実行結果図

結果をリーク確認として読む

大事なのは前処理 step の数ではなく、欠損処理、スケーリング、エンコードが training split だけから学び、その後で test split を変換することです。

よくある間違い

1つ目の間違いは、すべてのモデルに標準化をかけることです。木系モデルでは通常不要で、かけても効果がないことがあります。2つ目の間違いは、訓練データとテストデータに分ける前に前処理をしてしまうことです。これはデータリークになります。3つ目の間違いは、カテゴリを適当に数字へ置き換えてしまい、存在しない大小関係をモデルに学習させることです。4つ目の間違いは、外れ値を消しすぎて、本当に価値のある極端なサンプルまで失うことです。

練習

  1. Titanic データセットを使って欠損率をそれぞれ集計し、各列の処理方針を考えてみましょう。
  2. 同じデータで LogisticRegression と RandomForest をそれぞれ学習し、標準化が両者に与える影響を比較しましょう。
  3. なぜ scaler は全量データではなく訓練集で fit するべきなのか説明しましょう。
  4. 高カーディナリティのカテゴリ特徴量を1つ選び、One-Hot と Target Encoding のそれぞれにどんなリスクがあるか考えましょう。

クリア基準

この節を学び終えたら、表形式データに対する前処理方針を自分で書けるようになり、各ステップの理由を説明でき、Pipeline を使ってデータリークを防げるようになり、あるモデルに標準化やカテゴリエンコードが本当に必要か判断できるようになっているはずです。