pandas で欠損値(NaN)を補間する方法まとめ

実データには欠損値(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
# ...
ffill(前方補間)

時系列データで「最新の既知の値を使う」場合に適している。例:センサー故障時に直前の値を使う。

bfill(後方補間)

データを遡って処理する場合に使う。リアルタイム処理では使えない(未来の値を参照するため)。

線形補間(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(直前の価格を使用)

ゼロや平均値での補間は不適切

科学データ

スプライン補間

滑らかな連続性が必要

欠損値の補間は分析結果に大きく影響します。データの欠損パターン(ランダムか、特定の条件で発生するか)を理解し、適切な補間方法を選ぶことが重要です。また、補間後のデータが元のデータの特性(分布、相関など)を大きく変えていないか確認することも大切です。