窗口操作#

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. 指数加权窗口:对值进行累积和指数加权窗口。

概念

方法

返回对象

支持基于时间的窗口

支持链式分组

支持表格方法

支持在线操作

滚动窗口

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,或者在窗口操作期间利用其他列的能力,这可以提供有用的性能优势。只有在相应的调用中指定了 engine='numba' 时,才能使用 method='table' 选项。

例如,可以使用 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 对象,以使用新值继续窗口计算(即在线计算)。

这些新的窗口对象上的方法必须首先调用聚合方法来“初始化”在线计算的初始状态。然后,新的 DataFrameSeries 对象可以传递到 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

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

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

新增于 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

滚动应用#

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 执行应用聚合 (raw 也必须设置为 True)。有关参数的通用用法和性能注意事项,请参阅 使用 Numba 提高性能

Numba 将在可能的两项例程中应用

  1. 如果 func 是一个标准的 Python 函数,则该引擎将 JIT 传递的函数。 func 也可以是一个 JITed 函数,在这种情况下,该引擎不会再次 JIT 该函数。

  2. 该引擎将 JIT 应用函数应用于每个窗口的 for 循环。

engine_kwargs 参数是一个关键字参数字典,它将被传递到 numba.jit 装饰器 中。这些关键字参数将应用于传递的函数(如果是标准 Python 函数)和每个窗口上的应用 for 循环。

新增于 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 输入的情况下,将生成一个多索引的 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]

加权窗口#

win_type 参数在 .rolling 中生成加权窗口,这些窗口通常用于滤波和频谱估计。 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 矩的 **跨度**、**质心 (com)** 或 **半衰期**

\[\begin{split}\alpha = \begin{cases} \frac{2}{s + 1}, & \text{当跨度}\ 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 函数中精确指定 **跨度**、**质心**、**半衰期** 和 **alpha** 之一。

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

  • **质心** 具有更物理的解释,可以从跨度方面考虑:\(c = (s - 1) / 2\).

  • **半衰期** 是指数权重减半所需的时间段。

  • **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=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\)。)有关更多详细信息,请参阅维基百科上的 加权样本方差