窗口操作#

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. 滚动窗口 (Rolling window): 对值进行通用的固定或可变滑动窗口操作。

  2. 加权窗口 (Weighted window): 由 scipy.signal 库提供的加权、非矩形窗口。

  3. 扩展窗口 (Expanding window): 对值进行累积窗口操作。

  4. 指数加权窗口 (Exponentially Weighted window): 对值进行累积和指数加权窗口操作。

概念

方法

返回对象

支持基于时间的窗口

支持链式 groupby

支持 table 方法

支持在线操作

滚动窗口

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' 选项,该选项对整个 DataFrame 执行窗口操作,而不是每次只对单个列或行执行。这可以为具有许多列或行(使用相应的 axis 参数)的 DataFrame 提供有益的性能优势,或者在窗口操作期间利用其他列的能力。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 方法,该方法返回一个新对象,支持传入新的 DataFrameSeries 对象,以继续使用新值进行窗口计算(即在线计算)。

此新窗口对象上的方法必须首先调用聚合方法以“初始化”在线计算的初始状态。然后,可以通过 update 参数传入新的 DataFrameSeries 对象来继续窗口计算。

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

所有窗口操作都支持 min_periods 参数,该参数规定窗口必须包含的非 np.nan 值的最小数量;否则,结果值为 np.nanmin_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

这也可以应用于日期时间类索引。

自 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 子类需要定义一个 get_window_bounds 方法,该方法返回一个包含两个数组的元组,第一个数组是窗口的起始索引,第二个数组是窗口的结束索引。此外,num_valuesmin_periodscenterclosedstep 将自动传递给 get_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 子类的其他示例

这些示例中值得注意的一个子类是 VariableOffsetWindowIndexer,它允许对非固定偏移量(如 BusinessDay)执行滚动操作。

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

滚动应用 (Rolling apply)#

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 引擎#

此外,如果 Numba 作为可选依赖项安装,apply() 可以利用它。通过指定 engine='numba'engine_kwargs 参数(raw 也必须设置为 True),可以使用 Numba 执行应用聚合。有关参数的通用用法和性能考量,请参见使用 Numba 提升性能

Numba 可能在两个例程中应用

  1. 如果 func 是标准 Python 函数,引擎将对传入函数进行 JIT 编译func 也可以是已 JIT 编译的函数,在这种情况下引擎不会再次对其进行 JIT 编译。

  2. 引擎将对循环(应用函数应用于每个窗口)进行 JIT 编译。

The engine_kwargs argument is a dictionary of keyword arguments that will be passed into the numba.jit decorator. These keyword arguments will be applied to both the passed function (if a standard Python function) and the apply for loop over each window.

自 1.3.0 版本新增。

meanmedianmaxminsum 也支持 engineengine_kwargs 参数。

二元窗口函数#

cov()corr() 可以计算关于两个 Series 或任意组合的 DataFrame/SeriesDataFrame/DataFrame 的移动窗口统计量。以下是每种情况的行为

  • 两个 Series: 计算配对的统计量。

  • DataFrame/Series: 计算 DataFrame 的每一列与传入 Series 的统计量,返回一个 DataFrame。

  • DataFrame/DataFrame: 默认计算匹配列名的统计量,返回一个 DataFrame。如果传入关键字参数 pairwise=True,则计算每对列的统计量,返回一个带有 MultiIndexDataFrame,其值是相关日期(请参见下一节)。

例如

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 参数。

注意

缺失值将被忽略,每个条目都使用成对完整观测值进行计算。

假设缺失数据是随机缺失的,这将得到一个无偏的协方差矩阵估计。然而,对于许多应用而言,此估计可能不可接受,因为估计的协方差矩阵不保证是半正定的。这可能导致估计的相关系数的绝对值大于一,和/或一个不可逆的协方差矩阵。有关更多详细信息,请参见协方差矩阵估计

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]

加权窗口#

.rolling 中的 win_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 中的实现使得以下两种调用是等效的

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 函数支持两种指数权重变体。默认情况下,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.\]

上述两种变体之间的差异产生是因为我们处理的是具有有限历史的序列。考虑一个具有无限历史的序列,当 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 的表达式相同,因此显示了这两种变体对于无限序列的等效性。当 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{for span}\ s \geq 1\\ \frac{1}{1 + c}, & \text{for center of mass}\ c \geq 0\\ 1 - \exp^{\frac{\log 0.5}{h}}, & \text{for half-life}\ h > 0 \end{cases}\end{split}\]

必须且只能向 EW 函数指定 **span**、**质心**、**半衰期** 和 **alpha** 中的一个

  • **Span** 对应于通常所说的“N 天 EW 移动平均”。

  • **质心 (Center of mass)** 具有更物理的解释,可以从 span 的角度来考虑:\(c = (s - 1) / 2\)

  • **半衰期 (Half-life)** 是指数权重衰减到一半所需的时间周期。

  • **Alpha** 直接指定平滑因子。

您还可以将 halflife 指定为可转换为时间差的单位,以指定观测值衰减到其值一半所需的时间,同时也要指定 times 序列。

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 参数,它决定了中间空值如何影响权重的计算。当 ignore_na=False(默认值)时,权重根据绝对位置计算,因此中间空值会影响结果。当 ignore_na=True 时,权重通过忽略中间空值来计算。例如,假设 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=Trueewmvar(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\)。)有关更多详细信息,请参见维基百科上的加权样本方差