
本文介绍一种高效、无显式 for/while 循环的替代方案,利用 `numpy.sliding_window_view` 构建结构化滚动窗口,支持按列名操作 dataframe 子视图,并自由返回任意数量和名称的新列。
Pandas 原生的 DataFrame.rolling().apply() 在处理多列输入 → 多列输出(尤其是输出列数 ≠ 输入列数)且需按列名访问数据的场景时存在明显限制:raw=True 仅传入 NumPy 数组(丢失列名),而 raw=False 又强制要求返回值形状与输入一致(如 window.shape == (2, 3) 时必须返回 (2, 3))。这使得实现如“基于 A/B/C 列计算 D/E/F/G 四个新指标”的需求变得困难。
此时,推荐使用 NumPy 1.20+ 提供的 sliding_window_view —— 它能以零拷贝方式生成滑动窗口视图,再结合轻量级 pd.DataFrame 构造(设置 copy=False),即可在保持高性能的同时获得完整 DataFrame 接口:
from numpy.lib.stride_tricks import sliding_window_view
import pandas as pd
import numpy as np
# 示例数据
df = pd.DataFrame({
"A": range(10),
"B": range(10, 20),
"C": range(20, 30)
})
cols = ["A", "B", "C"]
window_size = 2
# 预分配结果列表(首行为 NaN 占位,对应窗口未就绪行)
results = [tuple([np.nan] * 4)] # 对应 D, E, F, G 四列
# 滑动窗口遍历(shape: (n_windows, window_size, n_cols))
for window_arr in sliding_window_view(df.values, window_shape=(window_size, len(cols))):
# 构造临时 DataFrame(零拷贝,不复制原始数据)
window_df = pd.DataFrame(window_arr[0], columns=cols, copy=False)
# ✅ 自由按列名操作:可调用 .sum(), .mean(), .prod() 等,支持复杂逻辑
D_val = window_df["A"].sum() # 标量
E_val = (window_df["A"] + window_df["B"]).mean() # 标量
F_val = (window_df["C"] - 1).prod() # 标量
G_val = (window_df["B"] * 2).sum() # 标量
results.append((D_val, E_val, F_val, G_val))
# 合并结果到原 DataFrame
result_df = pd.DataFrame(results, columns=["D", "E", "F", "G"])
df_final = pd.concat([df, result_df], axis=1)
print(df_final)✅ 关键优势说明:
- 列名友好:window_df["A"] 直接访问,无需索引或位置硬编码;
- 输出自由:返回任意长度元组,映射为新列,不受输入列数约束;
- 性能可控:sliding_window_view 是内存视图(非复制),copy=False 进一步避免冗余拷贝;
- 可扩展性强:可在 window_df 上调用任意 Pandas 方法(groupby, agg, 自定义函数等)。
⚠️ 注意事项:
- sliding_window_view 返回的是 ndarray 视图,确保原始 df.values 不被修改,否则影响结果;
- 若需跨行聚合(如窗口内每列独立统计),注意 window_arr[0] 提取的是第一个窗口切片(二维子数组),其行为与 df.iloc[i:i+window_size] 一致;
- 对于超大规模数据(>千万行),可考虑分块处理或改用 numba.jit 加速核心计算逻辑。
该方法在代码清晰度、灵活性与执行效率之间取得了良好平衡,是替代低效循环或受限 rolling.apply 的生产级实践方案。









