Pythonで学ぶSimpleImputer:Scikit-learnを使った欠損値補完の第一歩

Python

データ分析や機械学習のプロジェクトを進める中で、私たちは必ずと言っていいほど「欠損値(Missing Value)」の問題に直面します。欠損値とは、データが収集されなかったり、入力ミスで失われたりした「空白」のデータのことです。

この記事を読んでいるあなたも、「欠損値があるせいでモデルがエラーになる」「欠損値をどう処理すればいいか分からない」といった悩みを抱えているかもしれません。

ご安心ください。Pythonの機械学習ライブラリである**Scikit-learn(サイキット・ラーン)**には、この問題を解決するための強力なツールが用意されています。それが今回紹介するSimpleImputerです。

この記事では、データ前処理の第一歩として、SimpleImputerの基本的な使い方から、実務で役立つPandas DataFrameでの活用法まで、初心者の方にも分かりやすく徹底解説していきます。

本記事で解説するSimpleImputerの公式ドキュメントはこちらです。

この記事で学べること:

  • SimpleImputerの基本的な使い方(fittransform
  • 欠損値を埋める4つの異なる戦略(strategy)の違いと使い分け
  • 実務で必須!Pandas DataFrameの欠損値を補完する方法

1. はじめに:データ分析と欠損値処理の重要性

まず、なぜ欠損値処理がそんなに重要なのでしょうか?

欠損値を放置するリスク

もしデータに欠損値(多くの場合、NaNNoneとして表現されます)が含まれたまま機械学習モデル(例: 線形回帰、ランダムフォレストなど)に学習させようとすると、多くの場合エラーが発生して処理が停止してしまいます。

また、仮にエラーが出ないモデルであっても、欠損値の扱いや補完方法が不適切だと、モデルの予測精度が著しく低下する原因となります。

Scikit-learnのSimpleImputerが解決策

SimpleImputerは、こうした欠損値を「あるルールに基づいて」補完(Imputation)するためのクラスです。

「あるルール」とは、例えば「列の平均値で埋める」「列の中央値で埋める」といった単純明快な戦略(Strategy)のことです。これにより、モデルが学習可能な状態にデータを整えることができます。

SimpleImputerは、Scikit-learnを使ったデータ前処理の基本中の基本であり、欠損値処理の第一歩として非常に強力なツールです。

2. SimpleImputerとは?Scikit-learnの欠損値補完ツール

SimpleImputerは、Scikit-learnライブラリのsklearn.imputeモジュールに含まれているクラスです。

その名の通り、**シンプル(Simple)な方法で欠損値を補完(Impute)**する機能を提供します。

(補足)古いバージョンとの違い

以前のScikit-learn(バージョン 0.20未満)では、sklearn.preprocessing.Imputerという名前で同様の機能が提供されていました。

しかし、現在は機能が整理され、sklearn.impute.SimpleImputerとして提供されています。これから学ぶ方は、SimpleImputerを使うものと覚えておけば問題ありません。

3. SimpleImputerの基本的な使い方(NumPy配列編)

まずは、最も基本的な使い方であるNumPy配列を使った例を見ていきましょう。

必要なライブラリのインポート

最初に、SimpleImputerと、データを作成するためにnumpyをインポートします。

import numpy as np
from sklearn.impute import SimpleImputer

サンプルデータの準備

np.nanを使って、欠損値を含むNumPy配列を作成します。

# 欠損値(np.nan)を含むデータを作成
data_np = np.array([[1.0, 2.0, np.nan],
                    [4.0, np.nan, 6.0],
                    [7.0, 8.0, 9.0],
                    [np.nan, 11.0, 12.0]])

print("元のデータ:")
print(data_np)

実行結果:

元のデータ:
[[ 1.  2. nan]
 [ 4. nan  6.]
 [ 7.  8.  9.]
 [nan 11. 12.]]

SimpleImputerの基本的な流れ

SimpleImputerの使い方は、Scikit-learnの他の変換器(Transformer)と同様、fit(学習)と**transform**(変換)の2ステップで行います。

  1. インスタンスの作成: SimpleImputer()で、どのような戦略で補完するかを決めてインスタンスを作成します。
  2. fit()(学習): fit()メソッドにデータを渡します。SimpleImputerは、渡されたデータを分析し、「補完に使う値(例: 各列の平均値)」を計算して内部に記憶します。
  3. transform()(変換): transform()メソッドにデータを渡します。fit()で記憶した値を使って、実際の欠損値(np.nan)を補完(置換)します。

コード例(fitとtransform)

デフォルト(何も指定しない場合)の戦略は'mean'(平均値)です。

# 1. インスタンスの作成 (戦略 strategy='mean' はデフォルト)
# strategy='mean' は、各列の「NaNを除いた平均値」で補完することを意味します
imputer = SimpleImputer(strategy='mean')

# 2. fit() で学習(各列の平均値を計算させる)
# 1列目: (1+4+7)/3 = 4.0
# 2列目: (2+8+11)/3 = 7.0
# 3列目: (6+9+12)/3 = 9.0
imputer.fit(data_np)

# 内部に計算された統計値を確認 (statistics_)
print(f"各列の平均値 (補完に使われる値): {imputer.statistics_}")

# 3. transform() で変換(実際に補完する)
data_imputed = imputer.transform(data_np)

print("\n補完後のデータ:")
print(data_imputed)

実行結果:

各列の平均値 (補完に使われる値): [4. 7. 9.]

補完後のデータ:
[[ 1.  2.  9.]
 [ 4.  7.  6.]
 [ 7.  8.  9.]
 [ 4. 11. 12.]]

1列目のnp.nan4.0に、2列目のnp.nan7.0に、3列目のnp.nan9.0に置き換わっていることが確認できます。

fit_transform()で学習と補完を同時に行う

実務では、fit()transform()を別々に行うのではなく、fit_transform()メソッドを使って一度に実行するのが一般的です。

# データをもう一度用意
data_np = np.array([[1.0, 2.0, np.nan],
                    [4.0, np.nan, 6.0],
                    [7.0, 8.0, 9.0],
                    [np.nan, 11.0, 12.0]])

# インスタンス作成
imputer_mean = SimpleImputer(strategy='mean')

# fit()とtransform()を同時に実行
data_imputed_ft = imputer_mean.fit_transform(data_np)

print("fit_transformによる補完後のデータ:")
print(data_imputed_ft)

実行結果:

fit_transformによる補完後のデータ:
[[ 1.  2.  9.]
 [ 4.  7.  6.]
 [ 7.  8.  9.]
 [ 4. 11. 12.]]

同じ結果が得られました。こちらの書き方の方がコードが短く済むため、よく使われます。

4. 4つの補完戦略(strategy)を理解する

SimpleImputerの最も重要なパラメータがstrategy(戦略)です。これにより、欠損値を「何で埋めるか」を指定できます。

ここでは主要な4つの戦略(mean, median, most_frequent, constant)を見ていきましょう。

strategy=’mean’(平均値)

これは先ほども使用したデフォルトの戦略で、各列の平均値NaNを除外して計算)で欠損値を補完します。

  • 特徴:
    • 最も一般的に使われる方法です。
    • データの統計的な分布(平均値)を大きく変えにくいです。
  • 注意点:
    • 外れ値(極端に大きい値や小さい値)に弱いです。例えば、[1, 2, 3, 1000] というデータがあると、平均値は大きく1000側に引っ張られてしまいます。

(コード例はセクション3で示したため、ここでは省略します)

strategy=’median’(中央値)

各列の中央値NaNを除外して計算)で欠損値を補完します。中央値とは、データを小さい順に並べたときに、ちょうど真ん中に来る値のことです。

  • 特徴:
    • **外れ値に強い(堅牢である)**という大きなメリットがあります。先ほどの[1, 2, 3, 1000] の例でも、中央値は2.5となり、外れ値1000の影響を受けません。
    • データの分布に歪みがある場合(例: 所得データなど)に適しています。
  • 使い所:
    • データに外れ値が含まれている可能性が高い場合に、meanの代わりに選択すると良いでしょう。
# 中央値で補完する例
imputer_median = SimpleImputer(strategy='median')

data_imputed_median = imputer_median.fit_transform(data_np)

print(f"中央値 (補完に使われる値): {imputer_median.statistics_}")
print("\n中央値で補完後のデータ:")
print(data_imputed_median)

実行結果:

中央値 (補完に使われる値): [4. 8. 9.]

中央値で補完後のデータ:
[[ 1.  2.  9.]
 [ 4.  8.  6.]
 [ 7.  8.  9.]
 [ 4. 11. 12.]]

2列目の平均値は7.0でしたが、中央値([2.0, 8.0, 11.0] の真ん中)は8.0となったため、np.nan8.0で補完されています。

strategy=’most_frequent’(最頻値)

各列の最頻値NaNを除外して計算)で欠損値を補完します。最頻値とは、データの中で最も頻繁に出現する値のことです。

  • 特徴:
    • 数値データ(int, float)だけでなく、**カテゴリカルデータ(文字列、object型)**の欠損値補完にも使えます。
  • 使い所:
    • 性別(’男性’, ‘女性’)やアンケートの回答(’A’, ‘B’, ‘C’)など、文字列で表されるデータの欠損値を補完したい場合に非常に便利です。

数値データの例

data_np_freq = np.array([[1.0, 10.0],
                         [2.0, 20.0],
                         [1.0, 10.0],
                         [2.0, np.nan],
                         [np.nan, 10.0]])

imputer_freq = SimpleImputer(strategy='most_frequent')
data_imputed_freq = imputer_freq.fit_transform(data_np_freq)

print(f"最頻値 (補完に使われる値): {imputer_freq.statistics_}")
print("\n最頻値で補完後のデータ:")
print(data_imputed_freq)

実行結果:

最頻値 (補完に使われる値): [ 1. 10.]

最頻値で補完後のデータ:
[[ 1. 10.]
 [ 2. 20.]
 [ 1. 10.]
 [ 2. 10.]
 [ 1. 10.]]

1列目は1.0が、2列目は10.0が最頻値のため、それで補完されました。

文字列データ(カテゴリカルデータ)の例

カテゴリカルデータの前処理には、SimpleImputerのほかにもOneHotEncoderLabelEncoderといった手法がよく使われます。これらは欠損値補完とは異なりますが、機械学習モデルがカテゴリデータを扱えるように変換する重要な処理です。(OneHotEncoderに関する詳しい記事はこちらです。LabelEncoderに関する詳しい記事はこちらです。)

# object型(文字列)を含むデータ
data_str = np.array([['A', 'Red'],
                     ['B', 'Blue'],
                     ['A', 'Red'],
                     [np.nan, 'Green'],
                     ['B', np.nan],
                     ['A', 'Red']], dtype=object) # dtype=object が重要

imputer_str = SimpleImputer(strategy='most_frequent')
data_imputed_str = imputer_str.fit_transform(data_str)

print(f"最頻値 (補完に使われる値): {imputer_str.statistics_}")
print("\n最頻値で補完後のデータ:")
print(data_imputed_str)

実行結果:

最頻値 (補完に使われる値): ['A' 'Red']

最頻値で補完後のデータ:
[['A' 'Red']
 ['B' 'Blue']
 ['A' 'Red']
 ['A' 'Green']
 ['B' 'Red']
 ['A' 'Red']]

1列目のnp.nanは最頻値の'A'に、2列目のnp.nanは最頻値の'Red'に補完されました。

strategy=’constant’(定数値)

strategy='constant'は、指定した**固定値(定数値)で欠損値を補完します。 この戦略を使う場合は、fill_value**パラメータも同時に指定して、何で埋めるかを明示する必要があります。

  • 特徴:
    • 0で埋めたい、'Unknown'(不明)という文字列で埋めたいなど、統計値ではなく特定の意味を持つ値で補完したい場合に便利です。
  • 使い所:
    • 数値データの場合はfill_value=0fill_value=-1など。
    • カテゴリカルデータの場合はfill_value='Missing'fill_value='N/A'などがよく使われます。

数値データの例(0で埋める)

# constant と fill_value=0 を指定
imputer_const_num = SimpleImputer(strategy='constant', fill_value=0)

data_imputed_const = imputer_const_num.fit_transform(data_np)

print("\n0で補完後のデータ:")
print(data_imputed_const)

実行結果:

0で補完後のデータ:
[[ 1.  2.  0.]
 [ 4.  0.  6.]
 [ 7.  8.  9.]
 [ 0. 11. 12.]]

文字列データの例(’Unknown’で埋める)

imputer_const_str = SimpleImputer(strategy='constant', fill_value='Unknown')

data_imputed_const_str = imputer_const_str.fit_transform(data_str)

print(f"\n'Unknown'で補完後のデータ:")
print(data_imputed_const_str)

実行結果:

'Unknown'で補完後のデータ:
[['A' 'Red']
 ['B' 'Blue']
 ['A' 'Red']
 ['Unknown' 'Green']
 ['B' 'Unknown']
 ['A' 'Red']]

5. 【実践】Pandas DataFrameでSimpleImputerを使う方法

さて、ここまではNumPy配列で基本を学びましたが、実務のデータ分析ではPandas DataFrameを使うことがほとんどです。

DataFrameでSimpleImputerを使う場合、いくつかの注意点があります。

サンプルDataFrameの準備

pandasをインポートし、数値列(’age’, ‘score’)とカテゴリ列(’gender’)が混在し、それぞれに欠損値を含むDataFrameを作成します。

import pandas as pd

df = pd.DataFrame({
    'age': [25, 30, np.nan, 35, 40],
    'gender': ['Male', 'Female', 'Male', np.nan, 'Female'],
    'score': [88, 95, 76, np.nan, 89]
})

print("元のDataFrame:")
print(df)
print("\nDataFrameの情報:")
df.info()

実行結果:

元のDataFrame:
    age  gender  score
0  25.0    Male   88.0
1  30.0  Female   95.0
2   NaN    Male   76.0
3  35.0     NaN    NaN
4  40.0  Female   89.0

DataFrameの情報:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5 entries, 0 to 4
Data columns (total 3 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   age     4 non-null      float64
 1   gender  4 non-null      object 
 2   score   4 non-null      float64
dtypes: float64(2), object(1)
memory usage: 248.0+ bytes

agescore(数値)、gender(文字列)に欠損値(NaN)があることがわかります。

方法1:数値列のみを選択して適用する

最もシンプルでよく使われる方法が、数値列だけを先に補完する方法です。

注意点: SimpleImputer(に限らずScikit-learnの変換器)は、fit_transformの戻り値としてNumPy配列を返します。Pandas DataFrameは返してくれません。

そのため、補完した結果(NumPy配列)を、元のDataFrameの形式に戻す作業が必要になります。

# 1. 数値列(float64, int64)だけを抽出
numeric_cols = df.select_dtypes(include=['float64', 'int64']).columns
print(f"数値列: {numeric_cols}")

# 2. 数値列用のImputerを作成(ここでは中央値 median を使用)
imputer_numeric = SimpleImputer(strategy='median')

# 3. 数値列にのみ fit_transform を適用
# df[numeric_cols] で数値列だけのDataFrameを渡す
# 戻り値はNumPy配列
df_numeric_imputed = imputer_numeric.fit_transform(df[numeric_cols])

# 4. 補完結果を新しいDataFrameに変換(カラム名を戻す)
df_numeric_imputed_pd = pd.DataFrame(df_numeric_imputed, columns=numeric_cols)

print("\n数値列を補完した結果 (DataFrame):")
print(df_numeric_imputed_pd)

# 5. 元のDataFrameの数値列を、補完後のデータで置き換える
# (カテゴリ列 'gender' はそのまま)
df_imputed = df.copy() # 元のdfを変更しないようにコピー
df_imputed[numeric_cols] = df_numeric_imputed_pd

print("\n最終的なDataFrame (数値列のみ補完):")
print(df_imputed)

実行結果:

数値列: Index(['age', 'score'], dtype='object')

数値列を補完した結果 (DataFrame):
    age  score
0  25.0   88.0
1  30.0   95.0
2  32.5   76.0
3  35.0   88.5
4  40.0   89.0

最終的なDataFrame (数値列のみ補完):
    age  gender  score
0  25.0    Male   88.0
1  30.0  Female   95.0
2  32.5    Male   76.0
3  35.0     NaN   88.5
4  40.0  Female   89.0

ageNaNが中央値32.5に、scoreNaNが中央値88.5に補完されました。genderNaNはそのままです。

このように、欠損値補完の後は、StandardScalerMinMaxScalerといったクラスを使って数値のスケール(尺度)を揃える「スケーリング」処理を行うのが一般的です。これらもデータ前処理の重要なステップです。(StandardScalerに関する詳しい記事はこちらです。MinMaxScalerに関する詳しい記事はこちらです。)

方法2:ColumnTransformerと組み合わせて列ごとに戦略を変える

この方法は少し発展的ですが、Scikit-learnのパイプライン機能の核心であり、非常に強力です。

ColumnTransformersklearn.composeモジュール)を使うと、「この列にはこの処理」「あの列にはあの処理」というルールを一度に定義できます。

今回は、以下のように設定します。

  • 数値列('age', 'score')には: SimpleImputer(strategy='median')を適用
  • カテゴリ列('gender')には: SimpleImputer(strategy='most_frequent')を適用
from sklearn.compose import ColumnTransformer

# 1. 処理を適用する列リストを定義
numeric_features = ['age', 'score']
categorical_features = ['gender']

# 2. 各列に適用する「処理のパイプライン」を定義
# 今回はImputerだけだが、実際にはこの後にStandardScalerやOneHotEncoderを繋げることが多い
numeric_transformer = SimpleImputer(strategy='median')
categorical_transformer = SimpleImputer(strategy='most_frequent')

# 3. ColumnTransformer で上記ルールをまとめる
# (名前, 変換器, 対象列リスト) のタプルで指定
preprocessor = ColumnTransformer(
    transformers=[
        ('num', numeric_transformer, numeric_features),
        ('cat', categorical_transformer, categorical_features)
    ])

# 4. ColumnTransformer を DataFrame に fit_transform する
# これで、数値列とカテゴリ列の欠損値補完が一括で実行される
data_processed = preprocessor.fit_transform(df)

print("\nColumnTransformerによる一括処理後のデータ (NumPy配列):")
print(data_processed)

# 補足: ColumnTransformerもNumPy配列を返すため、DataFrameに戻す作業は必要
# ただし、処理の順番が変わる(定義順 'num' -> 'cat')ことに注意
processed_cols = numeric_features + categorical_features
df_processed = pd.DataFrame(data_processed, columns=processed_cols)

print("\n処理結果をDataFrameに戻したもの:")
print(df_processed)

実行結果:

ColumnTransformerによる一括処理後のデータ (NumPy配列):
[[25.0 88.0 'Male']
 [30.0 95.0 'Female']
 [32.5 76.0 'Male']
 [35.0 88.5 'Male']
 [40.0 89.0 'Female']]

処理結果をDataFrameに戻したもの:
    age score  gender
0  25.0  88.0    Male
1  30.0  95.0  Female
2  32.5  76.0    Male
3  35.0  88.5    Male
4   40.0  89.0  Female

agescoreが中央値で補完され、genderNaN(3番目の行)が最頻値である'Male'で補完されていることがわかります。

ColumnTransformerは、SimpleImputerと、前述のStandardScalerOneHotEncoderを組み合わせて「データ前処理パイプライン」を作る際に必須のテクニックです。

6. まとめ:SimpleImputerを使いこなしてデータ前処理を効率化しよう

今回は、Scikit-learnを使った欠損値処理の第一歩として、SimpleImputerの使い方を徹底的に解説しました。

  • SimpleImputerは欠損値処理の基本: Scikit-learnのfit -> transform(またはfit_transform)の作法で簡単に使えます。
  • 4つのstrategyが重要:
    • 'mean' (平均値): デフォルト。外れ値に弱い。
    • 'median' (中央値): 外れ値に強い(堅牢)。
    • 'most_frequent' (最頻値): カテゴリカルデータ(文字列)にも使える。
    • 'constant' (定数値): fill_valueと組み合わせて固定値で埋める。
  • Pandas DataFrameでの利用が実務の鍵:
    • SimpleImputerはNumPy配列を返すため、DataFrameに戻す作業が必要です。
    • ColumnTransformerを使うと、列の型(数値/カテゴリ)ごとに異なる補完戦略を一括で適用でき、非常に強力です。

データ分析や機械学習において、前処理はモデル構築と同じくらい、あるいはそれ以上に重要です。

まずはSimpleImputerを使って、厄介な欠損値を手なずけるところから始めてみましょう!

コメント

タイトルとURLをコピーしました