Pandas: 窗口计算

最后更新:2026-08-26

第 17 课入门了 rolling/expanding/ewm,本课深入:自定义窗口聚合、时间窗口进阶、ewm 参数关系、groupby+rolling 组合。窗口计算是量化分析和传感器数据处理的核心工具——掌握它,你就掌握了"滑动看趋势"的能力。

⚠️ 注意: 以下代码需在本地 Python 环境中运行。

1. 你将学到


2. Carol 的运动心率窗口分析

(1) 痛点:30 秒窗口均值不够

Carol 的运动心率数据需要更丰富的窗口统计——不只是均值,还有最大值、心率变异度(标准差):

PYTHON
import pandas as pd
import numpy as np

np.random.seed(42)
hr = pd.DataFrame({
    'heart_rate': np.random.normal(75, 10, 60).round(0)
}, index=pd.date_range('2024-01-15 08:00', periods=60, freq='30s'))
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。

(2) 解法:rolling 多聚合

▶ 示例:rolling 多指标统计(难度⭐)

PYTHON
import pandas as pd
import numpy as np

np.random.seed(42)
hr = pd.DataFrame({
    'heart_rate': np.random.normal(75, 10, 60).round(0)
}, index=pd.date_range('2024-01-15 08:00', periods=60, freq='30s'))

# Rolling with multiple aggregations
stats = hr['heart_rate'].rolling(10).agg(['mean', 'std', 'min', 'max'])
stats.columns = ['hr_ma', 'hr_std', 'hr_min', 'hr_max']
print(stats.dropna().head(5).round(1))
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。

3. rolling 进阶

(1) 关键参数

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。

:rolling 参数详解(难度⭐⭐)

PYTHON
import pandas as pd
import numpy as np

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

# min_periods: allow partial windows
print(s.rolling(5, min_periods=2).mean().head(5))

# center: centered window (for smoothing)
print(s.rolling(5, center=True).mean().head(5))

# win_type: weighted window (Gaussian, triangular, etc.)
print(s.rolling(5, win_type='gaussian').mean(std=1.5).head(8))

# on: use a column as time (when not the index)
df = pd.DataFrame({
    'timestamp': pd.date_range('2024-01', periods=20, freq='6h'),
    'value': np.random.randn(20).cumsum()
})
print(df.rolling('12h', on='timestamp')['value'].mean().head(5))
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。

(2) rolling 参数速查

参数 默认 说明
window 必填 窗口大小(整数行数或时间字符串)
min_periods window 窗口内最少观测数
center False 窗口居中
win_type None 加权类型(gaussian/triangular等)
on None 用某列做时间(非 Index)
closed 'right' 窗口包含哪端

4. 自定义窗口聚合

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。

:自定义聚合函数(难度⭐⭐⭐)

PYTHON
import pandas as pd
import numpy as np

np.random.seed(42)
df = pd.DataFrame({
    'value': np.random.randint(10, 100, 20)
})

# Custom: range (max - min) in window
def window_range(arr):
    return arr.max() - arr.min()

df['rolling_range'] = df['value'].rolling(5).apply(window_range, raw=True)

# Custom: coefficient of variation (std/mean)
def cv(arr):
    return arr.std() / arr.mean() if arr.mean() != 0 else 0

df['rolling_cv'] = df['value'].rolling(5).apply(cv, raw=True)

# Using rolling.apply with raw=True for performance
# raw=True: receives numpy array (fast)
# raw=False: receives Series (slow but flexible)

print(df.head(10))
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
💡 提示: 自定义函数中 raw=True 性能好 10 倍(接收 NumPy 数组而非 Series),但无法使用 Index 信息。简单数值计算用 raw=True,需要 Index 用 raw=False。


5. expanding 深入

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。

:expanding 自定义(难度⭐)

PYTHON
import pandas as pd
import numpy as np

np.random.seed(42)
df = pd.DataFrame({
    'score': np.random.randint(60, 100, 15)
})

# Built-in expanding methods
df['cummean'] = df['score'].expanding().mean()
df['cumstd'] = df['score'].expanding().std()
df['cummax'] = df['score'].expanding().max()

# Expanding with min_periods
df['cummean_5'] = df['score'].expanding(min_periods=5).mean()

# Custom: expanding z-score
def zscore(arr):
    if len(arr) < 2: return np.nan
    return (arr[-1] - arr.mean()) / arr.std()

df['zscore'] = df['score'].expanding().apply(zscore, raw=True)
print(df.round(2).head(10))
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。

6. ewm 参数详解

(1) 4 种参数关系

参数 关系 含义
span com = span - 1 类似 N 期 SMA
com α = 1/(1+com) 质心
halflife α = 1 - 0.5^(1/halflife) 权重减半的期数
alpha 直接指定 平滑因子 0<α≤1

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。

:ewm 参数对比(难度⭐⭐)

PYTHON
import pandas as pd
import numpy as np

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

# These are all equivalent (approximately)
ewm_span5 = s.ewm(span=5).mean()
ewm_com4 = s.ewm(com=4).mean()          # com = span - 1
ewm_hl3 = s.ewm(halflife=3).mean()      # different weighting
ewm_alpha = s.ewm(alpha=0.333).mean()   # alpha = 2/(span+1)

# Compare with SMA
sma5 = s.rolling(5).mean()

print("SMA vs EWM (first 10):")
print(pd.DataFrame({
    'value': s, 'SMA_5': sma5, 'EWM_span5': ewm_span5
}).head(10).round(2))

# ewm std (volatility)
ewm_std = s.ewm(span=10).std()
print(f"\nLatest EWM std: {ewm_std.iloc[-1]:.2f}")
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。

7. groupby + rolling

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。

:分组内滚动窗口(难度⭐⭐⭐)

PYTHON
import pandas as pd
import numpy as np

# Sensor data: multiple sensors, each with its own time series
np.random.seed(42)
df = pd.DataFrame({
    'sensor_id': np.repeat(['T01', 'T02', 'T03'], 20),
    'timestamp': list(pd.date_range('2024-01', periods=20, freq='6h')) * 3,
    'temperature': np.random.normal(20, 3, 60).round(1)
})

# Rolling within each sensor group
df = df.sort_values(['sensor_id', 'timestamp'])
df['temp_ma'] = df.groupby('sensor_id')['temperature'].transform(
    lambda x: x.rolling(4, min_periods=1).mean()
)

# Alternative: groupby + rolling (returns MultiIndex)
result = df.groupby('sensor_id')['temperature'].rolling(4).mean()
print(result.head(12))

# Reset for clean output
df['temp_ma2'] = df.groupby('sensor_id')['temperature'].transform(
    lambda x: x.rolling(4).mean().values
)
print(df[df['sensor_id'] == 'T01'].head(6))
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。

8. 完整示例:运动心率数据分析

(5) ▶ 3 种窗口对比

100%
graph LR
    A[窗口计算] --> B[rolling 固定窗口]
    A --> C[expanding 累计窗口]
    A --> D[ewm 指数加权]
    B --> E[局部趋势 / 移动平均]
    C --> F[累计统计 / 从开头至今]
    D --> G[近期权重大 / 平滑]
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。

:窗口分析全流程(难度⭐⭐⭐)

PYTHON
import pandas as pd
import numpy as np

# ============================================
# Comprehensive example: Fitness heart rate
# window analysis pipeline
# ============================================

# 1. Generate 2-hour workout data (1 reading per 30s = 240 rows)
np.random.seed(42)
timestamps = pd.date_range('2024-01-15 08:00', periods=240, freq='30s')
exercise_type = (['Warmup'] * 30 + ['Cardio'] * 90 + ['Weights'] * 60 + ['Cooldown'] * 60)
hr_base = ([70] * 30 + list(np.linspace(70, 140, 90)) + [120] * 60 + list(np.linspace(120, 70, 60)))
heart_rates = [max(50, h + np.random.randn() * 5) for h in hr_base]

df = pd.DataFrame({
    'timestamp': timestamps,
    'exercise': exercise_type,
    'heart_rate': np.round(heart_rates, 0)
})

# 2. Rolling: 1-minute (2 readings) and 5-minute (10 readings) moving avg
df['hr_ma_1min'] = df['heart_rate'].rolling(2).mean()
df['hr_ma_5min'] = df['heart_rate'].rolling(10).mean()

# 3. Rolling: heart rate variability (std in 5-min window)
df['hrv'] = df['heart_rate'].rolling(10).std()

# 4. Expanding: cumulative average
df['cum_avg_hr'] = df['heart_rate'].expanding().mean()

# 5. EWM: smoothed trend
df['hr_ewm'] = df['heart_rate'].ewm(span=10).mean()

# 6. Groupby + rolling: per exercise type
df['hr_ma_per_exercise'] = df.groupby('exercise')['heart_rate'].transform(
    lambda x: x.rolling(5, min_periods=1).mean()
)

# 7. Summary by exercise
print("=== Exercise Summary ===")
summary = df.groupby('exercise')['heart_rate'].agg(['mean', 'max', 'min', 'std']).round(1)
print(summary)
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。

❓ 常见问题

Q rolling 支持时间窗口吗?
A 支持——rolling('7D') 使用 7 天日历窗口,rolling(7) 使用 7 行窗口。时间窗口适合非均匀采样数据(跳过周末/缺失日),行窗口适合均匀采样。时间窗口要求 Index 是 DatetimeIndex 或用 on 参数指定时间列。
Q 自定义函数性能如何?
A 自定义函数比内置聚合慢 10-100 倍——每个窗口都要调用一次 Python 函数。优化:raw=True 传 NumPy 数组(快 5-10 倍),用 NumPy 操作替代 Python 循环。内置函数用 C 实现,自定义函数走 Python 解释器。能用内置就别自定义。
Q ewm 的 span/halflife/com 关系?
A 三者在数学上等价,只是不同表述。span=N 类似 N 期 SMA 但近期权重大;com=span-1 是质心;halflife=权重减半的期数。常用 span(直觉最清晰)。α=2/(span+1) 是平滑因子。指定一个即可,不需要同时指定。
Q groupby+rolling 注意什么?
A 两个关键点:① 先 sort_values 按分组键+时间排序,否则窗口可能跨越组;② transform(lambda x: x.rolling(N).mean()) 返回与原 DataFrame 同长度结果,groupby+rolling 直接调用返回 MultiIndex。大数据时 transform 更方便。
Q win_type 有哪些?
A scipy.signal.windows 中的所有窗函数:gaussian(高斯加权)、triang(三角形)、blackman、hamming、kaiser 等。需要 pip install scipy。win_type 给窗口内数据加权(中间权重大,两端小),比等权窗口更平滑。
Q closed 参数有什么用?
A closed 控制窗口包含哪端——'right'(默认,包含右端=当前行),'left'(包含左端),'both'(两端),'neither'(都不含)。金融中常用 'right'(只用历史数据),某些统计场景需要 'both' 或 'left'。

📖 小节


📝 作业

  1. 基础题(难度⭐):创建 30 天温度数据,用 rolling(7, min_periods=3) 计算移动均值和标准差,用 ewm(span=7) 计算指数平滑。
  2. 进阶题(难度⭐⭐):创建多传感器温度数据(3 传感器各 20 行),用 groupby+rolling 计算每个传感器的 5 期移动平均。
  3. 挑战题(难度⭐⭐⭐):模拟 240 行运动心率数据(含 4 种运动类型),完成:rolling 多指标(均值/标准差/最大值) → expanding 累计平均 → ewm 平滑 → groupby+rolling 按运动类型 → 汇总报告。

← 上一课:多层索引 · 下一课:可视化 →

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏