ウィンドウ処理#

pandasには、ウィンドウ処理を実行するためのコンパクトなAPIセットが含まれています。ウィンドウ処理とは、値のスライディングパーティションに対して集計を行う処理です。このAPIはgroupby APIと同様に機能し、SeriesDataFrameは、必要なパラメータとともにウィンドウ処理メソッドを呼び出し、その後、集計関数を呼び出します。

In [1]: s = pd.Series(range(5))

In [2]: s.rolling(window=2).sum()
Out[2]: 
0    NaN
1    1.0
2    3.0
3    5.0
4    7.0
dtype: float64

ウィンドウは、現在の観測値からウィンドウの長さだけ遡って構成されます。上記の結果は、データの以下のウィンドウ分割の合計を求めることで導き出すことができます。

In [3]: for window in s.rolling(window=2):
   ...:     print(window)
   ...: 
0    0
dtype: int64
0    0
1    1
dtype: int64
1    1
2    2
dtype: int64
2    2
3    3
dtype: int64
3    3
4    4
dtype: int64

概要#

pandasは4種類のウィンドウ処理をサポートしています。

  1. ローリングウィンドウ:値に対する一般的な固定または可変のスライディングウィンドウ。

  2. ウェイト付きウィンドウ:scipy.signalライブラリによって提供される、ウェイト付きの非矩形ウィンドウ。

  3. 展開ウィンドウ:値に対する累積ウィンドウ。

  4. 指数加重ウィンドウ:値に対する累積および指数加重ウィンドウ。

概念

メソッド

返されるオブジェクト

時間ベースのウィンドウをサポート

チェーンされたgroupbyをサポート

テーブルメソッドをサポート

オンライン操作をサポート

ローリングウィンドウ

rolling

pandas.typing.api.Rolling

はい

はい

はい(バージョン1.3以降)

いいえ

ウェイト付きウィンドウ

rolling

pandas.typing.api.Window

いいえ

いいえ

いいえ

いいえ

展開ウィンドウ

expanding

pandas.typing.api.Expanding

いいえ

はい

はい(バージョン1.3以降)

いいえ

指数加重ウィンドウ

ewm

pandas.typing.api.ExponentialMovingWindow

いいえ

はい(バージョン1.2以降)

いいえ

はい(バージョン1.3以降)

前述のように、一部の操作では、時間オフセットに基づいたウィンドウを指定できます。

In [4]: s = pd.Series(range(5), index=pd.date_range('2020-01-01', periods=5, freq='1D'))

In [5]: s.rolling(window='2D').sum()
Out[5]: 
2020-01-01    0.0
2020-01-02    1.0
2020-01-03    3.0
2020-01-04    5.0
2020-01-05    7.0
Freq: D, dtype: float64

さらに、一部のメソッドでは、ウィンドウ処理操作とgroupby操作をチェーンできます。これにより、まず指定されたキーでデータをグループ化してから、グループごとにウィンドウ処理操作を実行します。

In [6]: df = pd.DataFrame({'A': ['a', 'b', 'a', 'b', 'a'], 'B': range(5)})

In [7]: df.groupby('A').expanding().sum()
Out[7]: 
       B
A       
a 0  0.0
  2  2.0
  4  6.0
b 1  1.0
  3  4.0

注意

ウィンドウ処理は現在、数値データ(整数と浮動小数点数)のみをサポートしており、常にfloat64値を返します。

警告

一部のウィンドウ集計(meansumvarstdメソッド)は、基盤となるウィンドウ処理アルゴリズムによる合計の累積のために、数値の精度が低下する可能性があります。値が\(1/np.finfo(np.double).eps\)の大きさで異なる場合、これは切り捨てにつながります。これらの値を含まないウィンドウには、大きな値が影響を与える可能性があることに注意する必要があります。Kahanの総和アルゴリズムを使用して、ローリング合計を計算し、精度を可能な限り維持しています。

バージョン1.3.0の新機能。

一部のウィンドウ処理操作では、コンストラクタでmethod='table'オプションもサポートしています。これは、一度に1つの列または行ではなく、DataFrame全体でウィンドウ処理操作を実行します。これは、多くの列または行を持つDataFrame(対応するaxis引数を使用)の場合、またはウィンドウ処理操作中に他の列を利用する場合に、パフォーマンス上の利点をもたらす可能性があります。method='table'オプションは、対応するメソッド呼び出しでengine='numba'が指定されている場合にのみ使用できます。

たとえば、加重平均の計算は、個別の重みの列を指定することでapply()を使用して計算できます。

In [8]: def weighted_mean(x):
   ...:     arr = np.ones((1, x.shape[1]))
   ...:     arr[:, :2] = (x[:, :2] * x[:, 2]).sum(axis=0) / x[:, 2].sum()
   ...:     return arr
   ...: 

In [9]: df = pd.DataFrame([[1, 2, 0.6], [2, 3, 0.4], [3, 4, 0.2], [4, 5, 0.7]])

In [10]: df.rolling(2, method="table", min_periods=0).apply(weighted_mean, raw=True, engine="numba")  # noqa: E501
Out[10]: 
          0         1    2
0  1.000000  2.000000  1.0
1  1.800000  2.000000  1.0
2  3.333333  2.333333  1.0
3  1.555556  7.000000  1.0

バージョン1.3の新機能。

一部のウィンドウ処理操作では、ウィンドウオブジェクトの構築後にonlineメソッドもサポートしています。これは、新しいDataFrameまたはSeriesオブジェクトを渡して、新しい値(つまり、オンライン計算)でウィンドウ処理計算を続行できる新しいオブジェクトを返します。

この新しいウィンドウ処理オブジェクトのメソッドは、最初に集計メソッドを呼び出してオンライン計算の初期状態を「準備」する必要があります。その後、新しいDataFrameまたはSeriesオブジェクトをupdate引数に渡して、ウィンドウ処理計算を続行できます。

In [11]: df = pd.DataFrame([[1, 2, 0.6], [2, 3, 0.4], [3, 4, 0.2], [4, 5, 0.7]])

In [12]: df.ewm(0.5).mean()
Out[12]: 
          0         1         2
0  1.000000  2.000000  0.600000
1  1.750000  2.750000  0.450000
2  2.615385  3.615385  0.276923
3  3.550000  4.550000  0.562500
In [13]: online_ewm = df.head(2).ewm(0.5).online()

In [14]: online_ewm.mean()
Out[14]: 
      0     1     2
0  1.00  2.00  0.60
1  1.75  2.75  0.45

In [15]: online_ewm.mean(update=df.tail(1))
Out[15]: 
          0         1         2
3  3.307692  4.307692  0.623077

すべてのウィンドウ処理操作は、ウィンドウが持つ必要のある非np.nan値の最小量を決定するmin_periods引数をサポートしています。そうでない場合、結果はnp.nanになります。min_periodsは、時間ベースのウィンドウでは1に、固定ウィンドウではwindowにデフォルト設定されています。

In [16]: s = pd.Series([np.nan, 1, 2, np.nan, np.nan, 3])

In [17]: s.rolling(window=3, min_periods=1).sum()
Out[17]: 
0    NaN
1    1.0
2    3.0
3    3.0
4    2.0
5    3.0
dtype: float64

In [18]: s.rolling(window=3, min_periods=2).sum()
Out[18]: 
0    NaN
1    NaN
2    3.0
3    3.0
4    NaN
5    NaN
dtype: float64

# Equivalent to min_periods=3
In [19]: s.rolling(window=3, min_periods=None).sum()
Out[19]: 
0   NaN
1   NaN
2   NaN
3   NaN
4   NaN
5   NaN
dtype: float64

さらに、すべてのウィンドウ処理操作は、ウィンドウに適用された複数の集計の結果を返すaggregateメソッドをサポートしています。

In [20]: df = pd.DataFrame({"A": range(5), "B": range(10, 15)})

In [21]: df.expanding().agg(["sum", "mean", "std"])
Out[21]: 
      A                    B                
    sum mean       std   sum  mean       std
0   0.0  0.0       NaN  10.0  10.0       NaN
1   1.0  0.5  0.707107  21.0  10.5  0.707107
2   3.0  1.0  1.000000  33.0  11.0  1.000000
3   6.0  1.5  1.290994  46.0  11.5  1.290994
4  10.0  2.0  1.581139  60.0  12.0  1.581139

ローリングウィンドウ#

一般的なローリングウィンドウでは、ウィンドウを一定数の観測値またはオフセットに基づく可変数の観測値として指定できます。時間ベースのオフセットが提供されている場合、対応する時間ベースのインデックスは単調でなければなりません。

In [22]: times = ['2020-01-01', '2020-01-03', '2020-01-04', '2020-01-05', '2020-01-29']

In [23]: s = pd.Series(range(5), index=pd.DatetimeIndex(times))

In [24]: s
Out[24]: 
2020-01-01    0
2020-01-03    1
2020-01-04    2
2020-01-05    3
2020-01-29    4
dtype: int64

# Window with 2 observations
In [25]: s.rolling(window=2).sum()
Out[25]: 
2020-01-01    NaN
2020-01-03    1.0
2020-01-04    3.0
2020-01-05    5.0
2020-01-29    7.0
dtype: float64

# Window with 2 days worth of observations
In [26]: s.rolling(window='2D').sum()
Out[26]: 
2020-01-01    0.0
2020-01-03    1.0
2020-01-04    3.0
2020-01-05    5.0
2020-01-29    4.0
dtype: float64

サポートされているすべての集計関数については、ローリングウィンドウ関数を参照してください。

ウィンドウの中央揃え#

デフォルトでは、ラベルはウィンドウの右端に設定されますが、centerキーワードを使用すると、ラベルを中央に設定できます。

In [27]: s = pd.Series(range(10))

In [28]: s.rolling(window=5).mean()
Out[28]: 
0    NaN
1    NaN
2    NaN
3    NaN
4    2.0
5    3.0
6    4.0
7    5.0
8    6.0
9    7.0
dtype: float64

In [29]: s.rolling(window=5, center=True).mean()
Out[29]: 
0    NaN
1    NaN
2    2.0
3    3.0
4    4.0
5    5.0
6    6.0
7    7.0
8    NaN
9    NaN
dtype: float64

これは、datetimeのようなインデックスにも適用できます。

バージョン1.3.0の新機能。

In [30]: df = pd.DataFrame(
   ....:     {"A": [0, 1, 2, 3, 4]}, index=pd.date_range("2020", periods=5, freq="1D")
   ....: )
   ....: 

In [31]: df
Out[31]: 
            A
2020-01-01  0
2020-01-02  1
2020-01-03  2
2020-01-04  3
2020-01-05  4

In [32]: df.rolling("2D", center=False).mean()
Out[32]: 
              A
2020-01-01  0.0
2020-01-02  0.5
2020-01-03  1.5
2020-01-04  2.5
2020-01-05  3.5

In [33]: df.rolling("2D", center=True).mean()
Out[33]: 
              A
2020-01-01  0.5
2020-01-02  1.5
2020-01-03  2.5
2020-01-04  3.5
2020-01-05  4.0

ローリングウィンドウの端点#

ローリングウィンドウ計算における区間の端点の包含は、closedパラメータで指定できます。

動作

'right'

右端点を閉じる

'left'

左端点を閉じる

'both'

両端点を閉じる

'neither'

端点を開く

たとえば、右端点を開いておくことは、現在の情報から過去の情報への汚染がないことが必要な多くの問題で役立ちます。これにより、ローリングウィンドウは「その時点まで」の統計を計算できますが、その時点自体は含まれません。

In [34]: df = pd.DataFrame(
   ....:     {"x": 1},
   ....:     index=[
   ....:         pd.Timestamp("20130101 09:00:01"),
   ....:         pd.Timestamp("20130101 09:00:02"),
   ....:         pd.Timestamp("20130101 09:00:03"),
   ....:         pd.Timestamp("20130101 09:00:04"),
   ....:         pd.Timestamp("20130101 09:00:06"),
   ....:     ],
   ....: )
   ....: 

In [35]: df["right"] = df.rolling("2s", closed="right").x.sum()  # default

In [36]: df["both"] = df.rolling("2s", closed="both").x.sum()

In [37]: df["left"] = df.rolling("2s", closed="left").x.sum()

In [38]: df["neither"] = df.rolling("2s", closed="neither").x.sum()

In [39]: df
Out[39]: 
                     x  right  both  left  neither
2013-01-01 09:00:01  1    1.0   1.0   NaN      NaN
2013-01-01 09:00:02  1    2.0   2.0   1.0      1.0
2013-01-01 09:00:03  1    2.0   3.0   2.0      1.0
2013-01-01 09:00:04  1    2.0   3.0   2.0      1.0
2013-01-01 09:00:06  1    1.0   2.0   1.0      NaN

カスタムウィンドウローリング#

整数またはオフセットをwindow引数として受け入れることに加え、rollingは、ウィンドウの境界を計算するためのカスタムメソッドをユーザーが定義できるBaseIndexerサブクラスも受け入れます。BaseIndexerサブクラスは、ウィンドウの開始インデックスを格納した配列とウィンドウの終了インデックスを格納した配列の2つのタプルを返すget_window_boundsメソッドを定義する必要があります。さらに、num_valuesmin_periodscenterclosedstepget_window_boundsに自動的に渡され、定義されたメソッドは常にこれらの引数を受け入れる必要があります。

例えば、以下のDataFrameがあるとします。

In [40]: use_expanding = [True, False, True, False, True]

In [41]: use_expanding
Out[41]: [True, False, True, False, True]

In [42]: df = pd.DataFrame({"values": range(5)})

In [43]: df
Out[43]: 
   values
0       0
1       1
2       2
3       3
4       4

use_expandingTrueの場合に拡大ウィンドウを使用し、それ以外の場合はサイズ1のウィンドウを使用したい場合、以下のBaseIndexerサブクラスを作成できます。

In [44]: from pandas.api.indexers import BaseIndexer

In [45]: class CustomIndexer(BaseIndexer):
   ....:      def get_window_bounds(self, num_values, min_periods, center, closed, step):
   ....:          start = np.empty(num_values, dtype=np.int64)
   ....:          end = np.empty(num_values, dtype=np.int64)
   ....:          for i in range(num_values):
   ....:              if self.use_expanding[i]:
   ....:                  start[i] = 0
   ....:                  end[i] = i + 1
   ....:              else:
   ....:                  start[i] = i
   ....:                  end[i] = i + self.window_size
   ....:          return start, end
   ....: 

In [46]: indexer = CustomIndexer(window_size=1, use_expanding=use_expanding)

In [47]: df.rolling(indexer).sum()
Out[47]: 
   values
0     0.0
1     1.0
2     3.0
3     3.0
4    10.0

BaseIndexerサブクラスの他の例はこちらで確認できます。

これらの例の中で注目すべきサブクラスの1つは、BusinessDayのような固定されていないオフセットに対するローリング操作を可能にするVariableOffsetWindowIndexerです。

In [48]: from pandas.api.indexers import VariableOffsetWindowIndexer

In [49]: df = pd.DataFrame(range(10), index=pd.date_range("2020", periods=10))

In [50]: offset = pd.offsets.BDay(1)

In [51]: indexer = VariableOffsetWindowIndexer(index=df.index, offset=offset)

In [52]: df
Out[52]: 
            0
2020-01-01  0
2020-01-02  1
2020-01-03  2
2020-01-04  3
2020-01-05  4
2020-01-06  5
2020-01-07  6
2020-01-08  7
2020-01-09  8
2020-01-10  9

In [53]: df.rolling(indexer).sum()
Out[53]: 
               0
2020-01-01   0.0
2020-01-02   1.0
2020-01-03   2.0
2020-01-04   3.0
2020-01-05   7.0
2020-01-06  12.0
2020-01-07   6.0
2020-01-08   7.0
2020-01-09   8.0
2020-01-10   9.0

一部の問題では、将来の情報が分析に使用可能です。例えば、各データポイントが実験から読み取られた完全な時系列であり、基礎となる条件を抽出することがタスクである場合に発生します。このような場合、前方参照ローリングウィンドウ計算を実行すると便利です。FixedForwardWindowIndexerクラスはこの目的で使用できます。このBaseIndexerサブクラスは、クローズド固定幅の前方参照ローリングウィンドウを実装しており、次のように使用できます。

In [54]: from pandas.api.indexers import FixedForwardWindowIndexer

In [55]: indexer = FixedForwardWindowIndexer(window_size=2)

In [56]: df.rolling(indexer, min_periods=1).sum()
Out[56]: 
               0
2020-01-01   1.0
2020-01-02   3.0
2020-01-03   5.0
2020-01-04   7.0
2020-01-05   9.0
2020-01-06  11.0
2020-01-07  13.0
2020-01-08  15.0
2020-01-09  17.0
2020-01-10   9.0

スライスを使用してローリング集計を適用し、その結果を反転することによっても、以下の例のように実現できます。

In [57]: df = pd.DataFrame(
   ....:     data=[
   ....:         [pd.Timestamp("2018-01-01 00:00:00"), 100],
   ....:         [pd.Timestamp("2018-01-01 00:00:01"), 101],
   ....:         [pd.Timestamp("2018-01-01 00:00:03"), 103],
   ....:         [pd.Timestamp("2018-01-01 00:00:04"), 111],
   ....:     ],
   ....:     columns=["time", "value"],
   ....: ).set_index("time")
   ....: 

In [58]: df
Out[58]: 
                     value
time                      
2018-01-01 00:00:00    100
2018-01-01 00:00:01    101
2018-01-01 00:00:03    103
2018-01-01 00:00:04    111

In [59]: reversed_df = df[::-1].rolling("2s").sum()[::-1]

In [60]: reversed_df
Out[60]: 
                     value
time                      
2018-01-01 00:00:00  201.0
2018-01-01 00:00:01  101.0
2018-01-01 00:00:03  214.0
2018-01-01 00:00:04  111.0

ローリング適用#

apply()関数は、追加のfunc引数を取り、汎用的なローリング計算を実行します。func引数は、ndarray入力から単一の値を生成する単一の関数である必要があります。rawは、ウィンドウがSeriesオブジェクト(raw=False)としてキャストされるか、ndarrayオブジェクト(raw=True)としてキャストされるかを指定します。

In [61]: def mad(x):
   ....:     return np.fabs(x - x.mean()).mean()
   ....: 

In [62]: s = pd.Series(range(10))

In [63]: s.rolling(window=4).apply(mad, raw=True)
Out[63]: 
0    NaN
1    NaN
2    NaN
3    1.0
4    1.0
5    1.0
6    1.0
7    1.0
8    1.0
9    1.0
dtype: float64

Numbaエンジン#

さらに、apply()は、オプションの依存関係としてインストールされている場合、Numbaを活用できます。engine='numba'およびengine_kwargs引数を指定することで、Numbaを使用して適用集計を実行できます(rawTrueに設定する必要があります)。引数の一般的な使用方法とパフォーマンスに関する考慮事項については、Numbaによるパフォーマンスの向上を参照してください。

Numbaは、可能性のある2つのルーチンに適用されます。

  1. funcが標準的なPython関数の場合、エンジンは渡された関数をJITコンパイルします。funcはJITコンパイル済みの関数にすることもでき、その場合、エンジンは関数を再度JITコンパイルしません。

  2. エンジンは、適用関数が各ウィンドウに適用されるforループをJITコンパイルします。

engine_kwargs引数は、numba.jitデコレータに渡されるキーワード引数の辞書です。これらのキーワード引数は、渡された関数(標準的なPython関数の場合)と、各ウィンドウに対する適用forループの両方に適用されます。

バージョン1.3.0の新機能。

meanmedianmaxminsumengineおよびengine_kwargs引数をサポートします。

バイナリウィンドウ関数#

cov()corr()は、2つのSeries、またはDataFrame/SeriesDataFrame/DataFrameの任意の組み合わせに関する移動ウィンドウ統計を計算できます。各ケースにおける動作は以下のとおりです。

  • 2つのSeries:ペアリングの統計を計算します。

  • DataFrame/Series:渡されたSeriesを使用してDataFrameの各列の統計を計算し、DataFrameを返します。

  • DataFrame/DataFrame:デフォルトでは、一致する列名の統計を計算し、DataFrameを返します。pairwise=Trueキーワード引数が渡された場合、各列のペアの統計を計算し、MultiIndexを値とするDataFrameを返します(次のセクションを参照)。

例えば

In [64]: df = pd.DataFrame(
   ....:     np.random.randn(10, 4),
   ....:     index=pd.date_range("2020-01-01", periods=10),
   ....:     columns=["A", "B", "C", "D"],
   ....: )
   ....: 

In [65]: df = df.cumsum()

In [66]: df2 = df[:4]

In [67]: df2.rolling(window=2).corr(df2["B"])
Out[67]: 
              A    B    C    D
2020-01-01  NaN  NaN  NaN  NaN
2020-01-02 -1.0  1.0 -1.0  1.0
2020-01-03  1.0  1.0  1.0 -1.0
2020-01-04 -1.0  1.0  1.0 -1.0

ローリングペアワイズ共分散と相関の計算#

金融データ分析やその他の分野では、時系列の集合に対する共分散行列と相関行列を計算することが一般的です。多くの場合、移動ウィンドウ共分散行列と相関行列にも関心があります。pairwiseキーワード引数を渡すことでこれを実行できます。これは、DataFrame入力がMultiIndexed DataFrameを生成します。そのindexは問題の日付です。単一のDataFrame引数の場合は、pairwise引数を省略することもできます。

注意

欠損値は無視され、各エントリはペアワイズ完全観測値を使用して計算されます。

欠損データがランダムに欠損していると仮定すると、これは共分散行列の不偏推定値になります。しかし、多くのアプリケーションでは、推定された共分散行列が正定値半定値であることが保証されないため、この推定値は許容できない可能性があります。これにより、推定された相関の絶対値が1より大きくなったり、共分散行列が非可逆になったりする可能性があります。詳細については、共分散行列の推定を参照してください。

In [68]: covs = (
   ....:     df[["B", "C", "D"]]
   ....:     .rolling(window=4)
   ....:     .cov(df[["A", "B", "C"]], pairwise=True)
   ....: )
   ....: 

In [69]: covs
Out[69]: 
                     B         C         D
2020-01-01 A       NaN       NaN       NaN
           B       NaN       NaN       NaN
           C       NaN       NaN       NaN
2020-01-02 A       NaN       NaN       NaN
           B       NaN       NaN       NaN
...                ...       ...       ...
2020-01-09 B  0.342006  0.230190  0.052849
           C  0.230190  1.575251  0.082901
2020-01-10 A -0.333945  0.006871 -0.655514
           B  0.649711  0.430860  0.469271
           C  0.430860  0.829721  0.055300

[30 rows x 3 columns]

加重ウィンドウ#

.rollingwin_type引数は、フィルタリングとスペクトル推定で一般的に使用される加重ウィンドウを生成します。win_typeは、scipy.signalウィンドウ関数に対応する文字列である必要があります。これらのウィンドウを使用するにはScipyをインストールする必要があり、Scipyウィンドウメソッドが使用する補足的な引数は集計関数で指定する必要があります。

In [70]: s = pd.Series(range(10))

In [71]: s.rolling(window=5).mean()
Out[71]: 
0    NaN
1    NaN
2    NaN
3    NaN
4    2.0
5    3.0
6    4.0
7    5.0
8    6.0
9    7.0
dtype: float64

In [72]: s.rolling(window=5, win_type="triang").mean()
Out[72]: 
0    NaN
1    NaN
2    NaN
3    NaN
4    2.0
5    3.0
6    4.0
7    5.0
8    6.0
9    7.0
dtype: float64

# Supplementary Scipy arguments passed in the aggregation function
In [73]: s.rolling(window=5, win_type="gaussian").mean(std=0.1)
Out[73]: 
0    NaN
1    NaN
2    NaN
3    NaN
4    2.0
5    3.0
6    4.0
7    5.0
8    6.0
9    7.0
dtype: float64

サポートされているすべての集計関数については、加重ウィンドウ関数を参照してください。

拡大ウィンドウ#

拡大ウィンドウは、その時点までに利用可能なすべてのデータを使用して集計統計の値を生成します。これらの計算はローリング統計の特別なケースであるため、pandasでは次の2つの呼び出しが等価になるように実装されています。

In [74]: df = pd.DataFrame(range(5))

In [75]: df.rolling(window=len(df), min_periods=1).mean()
Out[75]: 
     0
0  0.0
1  0.5
2  1.0
3  1.5
4  2.0

In [76]: df.expanding(min_periods=1).mean()
Out[76]: 
     0
0  0.0
1  0.5
2  1.0
3  1.5
4  2.0

サポートされているすべての集計関数については、拡大ウィンドウ関数を参照してください。

指数加重ウィンドウ#

指数加重ウィンドウは拡大ウィンドウに似ていますが、各前のポイントは現在のポイントに対して指数関数的に重みが減少します。

一般に、加重移動平均は次のように計算されます。

\[y_t = \frac{\sum_{i=0}^t w_i x_{t-i}}{\sum_{i=0}^t w_i},\]

ここで、\(x_t\)は入力、\(y_t\)は結果、\(w_i\)は重みです。

サポートされているすべての集計関数については、指数加重ウィンドウ関数を参照してください。

EW関数は、指数ウェイトの2つのバリアントをサポートしています。デフォルトのadjust=Trueは、ウェイト\(w_i = (1 - \alpha)^i\)を使用し、以下を与えます。

\[y_t = \frac{x_t + (1 - \alpha)x_{t-1} + (1 - \alpha)^2 x_{t-2} + ... + (1 - \alpha)^t x_{0}}{1 + (1 - \alpha) + (1 - \alpha)^2 + ... + (1 - \alpha)^t}\]

adjust=Falseを指定すると、移動平均は以下のように計算されます。

\[\begin{split}y_0 &= x_0 \\ y_t &= (1 - \alpha) y_{t-1} + \alpha x_t,\end{split}\]

これは、以下のウェイトを使用することと同等です。

\[\begin{split}w_i = \begin{cases} \alpha (1 - \alpha)^i & \text{if } i < t \\ (1 - \alpha)^i & \text{if } i = t. \end{cases}\end{split}\]

注意

これらの式は、\(\alpha' = 1 - \alpha\)の観点から記述されることもあります。例:

\[y_t = \alpha' y_{t-1} + (1 - \alpha') x_t.\]

上記2つのバリアントの違いは、有限の履歴を持つ系列を扱っているためです。adjust=Trueを持つ無限の履歴を持つ系列を考えてみましょう。

\[y_t = \frac{x_t + (1 - \alpha)x_{t-1} + (1 - \alpha)^2 x_{t-2} + ...} {1 + (1 - \alpha) + (1 - \alpha)^2 + ...}\]

分母は初項が1で、公比が\(1 - \alpha\)の幾何級数であることに注意すると、

\[\begin{split}y_t &= \frac{x_t + (1 - \alpha)x_{t-1} + (1 - \alpha)^2 x_{t-2} + ...} {\frac{1}{1 - (1 - \alpha)}}\\ &= [x_t + (1 - \alpha)x_{t-1} + (1 - \alpha)^2 x_{t-2} + ...] \alpha \\ &= \alpha x_t + [(1-\alpha)x_{t-1} + (1 - \alpha)^2 x_{t-2} + ...]\alpha \\ &= \alpha x_t + (1 - \alpha)[x_{t-1} + (1 - \alpha) x_{t-2} + ...]\alpha\\ &= \alpha x_t + (1 - \alpha) y_{t-1}\end{split}\]

これは上記adjust=Falseと同じ式であり、したがって無限系列に対する2つのバリアントの等価性を示しています。adjust=Falseの場合、\(y_0 = x_0\)および\(y_t = \alpha x_t + (1 - \alpha) y_{t-1}\)となります。したがって、\(x_0\)は通常の値ではなく、その時点までの無限系列の指数加重モーメントであるという仮定があります。

\(0 < \alpha \leq 1\)でなければならず、\(\alpha\)を直接渡すことは可能ですが、EWモーメントのspan中心(com)、または半減期のいずれかを考える方が簡単です。

\[\begin{split}\alpha = \begin{cases} \frac{2}{s + 1}, & \text{span}\ s \geq 1の場合\\ \frac{1}{1 + c}, & \text{中心}\ c \geq 0の場合\\ 1 - \exp^{\frac{\log 0.5}{h}}, & \text{半減期}\ h > 0の場合 \end{cases}\end{split}\]

EW関数には、span中心半減期alphaのいずれか1つを正確に指定する必要があります。

  • spanは、一般的に「N日EW移動平均」と呼ばれるものに対応します。

  • 中心はより物理的な解釈を持ち、spanに関して\(c = (s - 1) / 2\)として考えることができます。

  • 半減期とは、指数ウェイトが半分に減少する期間です。

  • Alphaは、平滑化係数を直接指定します。

timesのシーケンスも指定する場合、timedeltaに変換可能な単位でhalflifeを指定して、観測値が元の値の半分に減衰するまでの時間を指定することもできます。

In [77]: df = pd.DataFrame({"B": [0, 1, 2, np.nan, 4]})

In [78]: df
Out[78]: 
     B
0  0.0
1  1.0
2  2.0
3  NaN
4  4.0

In [79]: times = ["2020-01-01", "2020-01-03", "2020-01-10", "2020-01-15", "2020-01-17"]

In [80]: df.ewm(halflife="4 days", times=pd.DatetimeIndex(times)).mean()
Out[80]: 
          B
0  0.000000
1  0.585786
2  1.523889
3  1.523889
4  3.233686

時間の入力ベクトルを使用して指数加重平均を計算するために、次の式が使用されます。

\[y_t = \frac{\sum_{i=0}^t 0.5^\frac{t_{t} - t_{i}}{\lambda} x_{t-i}}{\sum_{i=0}^t 0.5^\frac{t_{t} - t_{i}}{\lambda}},\]

ExponentialMovingWindowには、ignore_na引数もあります。これは、中間的なNull値がウェイトの計算にどのように影響するかを決定します。ignore_na=False(デフォルト)の場合、ウェイトは絶対的な位置に基づいて計算されるため、中間的なNull値は結果に影響します。ignore_na=Trueの場合、中間的なNull値を無視してウェイトが計算されます。例えば、adjust=Trueを仮定すると、ignore_na=Falseの場合、3, NaN, 5の加重平均は以下のように計算されます。

\[\frac{(1-\alpha)^2 \cdot 3 + 1 \cdot 5}{(1-\alpha)^2 + 1}.\]

ignore_na=Trueの場合、加重平均は以下のように計算されます。

\[\frac{(1-\alpha) \cdot 3 + 1 \cdot 5}{(1-\alpha) + 1}.\]

var()std()、およびcov()関数は、結果にバイアスのある統計量を含めるべきかバイアスのない統計量を含めるべきかを指定するbias引数を持っています。例えば、bias=Trueの場合、ewmvar(x)ewmvar(x) = ewma(x**2) - ewma(x)**2として計算されます。一方、bias=False(デフォルト)の場合、バイアスのある分散統計量は、以下の脱バイアス係数でスケーリングされます。

\[\frac{\left(\sum_{i=0}^t w_i\right)^2}{\left(\sum_{i=0}^t w_i\right)^2 - \sum_{i=0}^t w_i^2}.\]

(\(w_i = 1\)の場合、これは通常の\(N / (N - 1)\)係数に還元され、\(N = t + 1\)となります。) 詳細については、WikipediaのWeighted Sample Varianceを参照してください。