Python Pandasで欠損値を効率的に処理する方法:検出・削除・補完のレシピ
はじめに:データ分析における欠損値の重要性
データ分析を行う際、私たちが扱うデータにはしばしば「欠損値」が含まれています。これは、データが記録されなかったり、取得できなかったり、あるいは何らかの理由で無効な値として表現されたりするものです。Pythonのデータ分析ライブラリPandasでは、このような欠損値は通常 NaN
(Not a Number) として表現されます。
欠損値がデータセットに存在すると、統計量の計算が不正確になったり、機械学習モデルの訓練に悪影響を与えたり、あるいは単純に処理がエラーで停止してしまったりする原因となります。そのため、データ分析の初期段階で欠損値を適切に処理することは、分析結果の信頼性を高め、スムーズな作業を進める上で非常に重要です。
この記事では、Pandasを使ってデータセット内の欠損値を「検出」し、不要な欠損値を「削除」し、そして分析に必要な欠損値を適切な値で「補完」する方法について、具体的なコードレシピとともに詳しく解説します。
問題提起:データ内の欠損値にどう対応するか
データ分析の現場では、以下のような課題に直面することがよくあります。
- 欠損値の存在を把握できない: どこに、どれくらいの欠損値があるのかが分からないと、次のステップに進めません。
- 不適切な欠損値処理: 欠損値をそのままにして分析を進めてしまい、誤った結論を導いてしまう可能性があります。
- 欠損値処理によるデータ損失: 欠損値を含む行や列を安易に削除してしまうと、重要な情報まで失ってしまうことがあります。
- 適切な補完方法の選択: 欠損値を補完する場合、どのような値で補完するのが最適なのか、その判断に迷うことがあります。
これらの課題を解決するために、これからご紹介するPandasの機能を活用し、効率的かつ適切に欠損値を処理する手順を習得しましょう。
解決策(コードレシピ):欠損値の検出・削除・補完
まず、サンプルデータを作成し、欠損値の処理方法を実践的に見ていきます。
1. 必要なライブラリのインポートとサンプルデータの準備
import pandas as pd
import numpy as np
# サンプルDataFrameの作成
data = {
'A': [1, 2, np.nan, 4, 5],
'B': [np.nan, 20, 30, np.nan, 50],
'C': [100, 200, 300, 400, 500],
'D': [1, 2, 3, 4, np.nan]
}
df = pd.DataFrame(data)
print("元のDataFrame:")
print(df)
print("-" * 30)
このコードでは、Pandasと数値計算ライブラリNumPyをインポートし、意図的に np.nan
(NumPyの欠損値表現) を含むDataFrame df
を作成しています。
2. 欠損値の検出
DataFrameに欠損値が含まれているかどうか、またどこに、どれくらい含まれているかを把握します。
# DataFrame全体の欠損値の有無を確認
print("DataFrame全体の欠損値の有無:")
print(df.isnull().any().any())
print("-" * 30)
# 各列の欠損値の数をカウント
print("各列の欠損値の数:")
print(df.isnull().sum())
print("-" * 30)
# 欠損値がある行を全て表示
print("欠損値を含む行:")
print(df[df.isnull().any(axis=1)])
print("-" * 30)
3. 欠損値の削除
欠損値を含む行や列を削除する方法です。
# 欠損値を含む行を削除(デフォルト)
df_dropped_rows = df.dropna()
print("欠損値を含む行を削除したDataFrame:")
print(df_dropped_rows)
print("-" * 30)
# 全ての要素が欠損値である行を削除
df_drop_all_nan_rows = df.dropna(how='all')
print("全ての要素が欠損値である行を削除したDataFrame:")
print(df_drop_all_nan_rows) # このサンプルデータでは該当する行がないため、元の行数は維持されます
print("-" * 30)
# 欠損値を含む列を削除
df_dropped_cols = df.dropna(axis=1)
print("欠損値を含む列を削除したDataFrame:")
print(df_dropped_cols)
print("-" * 30)
# 'B'列に欠損値がある行のみを削除
df_drop_col_B = df.dropna(subset=['B'])
print("'B'列に欠損値がある行のみを削除したDataFrame:")
print(df_drop_col_B)
print("-" * 30)
# 変更を元のDataFrameに適用する (inplace=True)
# df.dropna(inplace=True)
# print("元のDataFrameが変更された後の状態:")
# print(df)
# print("-" * 30)
4. 欠損値の補完
欠損値を特定の値や統計量、あるいは前後の値で埋める方法です。
# 欠損値を0で補完
df_fill_zero = df.fillna(0)
print("欠損値を0で補完したDataFrame:")
print(df_fill_zero)
print("-" * 30)
# 各列の平均値で欠損値を補完
df_fill_mean = df.fillna(df.mean())
print("各列の平均値で欠損値を補完したDataFrame:")
print(df_fill_mean)
print("-" * 30)
# 'B'列のみ中央値で補完
df_fill_b_median = df.copy() # オリジナルを保つためコピー
b_median = df_fill_b_median['B'].median()
df_fill_b_median['B'].fillna(b_median, inplace=True)
print("'B'列のみ中央値で補完したDataFrame:")
print(df_fill_b_median)
print("-" * 30)
# 直前の値で欠損値を補完 (forward fill)
df_ffill = df.fillna(method='ffill')
print("直前の値で欠損値を補完したDataFrame (ffill):")
print(df_ffill)
print("-" * 30)
# 直後の値で欠損値を補完 (backward fill)
df_bfill = df.fillna(method='bfill')
print("直後の値で欠損値を補完したDataFrame (bfill):")
print(df_bfill)
print("-" * 30)
# 変更を元のDataFrameに適用する (inplace=True)
# df.fillna(0, inplace=True)
# print("元のDataFrameが変更された後の状態:")
# print(df)
# print("-" * 30)
コードの詳細解説
ここでは、上記のコードレシピで使用したPandasの関数やメソッドについて、その機能と使い方を詳しく解説します。
1. 欠損値の検出:isnull()
/ isna()
と sum()
df.isnull()
/df.isna()
:- これらのメソッドは、DataFrameの各要素が欠損値であるかどうかをブール値(True/False)で返します。
True
は欠損値であることを示し、False
は欠損値でないことを示します。isnull()
とisna()
は全く同じ機能を提供します。 - 例:
df.isnull()
は、元のDataFrameと同じ形状のDataFrameを返します。
- これらのメソッドは、DataFrameの各要素が欠損値であるかどうかをブール値(True/False)で返します。
.any()
:- ブール値のDataFrameやSeriesに対して使用すると、少なくとも1つの
True
があるかどうかをチェックします。 df.isnull().any()
は、各列に一つでも欠損値があるか(True
)ないか(False
)をSeriesとして返します。df.isnull().any().any()
のように二回.any()
を連結することで、DataFrame全体に一つでも欠損値が存在するかどうかを単一のブール値で確認できます。
- ブール値のDataFrameやSeriesに対して使用すると、少なくとも1つの
.sum()
:- ブール値のSeriesやDataFrameに対して使用すると、
True
を1、False
を0として扱って合計を計算します。 df.isnull().sum()
は、各列に含まれる欠損値の総数をSeriesとして返します。これにより、どの列にどれくらいの欠損値があるかを一目で把握できます。
- ブール値のSeriesやDataFrameに対して使用すると、
df[df.isnull().any(axis=1)]
:df.isnull().any(axis=1)
は、各行に一つでも欠損値があるかを確認します。axis=1
を指定することで、列方向(行ごと)に集計を行う指示になります。- このブール値のSeriesを使ってDataFrameをフィルタリングすることで、欠損値を含む行のみを抽出して表示することができます。
2. 欠損値の削除:dropna()
dropna()
メソッドは、欠損値を含む行や列を削除するために使用します。
df.dropna()
:- デフォルトでは
axis=0
およびhow='any'
が適用されます。これは「一つでも欠損値を含む行があれば、その行全体を削除する」という意味になります。
- デフォルトでは
axis
引数:axis=0
(デフォルト): 欠損値を含む「行」を削除します。axis=1
: 欠損値を含む「列」を削除します。
how
引数:how='any'
(デフォルト): 欠損値が一つでもあれば、その行または列を削除します。how='all'
: その行または列の全ての要素が欠損値である場合にのみ、その行または列を削除します。
subset
引数:- 特定の列(例:
subset=['B']
)を指定することで、その列に欠損値がある行のみを削除するといった、よりきめ細やかな制御が可能です。
- 特定の列(例:
inplace
引数:inplace=True
を指定すると、dropna()
の結果が新しいDataFrameとして返されるのではなく、元のDataFrame自体が変更されます。これはメモリ効率が良いですが、元のデータを失うため、使用する際には注意が必要です。通常は、新しいDataFrame変数に結果を代入する(例:df_new = df.dropna()
)方が安全です。
3. 欠損値の補完:fillna()
fillna()
メソッドは、欠損値を指定した値や、あるルールに基づいて計算された値で埋めるために使用します。
df.fillna(value)
:- 最も基本的な使用法で、指定した
value
(例: 0、平均値、中央値など)で全ての欠損値を埋めます。 - 例:
df.fillna(0)
は、DataFrame内の全ての欠損値を0で置き換えます。
- 最も基本的な使用法で、指定した
df.fillna(df.mean())
:- この例では、
df.mean()
が各列の平均値をSeriesとして計算し、その平均値で対応する列の欠損値を補完しています。数値データの場合によく用いられる方法です。
- この例では、
df.fillna(method='ffill')
(forward fill):- 各列において、欠損値の直前の有効な値を使って欠損値を補完します。時系列データなどで、以前のデータが現在の状態をよく表す場合に有効です。
df.fillna(method='bfill')
(backward fill):- 各列において、欠損値の直後の有効な値を使って欠損値を補完します。
ffill
とは逆の方向から補完します。
- 各列において、欠損値の直後の有効な値を使って欠損値を補完します。
- 列ごとの異なる補完:
df.fillna({'A': df['A'].mean(), 'B': df['B'].median()})
のように辞書形式で指定することで、列ごとに異なる補完方法を適用することも可能です。
inplace
引数:dropna()
と同様に、inplace=True
を指定すると元のDataFrameが直接変更されます。
まとめ:欠損値処理でデータ分析をスムーズに
この記事では、PythonのPandasライブラリを用いて、データ分析における欠損値の基本的な処理方法を網羅的に解説しました。
isnull()
やisna()
とsum()
を組み合わせて欠損値の有無と数を検出する方法dropna()
を使って欠損値を含む行や列を削除する方法fillna()
を使って特定の値、統計量、あるいは前後の値で欠損値を補完する方法
これらの手法を適切に使いこなすことで、データクレンジングの時間を短縮し、より正確で信頼性の高い分析結果を得ることができます。データ分析の最初のステップとして欠損値処理をマスターすることは、その後のモデリングや可視化といった作業を円滑に進めるための重要な土台となります。ぜひ、ご自身のデータに適用して、その効果を実感してください。