窗口操作#

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开始)

Weighted window

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 求和 用于计算滚动和,以尽可能保持精度。

Added in version 1.3.0.

一些窗口操作在构造函数中也支持 method='table' 选项,该选项在整个 DataFrame 上执行窗口操作,而不是一次对单个列进行操作。这可以为具有许多列的 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

Added in version 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

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

Added in version 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 参数(raw 也必须设置为 True),可以使用 Numba 执行 apply 聚合。有关参数的一般用法和性能考虑,请参见 使用 Numba 增强性能

Numba 将可能应用于两个例程中:

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

  2. 引擎将对应用了 apply 函数的每个窗口进行 JIT 编译的 for 循环。

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

Added in version 1.3.0.

mean, median, max, min, 和 sum 也支持 engineengine_kwargs 参数。

二进制窗口函数#

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

  • 两个 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 输入,这将产生一个多索引的 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]

Weighted window#

.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 矩的 spancenter of mass (com)half-life

\[\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函数精确指定 spancenter of masshalf-lifealpha 中的一个:

  • Span 对应于通常称为“N天指数加权移动平均线”。

  • 质心 有一个更物理的解释,并且可以被认为是跨度的一种形式:\(c = (s - 1) / 2\)

  • 半衰期 是指数权重减少到一半所需的时间。

  • Alpha 直接指定平滑因子。

你也可以用可转换为 timedelta 的单位来指定 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\)。)有关更多详细信息,请参见维基百科上的 加权样本方差