機械学習データ前処理入門

機械学習データ前処理:数値データの欠損値をパターン別に補完する方法【Pandas入門】

Tags: 機械学習, データ前処理, Pandas, 欠損値, データ分析入門

機械学習モデルを構築する際、データの品質はモデルの性能に直接影響します。特に、データに欠損値が含まれている場合、適切に処理しないとモデルがうまく学習できなかったり、誤った予測をしたりする原因となります。

前回の記事「機械学習データ前処理の第一歩:欠損値の種類と対処法【Pandas入門】」では、欠損値がどのようなものか、どのように確認するか、そして基本的な削除方法やごく単純な補完方法に触れました。

本記事では、特に数値データに焦点を当て、欠損値の性質やパターンに応じた、より多様な補完方法をPythonのデータ分析ライブラリPandasを使用して学ぶことで、より高品質な前処理を目指します。

なぜ数値データの欠損値補完が重要なのか

多くの機械学習モデルは、入力として数値データを期待します。欠損値(多くの場合、NaNNoneとして表現されます)が含まれたままでは、計算処理が進められず、モデルを学習させることができません。

欠損値を単純に削除する方法もありますが、これはデータ量を減らしてしまい、情報損失につながる可能性があります。特に欠損が多い場合、削除は現実的な選択肢ではありません。

そこで重要になるのが「補完(Imputation)」です。これは、欠損している値を、他のデータから推測される値やルールに基づいて埋める処理です。数値データの場合、そのデータの持つ連続性や統計的な性質を考慮した補完が効果的です。

どのような補完方法を選ぶかは、データの性質、欠損のパターン、そして構築したいモデルに依存します。不適切な補完は、データの本来の分布を歪めたり、架空のパターンを作り出したりするリスクもあります。そのため、複数の補完方法を知り、適切に使い分けることが求められます。

Pandasを使った欠損値の確認(おさらい)

補完を行う前に、まずは欠損値の存在と量を確認しましょう。Pandasのisnull()メソッドとsum()メソッドを組み合わせることで、各列の欠損値の数を簡単に確認できます。

import pandas as pd
import numpy as np

# サンプルデータの作成
data = {'ColumnA': [1, 2, np.nan, 4, 5],
        'ColumnB': [np.nan, 2, 3, np.nan, 5],
        'ColumnC': [10, 20, 30, 40, np.nan]}
df = pd.DataFrame(data)

print("元のデータフレーム:")
print(df)

print("\n各列の欠損値の数:")
print(df.isnull().sum())

この出力から、ColumnAに1つ、ColumnBに2つ、ColumnCに1つの欠損値があることがわかります。

数値データの様々な欠損値補完方法

ここでは、Pandasを使って数値データの欠損値を補完するいくつかの代表的な方法を紹介します。それぞれの方法の基本的な考え方と、どのような場合に有効かを見ていきましょう。

1. 特定の値(定数)で補完する

欠損値に特定の値を割り当てる方法です。例えば、測定できなかった値は0として扱う、といったケースに利用できます。

考え方: 欠損に特定の意味がある場合や、欠損がデータ取得の失敗などを意味し、その状態自体を数値で表現したい場合に有効です。ただし、この値を不適切に選ぶと、モデルが誤ったパターンを学習するリスクがあります。

Pandasでの実装: fillna()メソッドに補完したい値を渡します。

# 特定の値 (例: 0) でColumnAの欠損値を補完
df_filled_zero = df.copy()
df_filled_zero['ColumnA'] = df_filled_zero['ColumnA'].fillna(0)

print("\nColumnAを0で補完したデータフレーム:")
print(df_filled_zero)

2. 平均値、中央値、最頻値で補完する

列の既存のデータの統計量(平均値、中央値、最頻値)で欠損値を補完する方法です。前回の記事でも触れましたが、数値データでは特に平均値や中央値がよく用いられます。

考え方: 欠損値がランダムに発生していると仮定できる場合(MCAR: Missing Completely At Randomに近い場合)に、データの中心傾向を保ちながら補完する基本的な手法です。 * 平均値: データの分布が左右対称に近い場合に適しています。外れ値に影響されやすい点に注意が必要です。 * 中央値: 外れ値の影響を受けにくいため、データの分布が歪んでいる場合に適しています。 * 最頻値: 数値データというよりはカテゴリカルな数値(例: 投票数)や離散値に使うことが多いですが、連続値でも使用できます。

Pandasでの実装: fillna()メソッドと、mean()median()mode()メソッドを組み合わせます。mode()は結果がSeriesになるため、インデックス0を指定して値を取得します。

# ColumnBの欠損値を平均値で補完
df_filled_mean = df.copy()
mean_b = df_filled_mean['ColumnB'].mean()
df_filled_mean['ColumnB'] = df_filled_mean['ColumnB'].fillna(mean_b)

print(f"\nColumnBを平均値 ({mean_b:.2f}) で補完したデータフレーム:")
print(df_filled_mean)

# ColumnCの欠損値を中央値で補完
df_filled_median = df.copy()
median_c = df_filled_median['ColumnC'].median()
df_filled_median['ColumnC'] = df_filled_median['ColumnC'].fillna(median_c)

print(f"\nColumnCを中央値 ({median_c:.1f}) で補完したデータフレーム:")
print(df_filled_median)

3. 前後の値で補完する(時系列データなどに有効)

欠損値の直前または直後の有効な値で補完する方法です。時系列データや、順番に意味があるデータに特に有効です。

考え方: データの値が時間や順序に対して滑らかに変化すると仮定できる場合に、欠損箇所をその前後の値で「引き継ぐ」ことで補完します。 * ffill (forward fill): 欠損値をその直前の値で補完します。method='pad' も同じです。 * bfill (backward fill): 欠損値をその直後の値で補完します。method='backfill' も同じです。

Pandasでの実装: fillna()メソッドにmethod='ffill'またはmethod='bfill'を指定します。

# サンプルデータの作成(時系列を想定)
data_ts = {'Value': [10, 12, np.nan, 15, 18, np.nan, np.nan, 25]}
df_ts = pd.DataFrame(data_ts)

print("\n元の時系列データフレーム:")
print(df_ts)

# 直前の値で補完 (ffill)
df_ffill = df_ts.copy()
df_ffill['Value'] = df_ffill['Value'].fillna(method='ffill')

print("\n直前の値で補完したデータフレーム (ffill):")
print(df_ffill)

# 直後の値で補完 (bfill)
df_bfill = df_ts.copy()
df_bfill['Value'] = df_bfill['Value'].fillna(method='bfill')

print("\n直後の値で補完したデータフレーム (bfill):")
print(df_bfill)

この例では、最初の欠損値(インデックス2)はffillで12に、bfillで15に補完されています。連続する欠損値(インデックス5, 6)の場合、ffillは直前の18で両方補完され、bfillは直後の25で両方補完されます。

4. 線形補間を行う(連続的なデータに有効)

欠損値の前後の有効な値を使い、線形的に変化すると仮定して値を補間する方法です。時間や位置に比例して値が変化すると考えられるデータに特に有効です。

考え方: 欠損箇所を直線で結んだ場合の値を推定します。単純な前後の値での補完よりも、データの連続性をより反映できます。

Pandasでの実装: interpolate()メソッドを使用します。デフォルトのmethod='linear'が線形補間です。

# サンプルデータの作成(時系列または連続的な変化を想定)
data_interp = {'Value': [10, 12, np.nan, 16, 20, np.nan, np.nan, 28]}
df_interp = pd.DataFrame(data_interp)

print("\n元のデータフレーム (補間用):")
print(df_interp)

# 線形補間
df_interp_linear = df_interp.copy()
df_interp_linear['Value'] = df_interp_linear['Value'].interpolate(method='linear')

print("\n線形補間したデータフレーム:")
print(df_interp_linear)

この例では、インデックス2の欠損値は (12 + 16) / 2 = 14 に補完されます。インデックス5と6の連続する欠損値は、16と20の間の直線上の値、つまり (16+20)/2=18 と 20 に補完されるわけではなく、欠損値の両端である20(インデックス4)と28(インデックス7)の間を線形補間します。インデックス5は20と28の間の1/3の位置なので 20 + (28-20)(1/(7-4)) = 20 + 8/3 ≒ 22.67 となります。インデックス6は2/3の位置なので 20 + (28-20)(2/(7-4)) = 20 + 16/3 ≒ 25.33 と補間されます。このように、interpolateは欠損値の「位置」を考慮して補間を行います。

どの補完方法を選ぶべきか

ここまでいくつかの補完方法を見てきましたが、どれが「正解」というわけではありません。データセットの特性や、欠損が発生した背景(欠損のメカニズム)を考慮して、最も適切と思われる方法を選択する必要があります。

選択のヒントとして以下のような点を考慮すると良いでしょう。

最も重要なのは、補完を行った後に、その列の統計量や分布(ヒストグラムなど)がどのように変化したかを確認し、データの持つ本来の情報を過度に歪めていないかを評価することです。可能であれば、異なる補完方法を試して、それぞれの方法でモデルを学習させ、その性能を比較することも有効です。

補完後の確認

補完が完了したら、再びisnull().sum()で欠損値がなくなったかを確認しましょう。また、補完した列の平均値、中央値、標準偏差などの統計量を元のデータと比較したり、ヒストグラムを描画して分布の変化を確認したりすることが推奨されます。

# 例えば、中央値補完したColumnCの統計量を確認
print("\nColumnC (元のデータ) の統計量:")
print(df['ColumnC'].describe())

print("\nColumnC (中央値補完後) の統計量:")
print(df_filled_median['ColumnC'].describe())

この出力を見ると、中央値は変化しませんが、欠損値を埋めたことでカウント数 (count) が増え、平均値 (mean) や標準偏差 (std) がわずかに変化していることがわかります。

まとめ

本記事では、機械学習のためのデータ前処理において、数値データの欠損値を補完する様々な方法を、Pandasを使った具体的なコードとともに解説しました。

これらの手法は、それぞれ異なる考え方に基づいており、データの性質や欠損のパターンに応じて適切に使い分けることが重要です。欠損値処理はデータ前処理の中でも特に重要なステップの一つであり、モデルの性能を大きく左右します。

この記事で紹介した基本的な補完方法を理解し、ご自身のデータに対して試してみることから始めてみてください。データの探索的分析(EDA)を通じて欠損の性質を理解することが、適切な補完方法を選択する上での第一歩となります。

次回の記事では、さらに他のデータ前処理のテクニックについて解説していきます。