【Python】Pandasで欠損値を補完する(fillna / ffill / bfill)

データ分析の過程において、欠損値(NaN)を単に削除してしまうと、重要なデータ量が減少したり、時系列の連続性が失われたりするリスクがあります。そのため、データの性質に合わせて「0」や「平均値」、あるいは「前後の値」で穴埋め(補完)を行うことが一般的です。

本記事では、Pandasの fillnaffillbfill メソッドを使用して、欠損値を適切な値で埋める手法について解説します。

目次

欠損値補完の基本戦略

データの種類によって、適切な補完方法は異なります。

  1. 固定値で埋める: 数値の 0 や文字列の "Unknown" など。
  2. 統計量で埋める: 平均値、中央値、最頻値など。データの分布を崩したくない場合に有効。
  3. 前後の値で埋める: 時系列データなど、直近の状態が継続していると仮定できる場合に有効。

実装サンプルコード

ここでは、ある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 のまま残る点に注意が必要です。
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次