pandasで窓関数を適用するrollingを使って移動平均などを算出
pandas.DataFrame, pandas.Seriesに窓関数(Window Function)を適用するにはrolling()を使う。
- pandas.DataFrame.rolling — pandas 0.23.3 documentation
- pandas.Series.rolling — pandas 0.23.3 documentation
窓関数はフィルタをデザインする際などに使われるが、単純に移動平均線を算出(前後のデータの平均を算出)したりするのにも使える。
ここでは以下の内容について説明する。
rolling()の基本的な使い方- Windowの幅を指定: 引数
window - Windowの中心に結果の値を格納する: 引数
center - 最小データ個数を指定: 引数
min_periods - 窓関数の種類を指定: 引数
win_type - 列方向に窓関数を適用: 引数
axis
- Windowの幅を指定: 引数
window.Rolling型に適用できるメソッド- 時系列データにおける
rolling()とresample()
rolling()の基本的な使い方
以下のpandas.Seriesを例とする。
import pandas as pd
s = pd.Series(range(10))
print(s)
# 0 0
# 1 1
# 2 2
# 3 3
# 4 4
# 5 5
# 6 6
# 7 7
# 8 8
# 9 9
# dtype: int64
rolling()メソッドを呼んでも何か値が算出されるわけではなく、window.Rolling型のオブジェクトが返される。
print(s.rolling(3))
# Rolling [window=3,center=False,axis=0]
print(type(s.rolling(3)))
# <class 'pandas.core.window.rolling.Rolling'>
さらにメソッドを実行する必要がある。
print(s.rolling(3).sum())
# 0 NaN
# 1 NaN
# 2 3.0
# 3 6.0
# 4 9.0
# 5 12.0
# 6 15.0
# 7 18.0
# 8 21.0
# 9 24.0
# dtype: float64
生成したオブジェクトから計算用のメソッドを呼ぶということで、使い方のイメージとしてはgroupby()に近い。
この例ではrolling(3)で上から順に3個ずつデータが選択されsum()で合計が算出される。
s[0],s[1],s[2]の合計 → 返り値の[2]に格納s[1],s[2],s[3]の合計 → 返り値の[3]に格納- …
s[6],s[7],s[8]の合計 → 返り値の[8]に格納s[7],s[8],s[9]の合計 → 返り値の[9]に格納
返り値の先頭2つ([0], [1])は3個分のデータが無いため欠損値NaNとなる。NaNの扱いについては以下の記事を参照。
以下、rolling()の引数について説明する。window.Rolling型のメソッドについては後述。
Windowの幅を指定: 引数window
Windowの幅は第一引数windowに指定する。省略不可。
print(s.rolling(2).sum())
# 0 NaN
# 1 1.0
# 2 3.0
# 3 5.0
# 4 7.0
# 5 9.0
# 6 11.0
# 7 13.0
# 8 15.0
# 9 17.0
# dtype: float64
print(s.rolling(4).sum())
# 0 NaN
# 1 NaN
# 2 NaN
# 3 6.0
# 4 10.0
# 5 14.0
# 6 18.0
# 7 22.0
# 8 26.0
# 9 30.0
# dtype: float64
時系列データについては特殊な指定方法がある。後述。
Windowの中心に結果の値を格納する: 引数center
これまでの例のように、デフォルトではWindowの最後の位置に算出されたデータが格納される。
引数center=Trueとすると、Windowの中心位置に算出されたデータが格納される。先頭と末尾のデータが足りない分は欠損値NaNとなる。
print(s.rolling(3, center=True).sum())
# 0 NaN
# 1 3.0
# 2 6.0
# 3 9.0
# 4 12.0
# 5 15.0
# 6 18.0
# 7 21.0
# 8 24.0
# 9 NaN
# dtype: float64
print(s.rolling(4, center=True).sum())
# 0 NaN
# 1 NaN
# 2 6.0
# 3 10.0
# 4 14.0
# 5 18.0
# 6 22.0
# 7 26.0
# 8 30.0
# 9 NaN
# dtype: float64
サンプルコードの前者(第一引数window=3)は以下の通りになっている。
s[0],s[1],s[2]の合計 → 返り値の[1]に格納- ...
s[7],s[8],s[9]の合計 → 返り値の[8]に格納
後者(第一引数window=4)は以下の通り。
s[0],s[1],s[2],s[3]の合計 → 返り値の[2]に格納- ...
s[6],s[7],s[8],s[9]の合計 → 返り値の[8]に格納
最小データ個数を指定: 引数min_periods
これまでの例のように、デフォルトではWindowに含まれるデータ個数が第一引数windowに満たない位置の結果は欠損値NaNとなる。
引数min_periodsに整数値を指定すると、その値の個数のデータが含まれていれば結果が算出される。
デフォルトでは上端のデータがNaNとなるが、min_periodsに小さい値を指定すると少ない個数のデータで結果が算出される。
print(s.rolling(3, min_periods=2).sum())
# 0 NaN
# 1 1.0
# 2 3.0
# 3 6.0
# 4 9.0
# 5 12.0
# 6 15.0
# 7 18.0
# 8 21.0
# 9 24.0
# dtype: float64
print(s.rolling(3, min_periods=1).sum())
# 0 0.0
# 1 1.0
# 2 3.0
# 3 6.0
# 4 9.0
# 5 12.0
# 6 15.0
# 7 18.0
# 8 21.0
# 9 24.0
# dtype: float64
窓関数の種類を指定: 引数win_type
窓関数の種類は引数win_typeで指定する。デフォルトは矩形窓で、Window内の全てのデータ点が一様に扱われる(全てのデータ点に対する重みが1)。
使用できる窓関数の種類は公式ドキュメントのNotesを参照。
列方向に窓関数を適用: 引数axis
これまでの例はpandas.Seriesだったがpandas.DataFrameでも同様。
デフォルトでは列に対して、引数axis=1とすると行に対して窓関数が適用される。
df = pd.DataFrame({'a': range(10), 'b': range(10, 0, -1),
'c': range(10, 20), 'd': range(20, 10, -1)})
print(df.rolling(2).sum())
# a b c d
# 0 NaN NaN NaN NaN
# 1 1.0 19.0 21.0 39.0
# 2 3.0 17.0 23.0 37.0
# 3 5.0 15.0 25.0 35.0
# 4 7.0 13.0 27.0 33.0
# 5 9.0 11.0 29.0 31.0
# 6 11.0 9.0 31.0 29.0
# 7 13.0 7.0 33.0 27.0
# 8 15.0 5.0 35.0 25.0
# 9 17.0 3.0 37.0 23.0
print(df.rolling(2, axis=1).sum())
# a b c d
# 0 NaN 10.0 20.0 30.0
# 1 NaN 10.0 20.0 30.0
# 2 NaN 10.0 20.0 30.0
# 3 NaN 10.0 20.0 30.0
# 4 NaN 10.0 20.0 30.0
# 5 NaN 10.0 20.0 30.0
# 6 NaN 10.0 20.0 30.0
# 7 NaN 10.0 20.0 30.0
# 8 NaN 10.0 20.0 30.0
# 9 NaN 10.0 20.0 30.0
window.Rolling型に適用できるメソッド
window.Rolling型に適用できるメソッドの一覧は公式ドキュメントを参照。
平均値mean(), 中央値median(), 最小値min(), 最大値max()、標準偏差std()などがある。
移動平均を算出したい場合はmean()。
print(s.rolling(3).mean())
# 0 NaN
# 1 NaN
# 2 1.0
# 3 2.0
# 4 3.0
# 5 4.0
# 6 5.0
# 7 6.0
# 8 7.0
# 9 8.0
# dtype: float64
agg()で複数の処理を同時に適用することもできる。agg()はaggregate()のエイリアス。どちらを使ってもよい。
リストで複数の処理を指定する。
各処理は、上記のwindow.Rolling型のメソッド名を文字列で指定できるほか、呼び出し可能オブジェクトを指定することも可能。呼び出し可能オブジェクトとしては、組み込み関数やNumPyの関数、defで定義した関数やラムダ関数を指定できる。
print(s.rolling(3).agg(['sum', 'mean', 'skew', 'cov',
max, min,
lambda x: max(x) - min(x)]))
# sum mean skew cov max min <lambda>
# 0 NaN NaN NaN NaN NaN NaN NaN
# 1 NaN NaN NaN NaN NaN NaN NaN
# 2 3.0 1.0 0.000000e+00 1.0 2.0 0.0 2.0
# 3 6.0 2.0 -7.993606e-15 1.0 3.0 1.0 2.0
# 4 9.0 3.0 2.398082e-14 1.0 4.0 2.0 2.0
# 5 12.0 4.0 -6.394885e-14 1.0 5.0 3.0 2.0
# 6 15.0 5.0 -7.993606e-14 1.0 6.0 4.0 2.0
# 7 18.0 6.0 1.918465e-13 1.0 7.0 5.0 2.0
# 8 21.0 7.0 2.238210e-13 1.0 8.0 6.0 2.0
# 9 24.0 8.0 -5.115908e-13 1.0 9.0 7.0 2.0
時系列データにおけるrolling()とresample()
以下のような隔日の時系列データを例とする。
import pandas as pd
df = pd.DataFrame({'value': range(1, 32, 2)},
index=pd.date_range('2018-01-01', '2018-01-31', freq='2D'))
print(df)
# value
# 2018-01-01 1
# 2018-01-03 3
# 2018-01-05 5
# 2018-01-07 7
# 2018-01-09 9
# 2018-01-11 11
# 2018-01-13 13
# 2018-01-15 15
# 2018-01-17 17
# 2018-01-19 19
# 2018-01-21 21
# 2018-01-23 23
# 2018-01-25 25
# 2018-01-27 27
# 2018-01-29 29
# 2018-01-31 31
時系列データだけの特徴
第一引数windowに数値を指定すると以下のようになる。これまで通り。
print(df.rolling(5).mean())
# value
# 2018-01-01 NaN
# 2018-01-03 NaN
# 2018-01-05 NaN
# 2018-01-07 NaN
# 2018-01-09 5.0
# 2018-01-11 7.0
# 2018-01-13 9.0
# 2018-01-15 11.0
# 2018-01-17 13.0
# 2018-01-19 15.0
# 2018-01-21 17.0
# 2018-01-23 19.0
# 2018-01-25 21.0
# 2018-01-27 23.0
# 2018-01-29 25.0
# 2018-01-31 27.0
時系列データの場合、第一引数windowにはD(日)、H(時)などのように頻度コードを文字列で指定することもできる。頻度コードについては以下の記事を参照。
'5D'(5日間)を指定すると以下のようになる。
print(df.rolling('5D').mean())
# value
# 2018-01-01 1.0
# 2018-01-03 2.0
# 2018-01-05 3.0
# 2018-01-07 5.0
# 2018-01-09 7.0
# 2018-01-11 9.0
# 2018-01-13 11.0
# 2018-01-15 13.0
# 2018-01-17 15.0
# 2018-01-19 17.0
# 2018-01-21 19.0
# 2018-01-23 21.0
# 2018-01-25 23.0
# 2018-01-27 25.0
# 2018-01-29 27.0
# 2018-01-31 29.0
windowを頻度コードで指定した場合の結果の違いは2つ。
- 引数
min_periods=1がデフォルトとなる - インデックスの日時を考慮してデータが選択される
2について2018-01-31の結果を例とすると、
- 数値
5で指定した場合2018-01-23から2018-01-31までの5個分のデータで算出
- 頻度コード
'5D'で指定した場合2018-01-27から2018-01-31までの5日分のデータで算出(データの個数としては3個)
となる。
rolling()とresample()の違い
時系列データに対して任意の範囲のデータを集約するメソッドにresample()がある。
rolling()とresample()の結果の違いは以下の通り。
print(df.rolling('5D').mean())
# value
# 2018-01-01 1.0
# 2018-01-03 2.0
# 2018-01-05 3.0
# 2018-01-07 5.0
# 2018-01-09 7.0
# 2018-01-11 9.0
# 2018-01-13 11.0
# 2018-01-15 13.0
# 2018-01-17 15.0
# 2018-01-19 17.0
# 2018-01-21 19.0
# 2018-01-23 21.0
# 2018-01-25 23.0
# 2018-01-27 25.0
# 2018-01-29 27.0
# 2018-01-31 29.0
print(df.resample('5D').mean())
# value
# 2018-01-01 3
# 2018-01-06 8
# 2018-01-11 13
# 2018-01-16 18
# 2018-01-21 23
# 2018-01-26 28
# 2018-01-31 31
元のデータ点ごとに指定範囲の統計量を算出するのがrolling()で、指定範囲ごとにデータを集約するのがresample()。
resample()はその名の通りリサンプリングに使われる。詳細は以下の記事を参照。