データ分析の過程において、欠損値(NaN)を単に削除してしまうと、重要なデータ量が減少したり、時系列の連続性が失われたりするリスクがあります。そのため、データの性質に合わせて「0」や「平均値」、あるいは「前後の値」で穴埋め(補完)を行うことが一般的です。
本記事では、Pandasの fillna、ffill、bfill メソッドを使用して、欠損値を適切な値で埋める手法について解説します。
目次
欠損値補完の基本戦略
データの種類によって、適切な補完方法は異なります。
- 固定値で埋める: 数値の
0や文字列の"Unknown"など。 - 統計量で埋める: 平均値、中央値、最頻値など。データの分布を崩したくない場合に有効。
- 前後の値で埋める: 時系列データなど、直近の状態が継続していると仮定できる場合に有効。
実装サンプルコード
ここでは、あるWebサーバーの監視ログ(CPU使用率、メモリ使用量、アクティブセッション数)を題材にします。通信エラー等により一部のデータが欠損している状態からスタートします。
import pandas as pd
import numpy as np
def demonstrate_fillna():
"""
Pandasを用いた様々な欠損値補完方法を実演する関数
"""
# 1. サンプルデータの作成 (サーバー監視ログ)
# CPU: パーセンテージ
# Memory: 使用量(GB)
# Sessions: 接続数
server_logs = {
"CPU_Usage": [45.0, 50.5, np.nan, 52.0, np.nan],
"Memory_GB": [12.0, np.nan, 12.5, np.nan, 13.0],
"Sessions": [100, 100, 105, np.nan, 120]
}
df = pd.DataFrame(server_logs)
print("--- 元のデータセット(欠損あり) ---")
print(df)
print("\n")
# 2. 固定値(0など)で補完する
print("=== 固定値による補完 (fillna) ===")
# すべての欠損値を 0 で埋める
# データがない=稼働していない、とみなす場合などに使用
df_fill_zero = df.fillna(0)
print("--- 0で補完 ---")
print(df_fill_zero)
# 特定の列だけを固定値で埋める場合
# 元のDataFrameには影響させず、コピーを作成して処理
df_col_fill = df.copy()
df_col_fill["CPU_Usage"] = df_col_fill["CPU_Usage"].fillna(0)
print("\n--- CPU列のみ0で補完 ---")
print(df_col_fill["CPU_Usage"])
print("\n")
# 3. 統計量(平均・中央値・最頻値)で補完する
print("=== 統計量による補完 ===")
# 平均値 (Mean) で補完
# データの分布を維持したい場合に一般的
df_fill_mean = df.fillna(df.mean())
print("--- 平均値で補完 ---")
print(df_fill_mean)
print("(CPU平均: {:.2f}, Mem平均: {:.2f})".format(df["CPU_Usage"].mean(), df["Memory_GB"].mean()))
# 中央値 (Median) で補完
# 外れ値の影響を受けにくい
df_fill_median = df.fillna(df.median())
print("\n--- 中央値で補完 ---")
print(df_fill_median)
# 最頻値 (Mode) で補完
# セッション数など、頻繁に現れる値を採用したい場合
# mode()はDataFrameを返すため、iloc[0]で最初の行を取得する必要があります
mode_values = df.mode().iloc[0]
df_fill_mode = df.fillna(mode_values)
print("\n--- 最頻値で補完 ---")
print(df_fill_mode)
print("\n")
# 4. 前後の値で補完する(時系列データ向け)
print("=== 前後の値による補完 (ffill / bfill) ===")
# 前方補完 (Forward Fill)
# 直前の有効な値をコピーして埋める
# 「ログが途切れたが、直前の状態が続いている」と仮定する場合に有効
df_ffill = df.ffill()
print("--- 前方補完 (ffill) ---")
print(df_ffill)
# 後方補完 (Backward Fill)
# 直後の有効な値をコピーして埋める
df_bfill = df.bfill()
print("\n--- 後方補完 (bfill) ---")
print(df_bfill)
if __name__ == "__main__":
demonstrate_fillna()
実行結果
--- 元のデータセット(欠損あり) ---
CPU_Usage Memory_GB Sessions
0 45.0 12.0 100.0
1 50.5 NaN 100.0
2 NaN 12.5 105.0
3 52.0 NaN NaN
4 NaN 13.0 120.0
=== 固定値による補完 (fillna) ===
--- 0で補完 ---
CPU_Usage Memory_GB Sessions
0 45.0 12.0 100.0
1 50.5 0.0 100.0
2 0.0 12.5 105.0
3 52.0 0.0 0.0
4 0.0 13.0 120.0
--- CPU列のみ0で補完 ---
0 45.0
1 50.5
2 0.0
3 52.0
4 0.0
Name: CPU_Usage, dtype: float64
=== 統計量による補完 ===
--- 平均値で補完 ---
CPU_Usage Memory_GB Sessions
0 45.000000 12.00 100.00
1 50.500000 12.50 100.00
2 49.166667 12.50 105.00
3 52.000000 12.50 106.25
4 49.166667 13.00 120.00
(CPU平均: 49.17, Mem平均: 12.50)
--- 中央値で補完 ---
CPU_Usage Memory_GB Sessions
0 45.0 12.0 100.0
1 50.5 12.5 100.0
2 50.5 12.5 105.0
3 52.0 12.5 102.5
4 50.5 13.0 120.0
--- 最頻値で補完 ---
CPU_Usage Memory_GB Sessions
0 45.0 12.0 100.0
1 50.5 12.0 100.0
2 45.0 12.5 105.0
3 52.0 12.0 100.0
4 45.0 13.0 120.0
=== 前後の値による補完 (ffill / bfill) ===
--- 前方補完 (ffill) ---
CPU_Usage Memory_GB Sessions
0 45.0 12.0 100.0
1 50.5 12.0 100.0
2 50.5 12.5 105.0
3 52.0 12.5 105.0
4 52.0 13.0 120.0
--- 後方補完 (bfill) ---
CPU_Usage Memory_GB Sessions
0 45.0 12.0 100.0
1 50.5 12.5 100.0
2 52.0 12.5 105.0
3 52.0 13.0 120.0
4 NaN 13.0 120.0
解説:ffill と bfill の挙動
時系列データの処理において、ffill(forward fill)と bfill(backward fill)は非常に重要です。
- ffill: 「前の時間帯の値が継続している」と仮定して埋めます。実行結果のインデックス2(Memory_GB)を見ると、直前のインデックス0の値(12.0)がコピーされていることがわかります。
- bfill: 「後の時間帯の値から推測する」場合に用います。ただし、データの最後尾(インデックス4のCPU_Usage)のように、後ろに値が存在しない場合は
NaNのまま残る点に注意が必要です。
