pandasで欠損値NaNを前後の値から補間するinterpolate
pandas.DataFrame
, pandas.Series
の欠損値NaN
を前後の値から補間するにはinterpolate()
メソッドを使う。
- pandas.DataFrame.interpolate — pandas 2.0.3 documentation
- pandas.Series.interpolate — pandas 2.0.3 documentation
欠損値NaN
を削除したり特定の値で穴埋めする場合はdropna()
, fillna()
を使う。以下の記事を参照。
本記事のサンプルコードのpandasのバージョンは以下の通り。バージョンによって仕様が異なる可能性があるので注意。説明のため、NumPyも使う。
import pandas as pd
import numpy as np
print(pd.__version__)
# 2.0.3
interpolate()の基本的な使い方
以下のpandas.DataFrame
を例とする。
df = pd.DataFrame({'col1': [0, np.nan, np.nan, 3, 4],
'col2': [np.nan, 1, 2, np.nan, np.nan],
'col3': [4, np.nan, np.nan, 7, 10]})
print(df)
# col1 col2 col3
# 0 0.0 NaN 4.0
# 1 NaN 1.0 NaN
# 2 NaN 2.0 NaN
# 3 3.0 NaN 7.0
# 4 4.0 NaN 10.0
デフォルトでは各列に対して線形補間を行う。下端の欠損値には同じ値が繰り返される。上端の欠損値はそのまま。
print(df.interpolate())
# col1 col2 col3
# 0 0.0 NaN 4.0
# 1 1.0 1.0 5.0
# 2 2.0 2.0 6.0
# 3 3.0 2.0 7.0
# 4 4.0 2.0 10.0
以下、引数の設定について説明する。基本的にはpandas.Series
でも同じ。
第一引数method
で指定する補間方法については後述。
行 or 列を指定: 引数axis
引数axis=1
とすると各行に対して補間される。右端の欠損値には同じ値が繰り返される。左端の欠損値はそのまま。
print(df.interpolate(axis=1))
# col1 col2 col3
# 0 0.0 2.0 4.0
# 1 NaN 1.0 1.0
# 2 NaN 2.0 2.0
# 3 3.0 5.0 7.0
# 4 4.0 7.0 10.0
補間する連続欠損値の最大数を指定: 引数limit
欠損値が連続している場合、最大でいくつの欠損値を補間するかを引数limit
で指定できる。デフォルトはNone
で、連続する欠損値すべてが補間される。
print(df.interpolate(limit=1))
# col1 col2 col3
# 0 0.0 NaN 4.0
# 1 1.0 1.0 5.0
# 2 NaN 2.0 NaN
# 3 3.0 2.0 7.0
# 4 4.0 NaN 10.0
補間方向を指定: 引数limit_direction
補間方向は引数limit_direction
で'forward'
, 'backward'
, 'both'
のいずれかを指定する。
print(df.interpolate(limit=1, limit_direction='forward'))
# col1 col2 col3
# 0 0.0 NaN 4.0
# 1 1.0 1.0 5.0
# 2 NaN 2.0 NaN
# 3 3.0 2.0 7.0
# 4 4.0 NaN 10.0
print(df.interpolate(limit=1, limit_direction='backward'))
# col1 col2 col3
# 0 0.0 1.0 4.0
# 1 NaN 1.0 NaN
# 2 2.0 2.0 6.0
# 3 3.0 NaN 7.0
# 4 4.0 NaN 10.0
print(df.interpolate(limit=1, limit_direction='both'))
# col1 col2 col3
# 0 0.0 1.0 4.0
# 1 1.0 1.0 5.0
# 2 2.0 2.0 6.0
# 3 3.0 2.0 7.0
# 4 4.0 NaN 10.0
上述のように、デフォルトでは上端(または左端)の欠損値はそのままとなるが、limit_direction='both'
とすると両方とも補間される。
print(df.interpolate(limit_direction='both'))
# col1 col2 col3
# 0 0.0 1.0 4.0
# 1 1.0 1.0 5.0
# 2 2.0 2.0 6.0
# 3 3.0 2.0 7.0
# 4 4.0 2.0 10.0
内挿のみ or 外挿のみ or 両方を指定: 引数limit_area
補間対象領域は引数limit_area
で指定する。
'inside'
だと内挿のみ、'outside'
だと外挿のみ、None
(デフォルト)だと両方が対象となる。外挿については上述のlimit_direction
で前方(上側・左側)、後方(下側・右側)、両方を指定できる。
print(df.interpolate(limit_area='inside'))
# col1 col2 col3
# 0 0.0 NaN 4.0
# 1 1.0 1.0 5.0
# 2 2.0 2.0 6.0
# 3 3.0 NaN 7.0
# 4 4.0 NaN 10.0
print(df.interpolate(limit_area='outside'))
# col1 col2 col3
# 0 0.0 NaN 4.0
# 1 NaN 1.0 NaN
# 2 NaN 2.0 NaN
# 3 3.0 2.0 7.0
# 4 4.0 2.0 10.0
print(df.interpolate(limit_area='outside', limit_direction='both'))
# col1 col2 col3
# 0 0.0 1.0 4.0
# 1 NaN 1.0 NaN
# 2 NaN 2.0 NaN
# 3 3.0 2.0 7.0
# 4 4.0 2.0 10.0
なお、便宜上「外挿」という言葉を使っているが、上の結果からも分かるように、線形補間(デフォルト)では外側の値は端の値の繰り返しとなり線形外挿はされない。後述のスプライン補間では外側の値も繰り返しではなく外挿された値となる。
オブジェクト自体を更新するかを指定: 引数inplace
ほかの多くのメソッドと同様、引数inplace
でオブジェクト自体を更新するかどうかを指定できる。
df.interpolate(inplace=True)
print(df)
# col1 col2 col3
# 0 0.0 NaN 4.0
# 1 1.0 1.0 5.0
# 2 2.0 2.0 6.0
# 3 3.0 2.0 7.0
# 4 4.0 2.0 10.0
補間方法: 引数method
補間方法は第一引数method
に指定する。デフォルトはmethod='linear'
で線形補間。
線形補間: linear, index, values
method='linear'
(デフォルト)ではインデックス列は考慮されないが、method='index'
またはmethod='values'
とするとインデックス列を考慮して補間される。インデックス列をY軸、対象の列をX軸として線形補間するようなイメージ。
s = pd.Series([0, np.nan, np.nan, 3],
index=[0, 4, 6, 8])
print(s)
# 0 0.0
# 4 NaN
# 6 NaN
# 8 3.0
# dtype: float64
print(s.interpolate())
# 0 0.0
# 4 1.0
# 6 2.0
# 8 3.0
# dtype: float64
print(s.interpolate('index'))
# 0 0.00
# 4 1.50
# 6 2.25
# 8 3.00
# dtype: float64
デフォルトのmethod='linear'
はインデックス列が文字列でもよいが、method='index'
またはmethod='values'
だとエラーとなる。
s.index = list('abcd')
print(s)
# a 0.0
# b NaN
# c NaN
# d 3.0
# dtype: float64
print(s.interpolate())
# a 0.0
# b 1.0
# c 2.0
# d 3.0
# dtype: float64
# print(s.interpolate('index'))
# TypeError: Cannot cast array data from dtype('O') to dtype('float64') according to the rule 'safe'
前後の値をそのまま使用: ffill, pad, bfill, backfill
method='ffill'
またはmethod='pad'
だと前のNaN
ではない値、method='bfill'
またはmethod='backfill'
だと後ろのNaN
ではない値で埋められる。
s = pd.Series([np.nan, 1, np.nan, 2, np.nan])
print(s)
# 0 NaN
# 1 1.0
# 2 NaN
# 3 2.0
# 4 NaN
# dtype: float64
print(s.interpolate('ffill'))
# 0 NaN
# 1 1.0
# 2 1.0
# 3 2.0
# 4 2.0
# dtype: float64
print(s.interpolate('bfill'))
# 0 1.0
# 1 1.0
# 2 2.0
# 3 2.0
# 4 NaN
# dtype: float64
method=''ffill'
, 'pad'
のときはlimit_direction='forward'
、method=''bfill'
, 'backfill'
のときはlimit_direction='backward'
でなければならない。
# s.interpolate('ffill', limit_direction='both')
# ValueError: `limit_direction` must be 'forward' for method `ffill`
# s.interpolate('bfill', limit_direction='both')
# ValueError: `limit_direction` must be 'backward' for method `bfill`
fillna()
メソッドで引数method
を指定しても同様の処理が可能。
print(s.fillna(method='ffill'))
# 0 NaN
# 1 1.0
# 2 1.0
# 3 2.0
# 4 2.0
# dtype: float64
print(s.fillna(method='bfill'))
# 0 1.0
# 1 1.0
# 2 2.0
# 3 2.0
# 4 NaN
# dtype: float64
スプライン補間: spline
method='spline'
とするとスプライン補間。同時に引数order
に次数を指定する必要がある。
s = pd.Series([0, 10, np.nan, np.nan, 4, np.nan],
index=[0, 2, 5, 6, 8, 12])
print(s)
# 0 0.0
# 2 10.0
# 5 NaN
# 6 NaN
# 8 4.0
# 12 NaN
# dtype: float64
print(s.interpolate('spline', order=2))
# 0 0.00
# 2 10.00
# 5 13.75
# 6 12.00
# 8 4.00
# 12 -30.00
# dtype: float64
スプライン補間は常にインデックス列を考慮して補間される。インデックスが変わると補間結果も変わる。
s.index = range(6)
print(s)
# 0 0.0
# 1 10.0
# 2 NaN
# 3 NaN
# 4 4.0
# 5 NaN
# dtype: float64
print(s.interpolate('spline', order=2))
# 0 0.0
# 1 10.0
# 2 14.0
# 3 12.0
# 4 4.0
# 5 -10.0
# dtype: float64
したがって、スプライン補間する場合はインデックス列が数値である必要がある。文字列だとエラー。
s.index = list('abcdef')
print(s)
# a 0.0
# b 10.0
# c NaN
# d NaN
# e 4.0
# f NaN
# dtype: float64
# print(s.interpolate('spline', order=2))
# ValueError: Index column must be numeric or datetime type when using spline method other than linear.
# Try setting a numeric or datetime index column before interpolating.
その他
引数method
に指定できる補間方法としては、そのほか、'nearest'
, 'zero'
, 'slinear'
, 'quadratic'
, 'cubic'
, 'barycentric'
, 'krogh'
, 'polynomial'
, 'piecewise_polynomial'
, 'from_derivatives'
, 'pchip'
, 'akima'
がある。
公式ドキュメントにもあるように、上述のスプライン補間('spline'
)も含めて、これらはSciPyの関数に渡される。
- pandas.DataFrame.interpolate — pandas 2.0.3 documentation
- Interpolation (scipy.interpolate) — SciPy v1.11.1 Manual
いずれの場合も上述のスプライン補間と同様にインデックスが数値である必要がある。
時系列データの補間
時系列データについては専用の補間方法としてmethod='time'
が用意されている。method='time'
の場合、インデックス列の日時に合わせて線形補間される。
df_nan = pd.DataFrame({'value': [1, np.nan, np.nan, np.nan, 31]},
index=pd.to_datetime(['2018-01-01', '2018-01-02', '2018-01-15', '2018-01-20', '2018-01-31']))
print(df_nan)
# value
# 2018-01-01 1.0
# 2018-01-02 NaN
# 2018-01-15 NaN
# 2018-01-20 NaN
# 2018-01-31 31.0
print(df_nan.interpolate())
# value
# 2018-01-01 1.0
# 2018-01-02 8.5
# 2018-01-15 16.0
# 2018-01-20 23.5
# 2018-01-31 31.0
print(df_nan.interpolate('time'))
# value
# 2018-01-01 1.0
# 2018-01-02 2.0
# 2018-01-15 15.0
# 2018-01-20 20.0
# 2018-01-31 31.0
時系列データのリサンプリングについては以下の記事を参照。
データ型dtypeがobjectの場合(文字列など)
例えば、要素として文字列を含む列のデータ型dtype
はobject
となる。
s_object = pd.Series(['A', np.nan, 'C'])
print(s_object)
# 0 A
# 1 NaN
# 2 C
# dtype: object
object
列はデフォルトのmethod='linear'
などでは補間できずNaN
のまま。ffill
, pad
, bfill
, backfill
といった前後の値をそのまま使う方法の場合は埋められる。
print(s_object.interpolate())
# 0 A
# 1 NaN
# 2 C
# dtype: object
print(s_object.interpolate('ffill'))
# 0 A
# 1 A
# 2 C
# dtype: object
要素が数値でもobject
列の場合は同様。
s_object_num = pd.Series([0, np.nan, 2], dtype=object)
print(s_object_num)
# 0 0
# 1 NaN
# 2 2
# dtype: object
print(s_object_num.interpolate())
# 0 0
# 1 NaN
# 2 2
# dtype: object
print(s_object_num.interpolate('ffill'))
# 0 0
# 1 0
# 2 2
# dtype: int64
astype()
でfloat
に変換すれば線形補間などが可能。NaN
を含んでいるとint
には変換できないので注意。
print(s_object_num.astype(float).interpolate())
# 0 0.0
# 1 1.0
# 2 2.0
# dtype: float64
# print(s_object_num.astype(int))
# ValueError: cannot convert float NaN to integer