機械学習データ前処理:どんな順番で行う?典型的なデータ前処理のステップ【Pandas入門】
機械学習モデルを構築する際、データ前処理はモデルの性能を大きく左右する非常に重要な工程です。これまで、欠損値の処理方法やカテゴリ変数の扱い方など、様々なデータ前処理の手法について解説してきました。
しかし、「これらの手法を、実際のデータに対してどのような順番で適用すれば良いのだろうか?」と疑問に感じている方もいらっしゃるかもしれません。個別の手法は理解できても、データ分析プロジェクト全体の流れの中で、データ前処理がどのように位置づけられ、どのようなステップで進めるのが一般的なのかを知ることは、分析をスムーズに進める上で非常に役立ちます。
この記事では、機械学習プロジェクトにおけるデータ前処理の典型的な流れと、各ステップでどのような処理を行うのかについて、体系的に解説します。特定のデータに対する完璧な正解手順があるわけではありませんが、ここで紹介する標準的なステップを理解することで、ご自身のデータ分析プロジェクトで迷うことなく前処理を進めるための強力な指針を得られるでしょう。
1. データ前処理の全体像:なぜ「流れ」が重要なのか
機械学習モデルは、「入力されたデータ」からパターンを学習し、予測や分類を行います。このとき、入力されるデータが不完全だったり、形式が不揃いだったりすると、モデルは正しく学習できません。データ前処理は、このような生データをモデルが扱える高品質な形に変換する作業全般を指します。
データ前処理には様々な手法が存在しますが、これらの手法をどのような順番で適用するかは、処理の結果に影響を与えることがあります。例えば、欠損値を補完する前に外れ値を処理すべきか、あるいはその逆か、といった順序によって、補完される値が変わる可能性があります。
典型的なデータ前処理の「流れ」を知ることは、以下の点で重要です。
- 効率的な作業: 闇雲に様々な処理を試すのではなく、標準的な手順に沿って進めることで、作業の効率が向上します。
- 問題の見落とし防止: 体系的なステップを踏むことで、欠損値やデータ型の不整合といった重要な問題を見落としにくくなります。
- 再現性の確保: 標準的な手順に従って処理を進めることで、他の人が同じ手順で同じ結果を得やすくなり、分析の信頼性が高まります。
- 次へのステップへのスムーズな移行: 前処理の次の工程である「モデルの学習」や「評価」に必要なデータ形式を意識しながら作業を進められます。
次に、具体的なデータ前処理の典型的なステップを見ていきましょう。
2. 典型的なデータ前処理のステップ
データ前処理の具体的なステップは、データの種類や分析の目的によって多少異なりますが、多くのプロジェクトで共通する標準的な流れがあります。ここでは、一般的なテーブルデータ(Excelの表のような形式のデータ)を扱う場合を想定して解説します。
ステップ0: データの読み込みと概要把握
厳密には前処理の一部ではないかもしれませんが、ここから全てが始まります。まずは分析対象のデータをPythonで読み込み、どのようなデータが入っているのか全体像を把握します。
このステップでは、主に以下の点を確認します。
- データの行数と列数
- 各列のデータ型(数値、文字列、日付など)
- 各列の欠損値の数
- 各列の簡単な統計量(平均値、中央値、標準偏差など)
- データの先頭数行や末尾数行の確認
Pandasライブラリを使用すると、これらの作業を簡単に行えます。
import pandas as pd
# 例としてCSVファイルを読み込む
df = pd.read_csv('your_data.csv')
# データの先頭5行を表示
print("データの先頭5行:")
print(df.head())
# データの基本情報を表示(欠損値の数やデータ型を含む)
print("\nデータの基本情報:")
df.info()
# 数値列の統計量を表示
print("\n数値列の統計量:")
print(df.describe())
df.info()
で表示される欠損値の数や、df.describe()
で表示される統計量(min, maxなど)から、データの「汚れ」の兆候や特徴を掴むことができます。
ステップ1: 欠損値の確認と対処
データに欠損値(値が存在しないセル)が含まれていると、多くの機械学習モデルはそのままでは処理できません。欠損値は分析結果に悪影響を与える可能性があるため、適切に対処する必要があります。
まずはどの列にどれくらいの欠損値があるのかを正確に把握します。
# 各列の欠損値の数を表示
print("各列の欠損値の数:")
print(df.isnull().sum())
欠損値の対処法には、いくつかの選択肢があります。
- 欠損値を含む行や列を削除する: 欠損値が非常に少ない場合や、その行・列が分析に不要な場合に有効です。
- 平均値や中央値などの代表値で埋める(補完): 数値データによく用いられます。
- 最頻値で埋める: カテゴリデータによく用いられます。
- 機械学習モデルなどを使って予測して埋める: より複雑な方法ですが、精度が高い場合があります。
どの方法を選択するかは、欠損値の量、データの特性、分析の目的に依存します。一般的には、欠損値が多い列を安易に削除せず、データの分布などを考慮して適切な補完方法を選択することが推奨されます。
# 例: 'Age'列の欠損値を中央値で補完
median_age = df['Age'].median()
df['Age'].fillna(median_age, inplace=True)
# 例: 'Embarked'列の欠損値を最頻値で補完
mode_embarked = df['Embarked'].mode()[0] # 最頻値が複数ある場合があるため [0] をつける
df['Embarked'].fillna(mode_embarked, inplace=True)
# 例: 欠損値が多い 'Cabin' 列を削除
df.drop('Cabin', axis=1, inplace=True)
print("\n欠損値処理後の各列の欠損値の数:")
print(df.isnull().sum())
このステップを最初の方で行うのは、その後の集計や統計量計算などが欠損値の影響を受けないようにするためです。
ステップ2: 外れ値(異常値)の確認と対処
外れ値とは、他の多くのデータから大きく外れた値のことです。外れ値は平均値などの統計量を歪めたり、機械学習モデルの学習に悪影響を与えたりする可能性があります。
外れ値を確認するには、ヒストグラムや箱ひげ図などのグラフを描画したり、統計的な手法(標準偏差からの距離、四分位範囲など)を使用したりします。
import matplotlib.pyplot as plt
import seaborn as sns
# 例: 'Fare'列の箱ひげ図を描画して外れ値を確認
plt.figure(figsize=(6, 4))
sns.boxplot(x=df['Fare'])
plt.title('Box plot of Fare')
plt.show()
外れ値への対処法もいくつかあります。
- 外れ値を含む行を削除する: 外れ値が少なく、明らかに測定ミスなどである場合に有効です。
- 外れ値を他の値(例: 四分位範囲内の最大値/最小値)に置き換える(クリッピング): 極端な値を抑えつつ、情報を完全に失わない方法です。
- 外れ値をそのままにしておく: モデルによっては外れ値に強いものもあり、無理に処理しない方が良い場合もあります。
外れ値処理は、欠損値処理の後に実施することが一般的です。補完された値が外れ値でないかを確認するため、あるいは外れ値によって統計量が歪んだ状態で補完を行わないようにするためです。
ステップ3: データ型の確認と変換
データを読み込んだ際、数値として扱いたい列が文字列型になっていたり、日付として扱いたい列がオブジェクト型になっていたりすることがあります。機械学習モデルは特定のデータ型(主に数値型)を想定していることが多いため、必要に応じて適切なデータ型に変換します。
df.info()
で現在のデータ型を確認し、必要ならastype()
メソッドなどを使って変換します。
print("変換前のデータ型:")
print(df.dtypes)
# 例: 'Pclass' 列をカテゴリ型(カテゴリとして扱う数値)に変換
df['Pclass'] = df['Pclass'].astype('category')
# 例: 'Fare' 列を整数型に変換(もし必要なら)
# df['Fare'] = df['Fare'].astype(int) # NaNがあるとエラーになる可能性あり
print("\n変換後のデータ型:")
print(df.dtypes)
特に、数値に見える文字列や、日付文字列などはこの段階で正しく認識させることが重要です。
ステップ4: カテゴリ変数やテキストデータの処理
カテゴリ変数(性別、出身地など、いくつかのカテゴリに分けられるデータ)やテキストデータは、そのままでは多くのモデルが扱えません。これらを数値データに変換する必要があります。
-
カテゴリ変数:
- One-Hot Encoding: カテゴリごとに新しい列を作成し、該当するカテゴリの列に1を、それ以外に0を設定する方法です。名義尺度(順序に意味がないカテゴリ)によく用いられます。
- Label Encoding: 各カテゴリに0, 1, 2...のように整数を割り当てる方法です。順序尺度(大小や順序に意味があるカテゴリ)に用いるのが適切ですが、名義尺度に適用するとモデルが誤って順序を学習する可能性があるため注意が必要です。
-
テキストデータ:
- 単語の出現回数や重要度(TF-IDFなど)に基づいて数値ベクトルに変換したり、より高度な自然言語処理技術(単語埋め込みなど)を使用したりします。
この記事ではPandas入門としてOne-Hot Encodingの例を示します。
# 例: 'Embarked' 列をOne-Hot Encoding
df = pd.get_dummies(df, columns=['Embarked'], drop_first=True) # drop_first=Trueでダミー変数の一つを削除し多重共線性を防ぐ
print("\nOne-Hot Encoding 処理後のデータ先頭5行:")
print(df.head())
これらの変換は、欠損値や外れ値の処理、データ型の確認が終わった後に行うのが自然な流れです。
ステップ5: 特徴量エンジニアリング
既存の列から新しい有用な特徴量を作成するステップです。例えば、生年月日と今日の日付から年齢を計算したり、複数の列を組み合わせて新たな指標を作ったりします。
# 例: 'SibSp' (兄弟/配偶者の数) と 'Parch' (両親/子供の数) を合計して 'FamilySize' (家族の人数) という特徴量を作成
df['FamilySize'] = df['SibSp'] + df['Parch'] + 1 # 自分自身も含むため+1
# 例: 'Fare' を人数で割って一人あたりの運賃を計算
# df['Fare_Per_Person'] = df['Fare'] / df['FamilySize'] # FamilySizeが0にならないように注意
print("\n特徴量エンジニアリング後のデータ先頭5行:")
print(df[['SibSp', 'Parch', 'FamilySize']].head())
新しい特徴量を作成することで、モデルがデータのパターンをより効率的に学習できるようになることがあります。このステップは、個別の列のクレンジングが終わった後に行うのが効率的です。
ステップ6: データのスケーリング(正規化・標準化)
多くの機械学習アルゴリズム(特に距離計算に基づくものや勾配降下法を用いるもの)は、特徴量のスケール(値の範囲)が大きく異なると性能が低下する傾向があります。このため、数値データに対してスケーリング(正規化または標準化)を適用することが一般的です。
- 正規化 (Normalization): データを0から1の間に収まるように変換します。
MinMaxScaler
などを使用します。 - 標準化 (Standardization): データの平均が0、標準偏差が1になるように変換します。
StandardScaler
などを使用します。
どちらを選択するかは、データの分布や使用するモデルによりますが、まずは標準化を試すことが多いです。スケーリングは、数値データが対象となるため、カテゴリ変数の数値化などが終わった後に行います。
from sklearn.preprocessing import StandardScaler
# 例: 'Age' と 'Fare' 列を標準化
# スケーリングは数値列に対して行う
numerical_features = ['Age', 'Fare', 'FamilySize'] # 例として新しい特徴量も含む
# スケーラーを初期化
scaler = StandardScaler()
# スケーリングを適用(元のデータフレームに結合する場合は注意が必要)
# df[numerical_features] = scaler.fit_transform(df[numerical_features]) # 直接置き換える例
# Scikit-learnのスケーラーはnumpy配列を返すため、元のデータフレームに結合する場合は注意が必要
# 一般的には、この後で訓練/テスト分割を行うため、分割後にスケーリングを適用することが多い
# 例として、スケーリング後のデータ(NumPy配列)を確認
scaled_data = scaler.fit_transform(df[numerical_features])
print("\nスケーリング後のデータ (一部):")
print(scaled_data[:5])
【重要】 スケーリングは、訓練データで学習したスケーリング情報(平均値、標準偏差、最大値、最小値など)を、テストデータに適用する必要があります。テストデータで新たにスケーリング情報を計算してはいけません。これは、テストデータは未知のデータとして扱う必要があるためです。このため、実際にはデータを訓練データとテストデータに分割した後でスケーリングを行うのが一般的なワークフローです。
ステップ7: データの分割(訓練データとテストデータ)
モデルの性能を正しく評価するためには、モデルの学習に使用するデータ(訓練データ)と、モデルの評価に使用するデータ(テストデータ)を分ける必要があります。テストデータは、モデルが一度も見たことのない「未知のデータ」を模倣するものです。
通常、データをランダムに、例えば訓練データに70%~80%、テストデータに20%~30%の割合で分割します。
from sklearn.model_selection import train_test_split
# 例: 特徴量Xと目的変数Yに分ける
# 例として、目的変数を 'Survived' 列とする
X = df.drop('Survived', axis=1) # 'Survived' 列を削除したものが特徴量
Y = df['Survived'] # 'Survived' 列が目的変数
# データを訓練用とテスト用に分割
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2, random_state=42) # random_stateは分割を固定するためのシード値
print(f"\n訓練データの形状: {X_train.shape}")
print(f"テストデータの形状: {X_test.shape}")
この分割は、データ前処理の最後の方に行います。特に、欠損値補完やスケーリングなどの統計量(平均値、標準偏差など)を使用する処理は、訓練データのみを使用して計算し、その計算結果をテストデータに適用する必要があります。もし分割前に全体データでこれらの統計量を計算してしまうと、テストデータの情報が訓練データに漏洩(Data Leakage)し、モデルの性能を過大評価してしまう原因となります。
そのため、多くの場合、欠損値補完やスケーリングは、訓練データとテストデータに分割した後で、それぞれのデータセットに対して行うことになります。(ただし、訓練データで学習した処理をテストデータに適用する)。
3. まとめ:データ前処理の進め方
この記事では、機械学習のためのデータ前処理の典型的なステップとその順序について解説しました。
一般的な流れは以下のようになります。
- データの読み込みと概要把握: まずはデータを読み込み、全体像や含まれる問題(欠損値など)を把握します。
- 欠損値の確認と対処: 欠損値の状況を確認し、削除や補完といった適切な方法で処理します。
- 外れ値の確認と対処: 外れ値の状況を確認し、分析に与える影響を考慮して対処します。
- データ型の確認と変換: 各列が適切なデータ型になっているか確認し、必要に応じて変換します。
- カテゴリ変数やテキストデータの処理: モデルが扱えるように数値データに変換します(One-Hot Encodingなど)。
- 特徴量エンジニアリング: 必要に応じて、既存の列から新しい有用な特徴量を作成します。
- データの分割(訓練データとテストデータ): モデルの学習用と評価用にデータを分割します。
- データのスケーリング: 数値データを対象に、訓練データで学習した情報を使って正規化や標準化を行います。(※多くの場合、分割後に実施)
この流れはあくまで一般的なものであり、データの種類や分析の目的によってはステップが追加されたり、順序が前後したりすることもあります。しかし、この標準的なフローを理解しておくことで、実際のデータ分析プロジェクトで次に何をすべきか、迷うことなく進めることができるでしょう。
各ステップの詳細な手法については、本サイトの他の記事で詳しく解説しています。ぜひ、この記事で全体の流れを把握した上で、個別の手法についても学習を深めてみてください。