首页 / Pandas 入门教程 / 扩展窗口与指数加权:expanding 与 ewm

Pandas 入门教程

扩展窗口与指数加权:expanding 与 ewm

本教程共 54 篇 · 第 43 篇 · 更新于 2026-08-11 · 约 8 分钟阅读

PandasPandas 入门教程expandingewm指数加权移动平均

本节目标:搞懂 expanding(累积窗口)和 ewm(指数加权窗口)的差别,知道什么时候用哪个,能算出累积统计量和指数加权平均。

expanding:窗口只增不减

上一节的 rolling 窗口大小固定,滑动前进。expanding(扩展窗口)不一样:窗口从第一个数据开始,每次都往后多包含一条,只增不减。第 i 个位置的结果,用的是从起点到 i 的全部数据。

用一句话记:expanding 算的是”到目前为止”的统计量。

import pandas as pd
import numpy as np

s = pd.Series([1, 2, 3, 4, 5])

# 累积均值:到每个位置为止的平均
print(s.expanding().mean())
# 0    1.0
# 1    1.5
# 2    2.0
# 3    2.5
# 4    3.0

# 累积最大值:到每个位置为止见过的最大值
print(s.expanding().max())
# 0    1.0
# 1    2.0
# 2    3.0
# 3    4.0
# 4    5.0

看累积均值:索引 1 是 (1+2)/2=1.5,索引 2 是 (1+2+3)/3=2.0。窗口越长,数据越多,但起点永远是第一个。

实际上 expanding 就是滚动窗口的特例:窗口大小等于整个数据长度,再用 min_periods=1 让第一个点就能出结果。

import pandas as pd
import numpy as np

s = pd.Series([1, 2, 3, 4, 5])

# 下面两种写法结果完全一样
print(s.rolling(window=len(s), min_periods=1).mean())
print(s.expanding(min_periods=1).mean())

expanding 的用法与场景

expanding 支持的聚合方法和 rolling 一样:sum、mean、max、min、std、var、median、count,也能用 agg 一次算多个:

import pandas as pd
import numpy as np

df = pd.DataFrame({"A": range(5), "B": range(10, 15)})
print(df.expanding().agg(["sum", "mean", "std"]))
#       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

min_periods 在这里同样有效:指定至少累积多少个值才出结果。

expanding 的典型场景:

  • 累计求和,比如”今年累计销售额”——其实用 cumsum() 也能做,expanding().sum() 是更通用的写法
  • 累计最大值/最小值,比如股价上市以来的最高价
  • 累积均值、累积标准差,比如”到目前为止的平均成绩”
Note

expanding 没有时间窗口的写法,窗口永远从起点开始。如果只想算”最近 3 个月的累积值”,那属于 rolling 加时间窗口的范畴。

ewm:越近的数据越重要

expanding 把历史上所有数据一视同仁。但很多场景里,昨天的数据比去年的数据更能说明问题。指数加权窗口(exponentially weighted window,ewm)就为此而生:每条数据都有权重,越靠近当前,权重越大,权重按指数递减。

import pandas as pd
import numpy as np

s = pd.Series([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

# alpha=0.5:每条数据的权重是前一条的 (1-0.5)=0.5 倍
print(s.ewm(alpha=0.5).mean().round(3))
# 0     1.000
# 1     1.667
# 2     2.429
# 3     3.267
# 4     4.161
# ...

拿索引 3 来说,结果 3.267 不是简单平均,而是当前值 4 的权重最大,越早的数据权重越小。alpha 是平滑系数,范围是 0 到 1:

  • alpha 越大,越看重近期数据,曲线跟得越紧
  • alpha 越小,历史数据的影响越久,曲线越平滑

把结果和 rolling 的普通平均对比,感觉更直观:同样 10 个递增的数,5 条平均在索引 4 才等于 2.0,而 ewm 从第二个点开始就”追”着最新值走。

参数怎么选:alpha、span 还是 halflife

直接记 alpha 不太符合直觉,pandas 提供了另外三个等价的参数,只能四选一:

参数含义与 alpha 的关系
alpha平滑系数直接指定
span跨度,类似”N 日加权平均”alpha = 2 / (span + 1)
halflife半衰期:权重衰减到一半所需的时间alpha = 1 - 0.5^(1/halflife)
com质心,物理含义较强alpha = 1 / (com + 1)

日常最常用的是 span。股票软件里的 EMA(指数移动平均线)就常用 span=12、span=26 这种”日”的说法:

import pandas as pd
import numpy as np

s = pd.Series([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

# span=3 等价于 alpha=2/(3+1)=0.5
print(s.ewm(span=3).mean().round(3))
# 0     1.000
# 1     1.667
# 2     2.429
# 3     3.267
# ...

两种计算方式的区别

ewm 的默认参数 adjust=True,结果按”加权平均”公式算:分子是各数据乘权重后求和,分母是权重之和。这样前几个点因为权重总和还很小,结果会明显偏向当前值。

改成 adjust=False,就变成递推公式:y_t = alpha * x_t + (1 - alpha) * y_{t-1},新的结果由”当前值”和”上一个结果”直接算出来,第一个点等于原始值。数据无限长时两种方式结果一致;数据短时略有差别。

import pandas as pd
import numpy as np

s = pd.Series([1, 2, 3, 4, 5])

print(s.ewm(alpha=0.5).mean().round(3))
# 0    1.000
# 1    1.667
# 2    2.429
# 3    3.267
# 4    4.161

print(s.ewm(alpha=0.5, adjust=False).mean().round(3))
# 0    1.000
# 1    1.500
# 2    2.250
# 3    3.125
# 4    4.062

看到差别了吗?adjust=False 时索引 1 是 0.52 + 0.51 = 1.5,完全由递推得到。默认的 adjust=True 足够日常使用,不用刻意改。

ewm 的其他方法

除了 mean,ewm 还支持 std、var、cov、corr。比如算”指数加权波动率”,对收益率序列做 ewm 标准差,比 rolling 标准差反应更快:

import pandas as pd
import numpy as np

s = pd.Series([0.01, -0.02, 0.03, 0.005, -0.01])
print(s.ewm(span=10).std().round(4))

小例子:平滑一条带噪声的曲线

把三种窗口放在同一份数据上对比,用途一目了然。造一串带噪声的数据,分别用 rolling 和 ewm 平滑:

import pandas as pd
import numpy as np

np.random.seed(42)
s = pd.Series(100 + np.random.randn(20).cumsum())

# 普通 5 条移动平均:对每个点都一视同仁
ma = s.rolling(5).mean()
# 指数加权:近期数据权重更大,拐弯更快
ema = s.ewm(span=5).mean()

print(pd.DataFrame({"原始": s, "MA5": ma, "EMA5": ema}).round(2).head(10))
#      原始    MA5   EMA5
# 0  100.50    NaN  100.50
# 1  100.36    NaN  100.41
# 2  101.01    NaN  100.69
# 3  102.53    NaN  101.46
# 4  102.30  101.34  101.78
# ...

注意看 EMA5 从第 0 个点就有值,而且每一步都贴着原始数据走;MA5 要凑满 5 条才开始输出,拐弯也更”钝”。这就是指数加权的意义:敏感、及时。

Tip

ewm 对缺失值默认按位置计算权重(ignore_na=False),中间有 NaN 会影响结果。数据缺失较多时,可以设 ignore_na=True,让权重跳过缺失值。

分组后的 expanding 与 ewm

窗口函数同样可以接在 groupby(§37)后面,每组各自累积。比如每个城市的销售额,各自算”开业以来累计销售额”和”指数加权均值”:

import pandas as pd
import numpy as np

df = pd.DataFrame(
    {"城市": ["北京"] * 3 + ["上海"] * 3,
     "销售额": [100, 200, 150, 300, 200, 250]}
)

df["累计销售额"] = df.groupby("城市")["销售额"].expanding().sum().reset_index(drop=True)
print(df)
#    城市  销售额  累计销售额
# 0  北京   100    100.0
# 1  北京   200    300.0
# 2  北京   150    450.0
# 3  上海   300    300.0
# 4  上海   200    500.0
# 5  上海   250    750.0

北京自己的累计是 100、300、450,上海的累计从 300 重新开始,两组互不干扰。

三种窗口怎么选

窗口窗口大小权重典型场景
rolling固定 n 条(或固定时长)平均移动平均线、短期波动率
expanding从起点到当前平均累计统计、历史极值
ewm从起点到当前近期更大技术指标 EMA、快速平滑

一句话总结:想算”最近 n 条”,用 rolling;想算”到目前为止”,用 expanding;想算”到目前为止但更看重近期”,用 ewm。

Warning

expanding 和 ewm 都要求数据按时间顺序排列。数据乱序时,累积窗口的含义就乱了,结果没有意义。