実データには欠損値(NaN)がつきものです。センサーの故障、通信エラー、データ収集の失敗など、さまざまな理由でデータが欠落します。pandas には欠損値を補間するための多様な方法が用意されています。
欠損値の確認
まず、欠損値を含むサンプルデータを作成します。
import pandas as pd import numpy as np # 欠損値を含むデータ df = pd.DataFrame({ 'date': pd.date_range('2024-01-01', periods=10, freq='D'), 'value': [100, np.nan, np.nan, 103, 105, np.nan, 108, 110, np.nan, 115] }).set_index('date') print(df) # value # date # 2024-01-01 100.0 # 2024-01-02 NaN # 2024-01-03 NaN # 2024-01-04 103.0 # 2024-01-05 105.0 # 2024-01-06 NaN # 2024-01-07 108.0 # 2024-01-08 110.0 # 2024-01-09 NaN # 2024-01-10 115.0
欠損値の数や位置を確認するには、いくつかの方法があります。
# 欠損値の数 print(df.isna().sum()) # value 4 # 欠損値がある行 print(df[df['value'].isna()]) # 欠損値の割合 print(df.isna().mean()) # value 0.4
前方補間(ffill)と後方補間(bfill)
最もシンプルな補間方法は、直前または直後の有効な値で埋める方法です。
# 前方補間:直前の値で埋める df['ffill'] = df['value'].ffill() # 後方補間:直後の値で埋める df['bfill'] = df['value'].bfill() print(df) # value ffill bfill # date # 2024-01-01 100.0 100.0 100.0 # 2024-01-02 NaN 100.0 103.0 # 2024-01-03 NaN 100.0 103.0 # 2024-01-04 103.0 103.0 103.0 # ...
連続する欠損値が多い場合、ffill だと同じ値が長く続いてしまいます。limit パラメータで補間する最大数を制限できます。
# 最大1つまで前方補間 df['ffill_limit'] = df['value'].ffill(limit=1) print(df) # value ffill_limit # date # 2024-01-01 100.0 100.0 # 2024-01-02 NaN 100.0 # 補間される # 2024-01-03 NaN NaN # 制限超過で補間されない # 2024-01-04 103.0 103.0 # ...
時系列データで「最新の既知の値を使う」場合に適している。例:センサー故障時に直前の値を使う。
データを遡って処理する場合に使う。リアルタイム処理では使えない(未来の値を参照するため)。
線形補間(interpolate)
前後の値から線形に補間する方法です。より自然な補間結果が得られます。
# 線形補間 df['linear'] = df['value'].interpolate(method='linear') print(df[['value', 'ffill', 'linear']]) # value ffill linear # date # 2024-01-01 100.0 100.0 100.0 # 2024-01-02 NaN 100.0 101.0 # (100+103)/2 に近い値 # 2024-01-03 NaN 100.0 102.0 # 2024-01-04 103.0 103.0 103.0 # ...
interpolate には多くの補間方法があります。
| method | 説明 |
|---|---|
| linear | 線形補間(デフォルト) |
| time | 時間間隔を考慮した線形補間 |
| index | インデックス値を考慮した補間 |
| pad / ffill | 前方補間 |
| nearest | 最も近い値で補間 |
| polynomial | 多項式補間 |
| spline | スプライン補間 |
時間ベースの補間
不規則な間隔の時系列データでは、method='time' を使うと時間間隔を考慮した補間ができます。
# 不規則な間隔のデータ irregular = pd.DataFrame({ 'value': [100, np.nan, 200] }, index=pd.to_datetime(['2024-01-01', '2024-01-02', '2024-01-11'])) # 線形補間(インデックス位置ベース) irregular['linear'] = irregular['value'].interpolate(method='linear') # 時間補間(時間間隔ベース) irregular['time'] = irregular['value'].interpolate(method='time') print(irregular) # value linear time # 2024-01-01 100.0 100.0 100.0 # 2024-01-02 NaN 150.0 110.0 # 時間では1/10の位置 # 2024-01-11 200.0 200.0 200.0
method='linear' はインデックスの位置(0, 1, 2)で補間するため中間値 150 になりますが、method='time' は時間間隔を考慮して 110 に近い値になります。
スプライン補間
滑らかな曲線で補間したい場合はスプライン補間が有効です。
# スプライン補間(3次) df['spline'] = df['value'].interpolate(method='spline', order=3) # 多項式補間(2次) df['poly'] = df['value'].interpolate(method='polynomial', order=2)
スプライン補間を使うには scipy がインストールされている必要があります。order パラメータで多項式の次数を指定します。
シンプルで高速。ほとんどの場合はこれで十分。
滑らかな曲線が必要な場合に使う。ただしデータの端で不安定になることがある。
特定の値で埋める
補間ではなく、固定値で埋めたい場合は fillna を使います。
# 0で埋める df['zero'] = df['value'].fillna(0) # 平均値で埋める df['mean'] = df['value'].fillna(df['value'].mean()) # 中央値で埋める df['median'] = df['value'].fillna(df['value'].median())
グループごとの平均値で埋めることもできます。
# カテゴリ付きデータ df = pd.DataFrame({ 'category': ['A', 'A', 'A', 'B', 'B', 'B'], 'value': [10, np.nan, 12, 100, np.nan, 110] }) # グループ別の平均値で埋める df['filled'] = df.groupby('category')['value'].transform( lambda x: x.fillna(x.mean()) ) print(df) # category value filled # 0 A 10.0 10.0 # 1 A NaN 11.0 # Aグループの平均 # 2 A 12.0 12.0 # 3 B 100.0 100.0 # 4 B NaN 105.0 # Bグループの平均 # 5 B 110.0 110.0
欠損値を削除する
補間が適切でない場合、欠損値を含む行を削除する選択肢もあります。
# 欠損値を含む行を削除 df_clean = df.dropna() # 特定の列に欠損値がある行を削除 df_clean = df.dropna(subset=['value']) # 全ての列が欠損の行のみ削除 df_clean = df.dropna(how='all') # 閾値以上のデータがある行を残す df_clean = df.dropna(thresh=2) # 2列以上に値があれば残す
補間方法の選び方
補間方法の選択は、データの性質と用途によって異なります。
センサーデータ
ffill または線形補間
直前の値を使うか、前後から推測
金融データ
ffill(直前の価格を使用)
ゼロや平均値での補間は不適切
科学データ
スプライン補間
滑らかな連続性が必要
欠損値の補間は分析結果に大きく影響します。データの欠損パターン(ランダムか、特定の条件で発生するか)を理解し、適切な補間方法を選ぶことが重要です。また、補間後のデータが元のデータの特性(分布、相関など)を大きく変えていないか確認することも大切です。