You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何计算Pandas Series滚动窗口内仅正数的标准差?

问题:计算固定滚动窗口内正数的标准差

我生成了一个取值范围在-1到+1之间的随机Pandas Series:

from pandas import Series
from random import random

x = Series([random() * 2 - 1. for i in range(1000)])

输出示例:

0    -0.499376
1    -0.386884
2     0.180656
3     0.014022
4     0.409052
        ...
995  -0.395711
996  -0.844389
997  -0.508483
998  -0.156028
999   0.002387
Length: 1000, dtype: float64

计算整个序列的滚动标准差很简单:

x.rolling(30).std()

输出示例:

0       NaN
1       NaN
2       NaN
3       NaN
4       NaN
        ...
995    0.575365
996    0.580220
997    0.580924
998    0.577202
999    0.576759
Length: 1000, dtype: float64

但我的需求是:计算固定滚动窗口(比如大小为30)内仅正数的标准差——如果窗口里只有15个正数,就计算这15个的标准差。

有人尝试先过滤掉负数再计算滚动标准差:

x[x > 0].rolling(30).std()

但这种方法不符合要求:它的窗口是连续的30个正数,而不是原序列中固定长度的窗口内的正数。我想避免遍历整个序列,求更Pythonic的实现方式。


解决方案

方法1:使用rolling.apply(直观但效率较低)

直接对每个滚动窗口筛选正数并计算标准差,当窗口内正数不足2个时返回NaN(标准差至少需要2个样本):

window_size = 30

def positive_std(window):
    positives = window[window > 0]
    return positives.std() if len(positives) >= 2 else float('nan')

positive_rolling_std = x.rolling(window_size).apply(positive_std, raw=False)

这种方法逻辑直观,但apply本质是逐窗口循环,处理大序列时速度较慢。

方法2:利用滚动统计量推导(高效,适合大规模数据)

利用标准差的数学公式:标准差 = √(均值(平方) - (均值)²),我们可以通过三个滚动统计量推导结果:

window_size = 30

# 仅保留正数,非正数替换为0
x_pos = x.where(x > 0, 0)
x_pos_sq = x_pos ** 2

# 计算滚动窗口内正数的数量、总和、平方和
count_pos = x.where(x > 0, 0).rolling(window_size).count()
sum_pos = x_pos.rolling(window_size).sum()
sum_pos_sq = x_pos_sq.rolling(window_size).sum()

# 计算均值与平方均值
mean_pos = sum_pos / count_pos
mean_pos_sq = sum_pos_sq / count_pos

# 计算标准差,数量不足2时设为NaN
positive_rolling_std = (mean_pos_sq - mean_pos ** 2) ** 0.5
positive_rolling_std = positive_rolling_std.where(count_pos >= 2, float('nan'))

这种方法完全依赖Pandas的向量化滚动操作,没有逐窗口循环,效率比apply高得多,适合处理十万级以上的大序列。


内容的提问来源于stack exchange,提问作者dimitriapostol

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 09:33:20