前処理(1):列を選び、おかしな値と欠損を直す
レッスン2の点検で見つけたことを、モデルが使える形に直します。
1. 使う列を選ぶ
X = df.drop(columns=["受講者ID", "修了証までの日数", "途中でやめた"]) # 特徴量
y = df["途中でやめた"] # 正解
2. ありえない値は、欠損として扱う
学習時間の 99999分は、入力の誤りです。本当の値は分からないので、消してしまわずに「分からない値(欠損)」に置き換え、ほかの欠損と一緒に扱います。
import numpy as np
too_long = df["最初の週の学習時間"] > 7 * 24 * 60 # 1週間の分数を超える
df.loc[too_long, "最初の週の学習時間"] = np.nan # 欠損(NaN)に置き換える
np.nan は、「数ではない値」を表し、pandas では欠損として扱われます。
3. 欠損を埋める
第3章では、欠損を平均で埋める方法を学びました。ただし、平均は、ありえない値や極端な値に強く引っ張られます(レッスン2では、15件の誤りで平均が117.1分から660.3分になりました)。そこで、中央値 で埋めることがよくあります。
埋める値(中央値)は、訓練データだけから 求めます。テストデータも含めた全体の中央値を使うと、テストデータの情報がモデル作りに紛れ込みます(第9章のデータリーク)。次のスライドの Pipeline を使うと、これを自動で守れます。
欠損を埋めるほかに、「欠損していたかどうか」を表す列(0か1)を足す方法もあります。欠損していること自体に意味がある場合(例えば、答えたくない質問への空欄)に役立ちます。
前処理(2):文字の列と数の列を、まとめて処理する
文字の列は、one-hot にする
「端末」の「PC・スマホ・タブレット」のような文字は、そのままではモデルに入れられません。第12章の one-hot と同じく、「PCか(0/1)」「スマホか(0/1)」「タブレットか(0/1)」の3列に変えます。scikit-learn では OneHotEncoder が行います。
端末(3種類)・申し込みの経路(3種類)・年代(4種類)で10列、数の列が5列で、合わせて 15列 の特徴量になります。
列の種類ごとに、違う処理をする:ColumnTransformer
数の列には「欠損を中央値で埋めて、標準化する」、文字の列には「one-hot にする」と、列の種類ごとに違う処理をします。これをまとめるのが make_column_transformer です。さらに、モデルと make_pipeline でつなぎます。
from sklearn.compose import make_column_transformer
from sklearn.pipeline import make_pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.linear_model import LogisticRegression
num = ["最初の週の学習時間", "最初の週のレッスン数", "最初の週の正解数", "ヒントを見た割合", "連続学習日数"]
cat = ["端末", "申し込みの経路", "年代"]
pre = make_column_transformer(
(make_pipeline(SimpleImputer(strategy="median"), StandardScaler()), num), # 数の列
(OneHotEncoder(handle_unknown="ignore"), cat), # 文字の列
)
model = make_pipeline(pre, LogisticRegression(max_iter=1000))
model.fit(X_train, y_train) # 中央値・平均・標準偏差・文字の種類は、訓練データだけから決まる
SimpleImputer(strategy="median"): 欠損を、訓練データの中央値で埋める
OneHotEncoder(handle_unknown="ignore"): 訓練データになかった文字(例えば、後から増えた「ゲーム機」)が来ても、エラーにせず、どの列も0にする
こうしてまとめておくと、fit のときは訓練データだけから処理の値を決め、predict のときは同じ値でテストデータを処理します。交差検証(第8章)でも、分割ごとに正しく処理されます。
実験:前処理を変えると、どれくらい変わるか
テストデータ(900人)で、ロジスティック回帰の結果を比べました。
リークの列を加えても、中央値で埋めるとF値は0.793で、ほとんど変わりませんでした。この列の「答え」は、値の大きさではなく 空欄かどうか に入っていたからです(空欄の人は全員やめた人)。中央値で埋めると空欄が消え、答えも消えます。ところが、「欠損していたか」の列を足すと、答えがそのまま戻り、1.000になりました。リークは、前処理の仕方しだいで、隠れたり表れたりします。結果が変わらなかったから大丈夫、ではなく、使ってはいけない列は最初に取り除きます。
このデータでは、ありえない値の処理や文字の列の効果は、F値で0.01ほどの小さな差でした。前処理は、いつも大きく結果を変えるわけではありません。それでも、ありえない値を残すと平均などの読み方を誤りますし、リークの列は、前処理しだいで、テストでは完ぺきなのに本番では使えないモデルを生みます。結果の差が小さくても、正しい手順で処理する ことが大切です。