【Pandas】DataFrameの欠損値を線形補間する
時系列データでは、週末のデータがなかったり月次データだったりで、日次にする際に欠損値を補間することが多い。
色々な補間の仕方があるが、欠損値前後の2点を直線で結ぶ方法は、割と直観的に分かりやすいしデータによっては、十分なことがある。
大きく分けると、インデックスを認識して線形補間してくれる方法とインデックスを認識しない線形補間の2種類がある。
色々な補間の種類
欠損値の補間の方法にはいくつかの方法がある。
線形補間は欠損値前後の2点間を直線で結ぶ、割とシンプルなやり方。
などがある。
本記事では線形補間を扱う。
pandasのDataFrameにinterpolateという補間をするためのメソッドがある。
線形補間には大きく分けて2種類のやり方がある。
データ作成
月次データを日次にする場合を考えていく。
1import numpy as np
2import pandas as pd
3# 各種import
4import matplotlib.pyplot as plt
5import seaborn as sns
6import japanize_matplotlib
7
8sns.set(font="meiryo")
9
10# 6か月分の月次データ作成
11data = [10, 20, 60, 40, 50, 100]
12start_date = "2020-1-1"
13date_monthly = pd.date_range(start=start_date, periods=len(data), freq="MS")
14df_monthly = pd.DataFrame(data=np.array(data).T, columns=["A"], index=date_monthly)
15
16print(df_monthly)
17
18# A
19# 2020-01-01 10
20# 2020-02-01 20
21# 2020-03-01 60
22# 2020-04-01 40
23# 2020-05-01 50
24# 2020-06-01 100
このデータを日次にする。
1# 日次のインデックス作成
2date_daily = pd.date_range(start=start_date, periods=180, freq="D")
3
4df_daily = pd.DataFrame(np.zeros(len(date_daily)), index=date_daily)
5print(df_daily.head())
6# 0
7# 2020-01-01 0.0
8# 2020-01-02 0.0
9# 2020-01-03 0.0
10# 2020-01-04 0.0
11# 2020-01-05 0.0
12
13# 日次データに月次データを結合する。不要な列を削除する。
14df_daily2 = pd.merge(df_daily, df_monthly, how="left", left_index=True, right_index=True).drop(0, axis=1)
15print(df_daily2.head())
16# A
17# 2020-01-01 10.0
18# 2020-01-02 NaN
19# 2020-01-03 NaN
20# 2020-01-04 NaN
21# 2020-01-05 NaN
日次インデックスのDataFrameに月次インデックスのデータを結合する。
NaNがたくさんできるので、これらの値を線形補間していく。
インデックスを意識しない(現在の並び順による)補間
現在の並び順による線形補間はinterpolateのmethodにlinearを指定することで行える。
interpolateのmethodはデフォルトがlinearなので、引数を指定しない場合もこの動作になる。
1df_linear = df_daily2.interpolate(method="linear")
2print(df_linear.head())
3# A
4# 2020-01-01 10.000000
5# 2020-01-02 10.322581
6# 2020-01-03 10.645161
7# 2020-01-04 10.967742
8# 2020-01-05 11.290323
なんかそれっぽく補間されてそう。
なお、インデックスがMultiIndexの場合、Linear補間しかできない。
linearで補間した際のグラフ
1plt.figure(figsize=(12, 6.75), facecolor="w")
2sns.lineplot(data=df_linear, x=df_linear.index, y="A")
3title = "interpolate_linear"
4plt.title(title)
5plt.tight_layout()
6plt.show()
点と点が直線で結ばれていることが確認できる。
シャッフルされたデータをlinearで補間する場合
linearはインデックスは関係なく、現在のデータの並び順のみを見て補間するため、データの並び順をシャッフルするとその並び順で補間される。
1# データの並び順をシャッフル
2# インデックスをシャッフル
3shuffled_index = np.array(df_daily2.index)
4np.random.shuffle(shuffled_index)
5# シャッフルしたインデックスのDataFrameにシャッフル前のDataFrameをインデックスで結合する
6df_daily_shuffle = pd.DataFrame(np.zeros(df_daily2.shape[0]), index=shuffled_index)
7df_daily_shuffle = pd.merge(df_daily_shuffle, df_daily2, how="left", left_index=True, right_index=True).drop(0, axis=1)
8print(df_daily_shuffle)
9# A
10# 2020-01-11 NaN
11# 2020-04-01 40.0
12# 2020-05-18 NaN
13# 2020-06-04 NaN
14# 2020-06-21 NaN
15
16# シャッフルした状態で線形補間
17df_linear_shuffle = df_daily_shuffle.interpolate(method="linear")
18print(df_linear_shuffle)
19# A
20# 2020-01-11 NaN
21# 2020-04-01 40.000000
22# 2020-05-18 40.444444
23# 2020-06-04 40.888889
24# 2020-06-21 41.333333
シャッフルされたデータを時系列としてグラフ描画
1# 時系列でみるとシャッフルされた状態で補間されたのが分かる
2data = df_linear_shuffle
3
4plt.figure(figsize=(12, 6.75), facecolor="w")
5sns.lineplot(data=data, x=data.index, y="A")
6title = "interpolate_linear_shuffle"
7plt.title(title)
8plt.tight_layout()
9plt.show()
シャッフルされたデータの並び順のままグラフ描画
補間された時の並び順のままグラフを描画すると、線形補間されているのが分かる
1# 時系列で見なければ直線で補間される
2data = df_linear_shuffle
3
4plt.figure(figsize=(12, 6.75), facecolor="w")
5sns.lineplot(data=data, x=np.arange(data.shape[0]), y="A")
6title = "interpolate_linear_shuffle_non_time_series"
7plt.title(title)
8plt.tight_layout()
9plt.show()
インデックスを意識した補間
methodにlinearを指定した場合は、現在のデータの並び順で補間されるが、index、time、valuesなどを指定すると、現在の並び順とは関係なく、インデックスの順序で補間される。
timeで補間する場合、インデックスはDatetimeIndexになっている必要がある。
indexでの補間
以下の例ではインデックスを数値に変えているが、日付のままでも補間できる。
1# indexを数値に変える
2_df_daily = df_daily2.copy()
3int_index = np.arange(0, df_daily2.shape[0])
4_df_daily.index = int_index
5print(_df_daily.head())
6# A
7# 0 10.0
8# 1 NaN
9# 2 NaN
10# 3 NaN
11# 4 NaN
12
13# DataFrameの並び順をシャッフルする
14shuffled_index = np.array(_df_daily.index)
15np.random.shuffle(shuffled_index)
16df_daily_shuffle = pd.DataFrame(np.zeros(_df_daily.shape[0]), index=shuffled_index)
17df_daily_shuffle = pd.merge(df_daily_shuffle, _df_daily, how="left", left_index=True, right_index=True).drop(0, axis=1)
18print(df_daily_shuffle.head())
19# A
20# 101 NaN
21# 9 NaN
22# 166 NaN
23# 70 NaN
24# 140 NaN
25
26# methodにindexを指定して補間
27df_index_shuffle = df_daily_shuffle.interpolate(method="index")
1# ソートすれば綺麗に並ぶ
2data = df_index_shuffle.sort_index()
3
4plt.figure(figsize=(12, 6.75), facecolor="w")
5sns.lineplot(data=data, x=data.index, y="A")
6title = "interpolate_index_shuffle"
7plt.title(title)
8plt.tight_layout()
9plt.show()
valuesでの補間
indexと同じ。
timeでの補間
インデックスがDatetimeIndexになっていない場合、ValueErrorが返ってくる。
1ValueError: time-weighted interpolation only works on Series or DataFrames with a DatetimeIndex
2
データをシャッフルしても、インデックスの順序を見て補間する。
1# データをシャッフル
2shuffled_index = np.array(df_daily2.index)
3np.random.shuffle(shuffled_index)
4df_daily_shuffle = pd.DataFrame(np.zeros(df_daily2.shape[0]), index=shuffled_index)
5df_daily_shuffle = pd.merge(df_daily_shuffle, df_daily2, how="left", left_index=True, right_index=True).drop(0, axis=1)
6print(df_daily_shuffle)
7# A
8# 2020-05-27 NaN
9# 2020-05-11 NaN
10# 2020-02-05 NaN
11# 2020-02-25 NaN
12# 2020-02-16 NaN
13
14# methodにtimeを指定して補間
15df_time_shuffle = df_daily_shuffle.interpolate(method="time")
時系列でグラフ描画すると、直線で補間されていることを確認できる。
1data = df_time_shuffle
2
3plt.figure(figsize=(12, 6.75), facecolor="w")
4sns.lineplot(data=data, x=data.index, y="A")
5title = "interpolate_time shuffle"
6plt.title(title)
7plt.tight_layout()
8plt.show()
まとめ
現在のデータの並び順で線形補間したければlinear(もしくは未指定)。
インデックスの順序で線形補間したければindex。
1# 現在のデータの並び順で補間
2df_linear = df_daily.interpolate(method="linear")
3
4# インデックスの順序でデータを補間、日付型もOK
5df_index = df_daily.interpolate(method="index")
6
7# 日付型インデックスの順序でデータを補間
8df_time = df_daily.interpolate(method="time")