pandasで時系列データをリサンプリングするresample, asfreq
時系列データを元データより高い頻度または低い頻度で再度サンプリングすることをリサンプリングと呼ぶ。以下の二通りがある。
- アップサンプリング(オーバーサンプリング)
- より高い頻度(短い周期)でリサンプリング
- ダウンサンプリング(アンダーサンプリング)
- より低い頻度(長い周期)でリサンプリング
pandasで時系列データをリサンプリングするにはresample()
またはasfreq()
を使う。
- pandas.DataFrame.asfreq — pandas 0.23.3 documentation
- pandas.DataFrame.resample — pandas 0.23.3 documentation
resample()
とasfreq()
にはそれぞれ以下のような違いがある。
resample()
: データを集約(合計や平均など)asfreq()
: データを選択
ここでは以下の内容について説明する。
asfreq()
の使い方- 基本的な使い方
- 元データに無い場合に任意の値で穴埋め: 引数
fill_value
- 元データに無い場合に前後の値で穴埋め: 引数
method
- 重複データがある場合
resample()
の使い方- 基本的な使い方
- ラベルを開始日時にするか終了日時にするかを指定: 引数
label
- 開始日時と終了日時のどちらを期間に含むかを指定: 引数
closed
- インデックスとみなす列名を指定: 引数
on
- アップサンプリングにおける値の補間
- 特定の値、前後の値でそのまま穴埋め
- 前後の値を使って補間(線形補間、スプライン補間など)
- 不等間隔の欠損値の補間
pandas.DataFrame
やpandas.Series
のインデックスをdatetime64
型のDatetimeIndex
として設定し時系列データとして扱う方法などについては以下の記事を参照。
また、集約でも選択でもなく、窓関数を適用する場合はrolling()
メソッドを使う。移動平均などを算出するのはrolling()
。以下の記事を参照。
データの集約にはマルチインデックスを用いる方法もある。こちらは曜日ごとの集計なども可能。
asfreq()の使い方
以下の2日おきのデータを例とする。
import pandas as pd
df = pd.DataFrame({'value': range(1, 32, 2)},
index=pd.date_range('2018-08-01', '2018-08-31', freq='2D'))
print(df)
# value
# 2018-08-01 1
# 2018-08-03 3
# 2018-08-05 5
# 2018-08-07 7
# 2018-08-09 9
# 2018-08-11 11
# 2018-08-13 13
# 2018-08-15 15
# 2018-08-17 17
# 2018-08-19 19
# 2018-08-21 21
# 2018-08-23 23
# 2018-08-25 25
# 2018-08-27 27
# 2018-08-29 29
# 2018-08-31 31
基本的な使い方
asfreq()
の第一引数freq
にはD
(日次)、W
(週次)などの頻度コードを指定する。詳細は以下の記事を参照。
上述のようにasfreq()
はデータの選択なので、元のデータに無い日時の値は欠損値NaN
となる。
print(df.asfreq('10D'))
# value
# 2018-08-01 1
# 2018-08-11 11
# 2018-08-21 21
# 2018-08-31 31
print(df.asfreq('5D'))
# value
# 2018-08-01 1.0
# 2018-08-06 NaN
# 2018-08-11 11.0
# 2018-08-16 NaN
# 2018-08-21 21.0
# 2018-08-26 NaN
# 2018-08-31 31.0
print(df.asfreq('W'))
# value
# 2018-08-05 5.0
# 2018-08-12 NaN
# 2018-08-19 19.0
# 2018-08-26 NaN
print(df.asfreq('W-WED'))
# value
# 2018-08-01 1.0
# 2018-08-08 NaN
# 2018-08-15 15.0
# 2018-08-22 NaN
# 2018-08-29 29.0
以下、元のデータに無い日時の値の穴埋め方法について説明する。asfreq()
でできるのは任意の値や前後の値をそのまま使った単純な穴埋め。前後の値を使った線形補間などはresample()
を使う。後述。
元データに無い場合に任意の値で穴埋め: 引数fill_value
元のデータに無い日時の値を任意の値で穴埋めするには引数fill_value
を指定する。
print(df.asfreq('W', fill_value=0))
# value
# 2018-08-05 5
# 2018-08-12 0
# 2018-08-19 19
# 2018-08-26 0
元データに無い場合に前後の値で穴埋め: 引数method
元のデータに無い日時の値を前後の値で穴埋めするには引数method
を指定する。
method=pad
またはmethod=ffill
で前の値で穴埋め、method=backfill
またはmethod=bfill
で後ろの値で穴埋めとなる。
print(df.asfreq('W', method='pad'))
# value
# 2018-08-05 5
# 2018-08-12 11
# 2018-08-19 19
# 2018-08-26 25
print(df.asfreq('W', method='ffill'))
# value
# 2018-08-05 5
# 2018-08-12 11
# 2018-08-19 19
# 2018-08-26 25
print(df.asfreq('W', method='backfill'))
# value
# 2018-08-05 5
# 2018-08-12 13
# 2018-08-19 19
# 2018-08-26 27
print(df.asfreq('W', method='bfill'))
# value
# 2018-08-05 5
# 2018-08-12 13
# 2018-08-19 19
# 2018-08-26 27
欠損値NaN
が連続している場合も同様。
df_3D = pd.DataFrame({'value': range(1, 32, 3)},
index=pd.date_range('2018-08-01', '2018-08-31', freq='3D'))
print(df_3D.asfreq('D', method='bfill'))
# value
# 2018-08-01 1
# 2018-08-02 4
# 2018-08-03 4
# 2018-08-04 4
# 2018-08-05 7
# 2018-08-06 7
# 2018-08-07 7
# 2018-08-08 10
# 2018-08-09 10
# 2018-08-10 10
# 2018-08-11 13
# 2018-08-12 13
# 2018-08-13 13
# 2018-08-14 16
# 2018-08-15 16
# 2018-08-16 16
# 2018-08-17 19
# 2018-08-18 19
# 2018-08-19 19
# 2018-08-20 22
# 2018-08-21 22
# 2018-08-22 22
# 2018-08-23 25
# 2018-08-24 25
# 2018-08-25 25
# 2018-08-26 28
# 2018-08-27 28
# 2018-08-28 28
# 2018-08-29 31
# 2018-08-30 31
# 2018-08-31 31
前後の値から線形補間などを行いたい場合はresample()
を使う。後述。
重複データがある場合
以下のような同じ日付の違う時刻のデータを例とする。
df_h = pd.DataFrame({'value': range(9)},
index=pd.date_range('2018-08-01', '2018-08-05', freq='12H'))
print(df_h)
# value
# 2018-08-01 00:00:00 0
# 2018-08-01 12:00:00 1
# 2018-08-02 00:00:00 2
# 2018-08-02 12:00:00 3
# 2018-08-03 00:00:00 4
# 2018-08-03 12:00:00 5
# 2018-08-04 00:00:00 6
# 2018-08-04 12:00:00 7
# 2018-08-05 00:00:00 8
これをasfreq()
でfreq='D'
(日次)としてダウンサンプリングする場合、先頭の値が選択される。
print(df_h.asfreq('D'))
# value
# 2018-08-01 0
# 2018-08-02 2
# 2018-08-03 4
# 2018-08-04 6
# 2018-08-05 8
平均値など、重複する複数のデータを集約した値を使いたい場合は次に説明するresample()
を使う。
print(df_h.resample('D').mean())
# value
# 2018-08-01 0.5
# 2018-08-02 2.5
# 2018-08-03 4.5
# 2018-08-04 6.5
# 2018-08-05 8.0
resample()の使い方
ここでも2日おきのデータを例とする。
import pandas as pd
df = pd.DataFrame({'value': range(1, 32, 2)},
index=pd.date_range('2018-08-01', '2018-08-31', freq='2D'))
print(df)
# value
# 2018-08-01 1
# 2018-08-03 3
# 2018-08-05 5
# 2018-08-07 7
# 2018-08-09 9
# 2018-08-11 11
# 2018-08-13 13
# 2018-08-15 15
# 2018-08-17 17
# 2018-08-19 19
# 2018-08-21 21
# 2018-08-23 23
# 2018-08-25 25
# 2018-08-27 27
# 2018-08-29 29
# 2018-08-31 31
補間してアップサンプリングする例については後述。
基本的な使い方
resample()
の第一引数rule
にも、asfreq()
と同様にD
(日次)、W
(週次)などの頻度コードを指定する。詳細は以下の記事を参照。
resample()
が返すのはDatetimeIndexResampler
型のオブジェクトで、それ自体をprint()
で出力しても値は表示されない。
print(df.resample('W'))
# DatetimeIndexResampler [freq=<Week: weekday=6>, axis=0, closed=right, label=right, convention=start, base=0]
print(type(df.resample('W')))
# <class 'pandas.core.resample.DatetimeIndexResampler'>
mean()
(平均値)やmedian()
(中央値)、sum()
(合計)などのメソッドを呼ぶことで集約された値が算出される。以下は週ごとの平均などの例。
print(df.resample('W').mean())
# value
# 2018-08-05 3
# 2018-08-12 9
# 2018-08-19 16
# 2018-08-26 23
# 2018-09-02 29
print(df.resample('W').median())
# value
# 2018-08-05 3
# 2018-08-12 9
# 2018-08-19 16
# 2018-08-26 23
# 2018-09-02 29
print(df.resample('W').sum())
# value
# 2018-08-05 9
# 2018-08-12 27
# 2018-08-19 64
# 2018-08-26 69
# 2018-09-02 87
グループ化したオブジェクトから計算用のメソッドを呼ぶということで、使い方のイメージとしてはgroupby()
に近い。
ほかにも、先頭の値、末尾の値を出力するfirst()
, last()
、個数を出力するcount()
、OHLC(Open: 始値、High: 高値、Low: 安値、Close: 終値)を出力するohlc()
もある。
print(df.resample('W').first())
# value
# 2018-08-05 1
# 2018-08-12 7
# 2018-08-19 13
# 2018-08-26 21
# 2018-09-02 27
print(df.resample('W').last())
# value
# 2018-08-05 5
# 2018-08-12 11
# 2018-08-19 19
# 2018-08-26 25
# 2018-09-02 31
print(df.resample('W').count())
# value
# 2018-08-05 3
# 2018-08-12 3
# 2018-08-19 4
# 2018-08-26 3
# 2018-09-02 3
print(df.resample('W').ohlc())
# value
# open high low close
# 2018-08-05 1 5 1 5
# 2018-08-12 7 11 7 11
# 2018-08-19 13 19 13 19
# 2018-08-26 21 25 21 25
# 2018-09-02 27 31 27 31
ohlc()
についての詳細は以下の記事を参照。
そのほか、任意の関数を指定できるapply()
や、複数の関数をまとめて指定できるagg()
などもある。
print(df.resample('W').apply(list))
# value
# 2018-08-05 [1, 3, 5]
# 2018-08-12 [7, 9, 11]
# 2018-08-19 [13, 15, 17, 19]
# 2018-08-26 [21, 23, 25]
# 2018-09-02 [27, 29, 31]
print(df.resample('W').agg(['min', 'max', 'sum']))
# value
# min max sum
# 2018-08-05 1 5 9
# 2018-08-12 7 11 27
# 2018-08-19 13 19 64
# 2018-08-26 21 25 69
# 2018-09-02 27 31 87
DatetimeIndexResampler
型のオブジェクトで使えるメソッドの一覧は公式ドキュメントを参照。
ラベルを開始日時にするか終了日時にするかを指定: 引数label
インデックス列のラベルを開始日時にするか終了日時にするかは引数label
で指定する。
label='left'
とすると開始日時、label='right'
とすると終了日時がラベルとなる。
デフォルトでは開始日時がラベルとなる(label='left'
)が、M
, Q
, A
, Y
, BM
, BQ
, BA
, BY
(それぞれ月、四半期、年の末日・最終営業日)及びW
はデフォルトがlabel='right'
となる。
print(df.resample('W').apply(list))
# value
# 2018-08-05 [1, 3, 5]
# 2018-08-12 [7, 9, 11]
# 2018-08-19 [13, 15, 17, 19]
# 2018-08-26 [21, 23, 25]
# 2018-09-02 [27, 29, 31]
print(df.resample('W', label='left').apply(list))
# value
# 2018-07-29 [1, 3, 5]
# 2018-08-05 [7, 9, 11]
# 2018-08-12 [13, 15, 17, 19]
# 2018-08-19 [21, 23, 25]
# 2018-08-26 [27, 29, 31]
開始日時と終了日時のどちらを期間に含むかを指定: 引数closed
引数closed
で開始日時と終了日時のどちらを期間に含むかを指定する。
closed='left'
とすると開始日時 <= 期間 < 終了日時
となり、closed='right'
とすると開始日時 < 期間 <= 終了日時
となる。
label
と同じく、デフォルトではclosed='left'
、M
, Q
, A
, Y
, BM
, BQ
, BA
, BY
(それぞれ月、四半期、年の末日・最終営業日)及びW
はデフォルトがclosed='right'
となる。
基本的にはlabel
とclosed
は同じ値にしておかないと直感に反する。
print(df.resample('W', label='left').apply(list))
# value
# 2018-07-29 [1, 3, 5]
# 2018-08-05 [7, 9, 11]
# 2018-08-12 [13, 15, 17, 19]
# 2018-08-19 [21, 23, 25]
# 2018-08-26 [27, 29, 31]
print(df.resample('W', label='left', closed='left').apply(list))
# value
# 2018-07-29 [1, 3]
# 2018-08-05 [5, 7, 9, 11]
# 2018-08-12 [13, 15, 17]
# 2018-08-19 [19, 21, 23, 25]
# 2018-08-26 [27, 29, 31]
インデックスとみなす列名を指定: 引数on
これまでの例のようにインデックス列が日時データであればそのままで問題ないが、インデックスではない列に日時データが格納されている場合、引数on
に日時データが格納された列名を指定するとリサンプリングが可能。
df_reset = df.reset_index()
print(df_reset)
# index value
# 0 2018-08-01 1
# 1 2018-08-03 3
# 2 2018-08-05 5
# 3 2018-08-07 7
# 4 2018-08-09 9
# 5 2018-08-11 11
# 6 2018-08-13 13
# 7 2018-08-15 15
# 8 2018-08-17 17
# 9 2018-08-19 19
# 10 2018-08-21 21
# 11 2018-08-23 23
# 12 2018-08-25 25
# 13 2018-08-27 27
# 14 2018-08-29 29
# 15 2018-08-31 31
print(df_reset.resample('W', on='index').sum())
# value
# index
# 2018-08-05 9
# 2018-08-12 27
# 2018-08-19 64
# 2018-08-26 69
# 2018-09-02 87
アップサンプリングにおける値の補間
アップサンプリングする場合、元のデータに含まれない日時のデータを補間する必要がある。
以下の時系列データを例とする。
import pandas as pd
df = pd.DataFrame({'value': [1, 15, 31]},
index=pd.to_datetime(['2018-01-01', '2018-01-15', '2018-01-31']))
print(df)
# value
# 2018-01-01 1
# 2018-01-15 15
# 2018-01-31 31
特定の値、前後の値でそのまま穴埋め
上述のように、asfreq()
の引数fill_value
, method
を指定すると、特定の値で穴埋めしたり前後の値いずれかで穴埋めしたりできる。
print(df.asfreq('5D'))
# value
# 2018-01-01 1.0
# 2018-01-06 NaN
# 2018-01-11 NaN
# 2018-01-16 NaN
# 2018-01-21 NaN
# 2018-01-26 NaN
# 2018-01-31 31.0
print(df.asfreq('5D', fill_value=15))
# value
# 2018-01-01 1
# 2018-01-06 15
# 2018-01-11 15
# 2018-01-16 15
# 2018-01-21 15
# 2018-01-26 15
# 2018-01-31 31
print(df.asfreq('5D', method='pad'))
# value
# 2018-01-01 1
# 2018-01-06 1
# 2018-01-11 1
# 2018-01-16 15
# 2018-01-21 15
# 2018-01-26 15
# 2018-01-31 31
print(df.asfreq('5D', method='bfill'))
# value
# 2018-01-01 1
# 2018-01-06 15
# 2018-01-11 15
# 2018-01-16 31
# 2018-01-21 31
# 2018-01-26 31
# 2018-01-31 31
resample()
でもffill()
, bfill()
メソッドが用意されている。
print(df.resample('5D').ffill())
# value
# 2018-01-01 1
# 2018-01-06 1
# 2018-01-11 1
# 2018-01-16 15
# 2018-01-21 15
# 2018-01-26 15
# 2018-01-31 31
print(df.resample('5D').bfill())
# value
# 2018-01-01 1
# 2018-01-06 15
# 2018-01-11 15
# 2018-01-16 31
# 2018-01-21 31
# 2018-01-26 31
# 2018-01-31 31
resample()
ではnearest()
メソッドがあり、前後どちらか近い方の値で穴埋めできる。
print(df.resample('5D').nearest())
# value
# 2018-01-01 1
# 2018-01-06 1
# 2018-01-11 15
# 2018-01-16 15
# 2018-01-21 15
# 2018-01-26 31
# 2018-01-31 31
前後の値を使って補間(線形補間、スプライン補間など)
resample()
にはinterpolate()
メソッドが用意されている。デフォルトでは前後の値から線形補間される。
print(df.resample('5D').interpolate())
# value
# 2018-01-01 1.0
# 2018-01-06 6.0
# 2018-01-11 11.0
# 2018-01-16 16.0
# 2018-01-21 21.0
# 2018-01-26 26.0
# 2018-01-31 31.0
interpolate()
ではアップサンプリング後のデータを使って補間が行われるので注意。
例えば中央の値を変更しても、中央の値が選択されない間隔でアップサンプリングすると、中央の値が考慮されずに補間されてしまう。
df.loc['2018-01-15', 'value'] = 100
print(df)
# value
# 2018-01-01 1
# 2018-01-15 100
# 2018-01-31 31
print(df.resample('5D').interpolate())
# value
# 2018-01-01 1.0
# 2018-01-06 6.0
# 2018-01-11 11.0
# 2018-01-16 16.0
# 2018-01-21 21.0
# 2018-01-26 26.0
# 2018-01-31 31.0
中央の値が選択される間隔でアップサンプリングしてから任意の間隔にダウンサンプリングすればOK。
print(df.resample('D').interpolate())
# value
# 2018-01-01 1.000000
# 2018-01-02 8.071429
# 2018-01-03 15.142857
# 2018-01-04 22.214286
# 2018-01-05 29.285714
# 2018-01-06 36.357143
# 2018-01-07 43.428571
# 2018-01-08 50.500000
# 2018-01-09 57.571429
# 2018-01-10 64.642857
# 2018-01-11 71.714286
# 2018-01-12 78.785714
# 2018-01-13 85.857143
# 2018-01-14 92.928571
# 2018-01-15 100.000000
# 2018-01-16 95.687500
# 2018-01-17 91.375000
# 2018-01-18 87.062500
# 2018-01-19 82.750000
# 2018-01-20 78.437500
# 2018-01-21 74.125000
# 2018-01-22 69.812500
# 2018-01-23 65.500000
# 2018-01-24 61.187500
# 2018-01-25 56.875000
# 2018-01-26 52.562500
# 2018-01-27 48.250000
# 2018-01-28 43.937500
# 2018-01-29 39.625000
# 2018-01-30 35.312500
# 2018-01-31 31.000000
print(df.resample('D').interpolate().asfreq('5D'))
# value
# 2018-01-01 1.000000
# 2018-01-06 36.357143
# 2018-01-11 71.714286
# 2018-01-16 95.687500
# 2018-01-21 74.125000
# 2018-01-26 52.562500
# 2018-01-31 31.000000
スプライン補間の例。interpolate()
の第一引数method
に'spline'
を指定した上で引数order
に次数を指定する。
print(df.resample('D').interpolate('spline', order=2))
# value
# 2018-01-01 1.000000
# 2018-01-02 13.004464
# 2018-01-03 24.250000
# 2018-01-04 34.736607
# 2018-01-05 44.464286
# 2018-01-06 53.433036
# 2018-01-07 61.642857
# 2018-01-08 69.093750
# 2018-01-09 75.785714
# 2018-01-10 81.718750
# 2018-01-11 86.892857
# 2018-01-12 91.308036
# 2018-01-13 94.964286
# 2018-01-14 97.861607
# 2018-01-15 100.000000
# 2018-01-16 101.379464
# 2018-01-17 102.000000
# 2018-01-18 101.861607
# 2018-01-19 100.964286
# 2018-01-20 99.308036
# 2018-01-21 96.892857
# 2018-01-22 93.718750
# 2018-01-23 89.785714
# 2018-01-24 85.093750
# 2018-01-25 79.642857
# 2018-01-26 73.433036
# 2018-01-27 66.464286
# 2018-01-28 58.736607
# 2018-01-29 50.250000
# 2018-01-30 41.004464
# 2018-01-31 31.000000
print(df.resample('D').interpolate('spline', order=2).asfreq('5D'))
# value
# 2018-01-01 1.000000
# 2018-01-06 53.433036
# 2018-01-11 86.892857
# 2018-01-16 101.379464
# 2018-01-21 96.892857
# 2018-01-26 73.433036
# 2018-01-31 31.000000
不等間隔の欠損値の補間
等間隔にアップサンプリングする場合ではなく、例えば以下のような不等間隔の日時データに欠損値NaN
がある場合についても触れておく。
import numpy as np
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
pandas.DataFrame
のinterpolate()
メソッドで補間できるが、デフォルトの線形補間(第一引数method='linear'
)の場合、インデックスの日時は考慮されない。
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
第一引数method='time'
とすると日時が考慮された線形補間となる。
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
pandas.DataFrame
のメソッドinterpolate()
についての詳細は以下の記事を参照。
index
を変更したい場合はreindex()
を使う。