Pandas: 窗口计算
最后更新:2026-08-26
第 17 课入门了 rolling/expanding/ewm,本课深入:自定义窗口聚合、时间窗口进阶、ewm 参数关系、groupby+rolling 组合。窗口计算是量化分析和传感器数据处理的核心工具——掌握它,你就掌握了"滑动看趋势"的能力。
⚠️ 注意: 以下代码需在本地 Python 环境中运行。
1. 你将学到
- ❶ rolling 进阶参数
- ❷ 自定义窗口聚合
- ❸ expanding 累计窗口深入
- ❹ ewm 参数详解
- ❺ groupby + rolling
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 种窗口对比
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'。
📖 小节
- rolling 进阶:min_periods 允许部分窗口,center 居中,win_type 加权,on 指定时间列
- 自定义聚合用 .apply(func, raw=True),raw=True 性能好 10 倍
- expanding 从开头累计到当前位置,支持任意聚合函数
- ewm 参数:span(N期)/com(N-1)/halflife/alpha,四者等价
- groupby+rolling 先排序再计算,transform 返回同长度结果
- 选择:局部趋势→rolling,累计→expanding,平滑→ewm
📝 作业
- 基础题(难度⭐):创建 30 天温度数据,用 rolling(7, min_periods=3) 计算移动均值和标准差,用 ewm(span=7) 计算指数平滑。
- 进阶题(难度⭐⭐):创建多传感器温度数据(3 传感器各 20 行),用 groupby+rolling 计算每个传感器的 5 期移动平均。
- 挑战题(难度⭐⭐⭐):模拟 240 行运动心率数据(含 4 种运动类型),完成:rolling 多指标(均值/标准差/最大值) → expanding 累计平均 → ewm 平滑 → groupby+rolling 按运动类型 → 汇总报告。