note.nkmk.me

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

Date: 2018-08-04 / 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'>

さらにメソッドを実行する必要がある。

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]の合計 → s[2]に格納
  • s[1], s[2], s[3]の合計 → s[3]に格納
  • s[6], s[7], s[8]の合計 → s[8]に格納
  • s[7], s[8], s[9]の合計 → s[9]に格納

はじめの2つ(s[0], s[1])は3個分のデータが無いため欠損値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の中心位置に算出されたデータが格納される。

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

最小データ個数を指定: 引数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で指定する。

使用できる窓関数の種類は公式ドキュメントを参照。

列方向に窓関数を適用: 引数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()に呼び出し可能オブジェクトをリストで指定して複数の処理を同時に適用することも可能。

print(s.rolling(3).agg([sum, min, max, 'mean']))
#     sum  min  max  mean
# 0   NaN  NaN  NaN   NaN
# 1   NaN  NaN  NaN   NaN
# 2   3.0  0.0  2.0   1.0
# 3   6.0  1.0  3.0   2.0
# 4   9.0  2.0  4.0   3.0
# 5  12.0  3.0  5.0   4.0
# 6  15.0  4.0  6.0   5.0
# 7  18.0  5.0  7.0   6.0
# 8  21.0  6.0  8.0   7.0
# 9  24.0  7.0  9.0   8.0

組み込み関数に含まれていないmean()は文字列'mean'で指定するか、np.mean()(NumPyをインポートする必要あり)、またはpd.np.mean()で指定する。詳細は以下の記事を参照。

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

スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事