窗口操作#

pandas 包含一套紧凑的 API,用于执行窗口操作——即在值的滑动分区上执行聚合的操作。该 API 的工作方式类似于 groupby API,即 SeriesDataFrame 使用必要的参数调用窗口方法,然后依次调用聚合函数。

窗口是通过从当前观测值向前回溯窗口长度来构成的。上述结果可以通过对以下窗口化数据分区求和得出:

概述#

pandas 支持 4 种类型的窗口操作:

  1. 滚动窗口:在值上进行通用的固定或可变滑动窗口。

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

  3. 扩展窗口:在值上进行累积窗口。

  4. 指数加权窗口:在值上进行累积和指数加权窗口。

概念

方法

返回对象

支持基于时间的窗口

支持链式 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 版本开始)

如上所述,一些操作支持指定一个基于偏移量的窗口:

此外,一些方法支持将 groupby 操作与窗口操作链接起来,它会首先按指定的键对数据进行分组,然后对每个组执行窗口操作。

备注

窗口操作目前仅支持数值型数据(整数和浮点数),并且将始终返回 float64 类型的值。

警告

某些窗口聚合操作,如 mean, sum, varstd 方法可能会由于底层的窗口算法累积求和而产生数值不精确的问题。当数值相差 \(1/np.finfo(np.double).eps\) 时,会导致截断。必须注意的是,较大的值可能会影响不包含这些值的窗口。为了尽可能地保持精度,会使用 Kahan summation 来计算滚动求和。

在 1.3.0 版本加入.

某些窗口操作还支持构造函数中的 method='table' 选项,该选项会作用于整个 DataFrame 而不是一次处理单个列或行。这可以为具有许多列或行的 DataFrame (以及相应的 axis 参数) 提供有用的性能优势,或者能够利用其他列来执行窗口操作。仅当在相应的调用方法中指定了 engine='numba' 时,才能使用 method='table' 选项。

例如,可以通过指定一个单独的权重列,使用 weighted mean 来计算 apply()

在 1.3 版本加入.

某些窗口操作还支持在构造窗口对象后使用的 online 方法,该方法返回一个新对象,支持传入新的 DataFrameSeries 对象以继续进行窗口计算(即在线计算)。

此新窗口对象上的方法必须先调用聚合方法来“预热”在线计算的初始状态。然后,可以将新的 DataFrameSeries 对象传递给 update 参数以继续进行窗口计算。

所有窗口操作都支持 min_periods 参数,该参数规定了窗口中必须包含的非 np.nan 值的最小数量;否则,结果值为 np.nan。时间类窗口 min_periods 的默认值为 1,固定窗口的默认值为 window

此外,所有窗口操作都支持 aggregate 方法,用于返回应用于窗口的多个聚合结果。

滚动窗口#

通用的滚动窗口支持将窗口指定为固定数量的观测值,或者根据偏移量指定可变数量的观测值。如果提供了基于时间的偏移量,则相应的基于时间的索引必须是单调的。

有关所有支持的聚合函数,请参阅 滚动窗口函数

中心化窗口#

默认情况下,标签设置在窗口的右边缘,但有一个 center 关键字可用,因此标签可以设置在中心。

这也适用于类 datetime 索引。

在 1.3.0 版本加入.

滚动窗口端点#

可以使用 closed 参数来指定是否在滚动窗口计算中包含区间端点:

行为

'right'

闭合右端点

'left'

闭合左端点

'both'

'both'

'neither'

开放端点

例如,在许多需要避免当前信息影响过去信息的问题中,将右端点设置为开放是有用的。这允许滚动窗口计算“直到那一时刻”的统计数据,但不包括那一时刻。

自定义窗口滚动#

除了接受整数或偏移量作为 window 参数外,rolling 还接受 BaseIndexer 子类,允许用户定义计算窗口边界的自定义方法。BaseIndexer 子类需要定义一个 get_window_bounds 方法,该方法返回一个包含两个数组的元组,第一个数组是窗口的起始索引,第二个数组是窗口的结束索引。此外,num_valuesmin_periodscenterclosedstep 将自动传递给 get_window_bounds,并且定义的该方法必须始终接受这些参数。

例如,如果我们有以下 DataFrame

并且我们想使用一个扩展窗口,其中 use_expandingTrue,否则使用大小为 1 的窗口,我们可以创建以下 BaseIndexer 子类:

您可以 here 查看 BaseIndexer 子类的其他示例

其中一个值得注意的子类是 VariableOffsetWindowIndexer,它允许在非固定偏移量(如 BusinessDay)上进行滚动操作。

对于某些问题,未来的知识可用于分析。例如,当每个数据点都是从实验中读取的完整时间序列时,就会发生这种情况,任务是提取潜在的条件。在这些情况下,执行前瞻性滚动窗口计算可能很有用。为此,可以使用 FixedForwardWindowIndexer 类。这个 BaseIndexer 子类实现了一个封闭的固定宽度前瞻性滚动窗口,我们可以如下使用它:

我们也可以通过使用切片、应用滚动聚合,然后翻转结果来实现这一点,如下面的示例所示:

滚动应用#

apply() 函数接受一个额外的 func 参数,并执行通用的滚动计算。func 参数应该是一个从 ndarray 输入产生单个值的函数。raw 指定窗口是作为 Series 对象(raw=False)还是 ndarray 对象(raw=True)进行转换。

Numba 引擎#

此外,apply() 可以利用 Numba (如果已安装为可选依赖项)。可以通过指定 engine='numba'engine_kwargs 参数(raw 也必须设置为 True)来使用 Numba 执行 apply 聚合。有关参数的一般用法和性能注意事项,请参阅 enhancing performance with Numba

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

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

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

engine_kwargs 参数是一个字典,其中包含将传递给 numba.jit decorator 的关键字参数。这些关键字参数将应用于传入的函数(如果是标准 Python 函数)以及应用于每个窗口的 for 循环。

在 1.3.0 版本加入.

meanmedianmaxminsum 也支持 engineengine_kwargs 参数。

二元窗口函数#

cov()corr() 可以计算两个 SeriesDataFrame SeriesDataFrame DataFrame 的任意组合的移动窗口统计信息。每种情况下的行为如下:

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

  • DataFrame Series :计算 DataFrame 中每列与传入 Series 的统计信息,从而返回一个 DataFrame。

  • DataFrame DataFrame :默认情况下,计算匹配列名的统计信息,返回一个 DataFrame。如果传递关键字参数 pairwise=True,则计算每对列的统计信息,返回一个具有 DataFrameMultiIndex ,其值为相关的日期(请参阅 the next section )。

例如:

计算滚动成对协方差和相关性#

在金融数据分析和其他领域,通常需要计算一系列时间序列的协方差和相关性矩阵。通常,人们也对移动窗口的协方差和相关性矩阵感兴趣。这可以通过传递 pairwise 关键字参数来实现,在 DataFrame 输入的情况下,这将产生一个 MultiIndexed DataFrame ,其 index 是相关的日期。在只有一个 DataFrame 参数的情况下,甚至可以省略 pairwise 参数:

备注

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

假设缺失数据是随机缺失的,这将导致协方差矩阵的估计是无偏的。然而,对于许多应用来说,这种估计可能无法接受,因为估计的协方差矩阵不保证是半正定的。这可能导致估计的相关值具有大于一的绝对值,和/或一个不可逆的协方差矩阵。更多细节请参见 Estimation of covariance matrices

加权窗口#

.rolling 中的 win_type 参数会生成常用于滤波和谱估计的加权窗口。win_type 必须是与 scipy.signal window function 对应的字符串。为了使用这些窗口,必须安装 Scipy,并且 Scipy 窗口方法接受的补充参数必须在聚合函数中指定。

有关所有支持的聚合函数,请参阅 加权窗口函数

扩展窗口#

扩展窗口会生成一个聚合统计值的数值,该数值包含了截至该时间点所有可用数据。由于这些计算是滚动统计的特例,因此在 pandas 中实现它们的方式如下,以下两个调用是等效的:

有关所有支持的聚合函数,请参阅 Expanding window functions

指数加权窗口#

指数加权窗口类似于扩展窗口,但每个先前点相对于当前点都呈指数衰减权重。

通常,加权移动平均计算如下:

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

其中 \(x_t\) 是输入,\(y_t\) 是结果,\(w_i\) 是权重。

有关所有支持的聚合函数,请参阅 Exponentially-weighted window functions

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 - \alpha\) ,首项为 1 的几何级数,我们得到:

\[\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)**、质心 (center 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}\]

必须精确指定 spancenter of masshalf-lifealpha 中的一个参数给 EW 函数:

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

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

  • Half-life 是指数权重减半所需的时间周期。

  • Alpha 直接指定平滑因子。

您还可以根据可转换为 timedelta 的单位指定 halflife,以指定当同时指定一系列 times 时,观测值衰减到其值的一半所需的时间量。

以下公式用于计算带有时间输入向量的指数加权移动平均:

\[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}.\]

:meth:`~Ewm.var :meth:`~Ewm.std:meth:`~Ewm.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