pandasで窓関数を適用するrollingを使って移動平均などを算出

Posted: | Tags: Python, pandas, 時系列データ

pandas.DataFrame, pandas.Seriesに窓関数(Window Function)を適用するにはrolling()を使う。

窓関数はフィルタをデザインする際などに使われるが、単純に移動平均線を算出(前後のデータの平均を算出)したりするのにも使える。

ここでは以下の内容について説明する。

  • rolling()の基本的な使い方
    • Windowの幅を指定: 引数window
    • Windowの中心に結果の値を格納する: 引数center
    • 最小データ個数を指定: 引数min_periods
    • 窓関数の種類を指定: 引数win_type
    • 列方向に窓関数を適用: 引数axis
  • 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つ。

  1. 引数min_periods=1がデフォルトとなる
  2. インデックスの日時を考慮してデータが選択される

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()はその名の通りリサンプリングに使われる。詳細は以下の記事を参照。

関連カテゴリー

関連記事