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

为何传入多函数的rolling.aggregate比循环调用单函数更慢?

Pandas中groupby.rolling.aggregate传函数列表为何比循环单函数慢?

结论

这是预期行为,并非操作错误。两种实现的性能差异源于Pandas内部的执行逻辑不同。

背后原因

1. 循环单函数(Vs2)的执行逻辑

当逐个传入单函数调用aggregate时,Pandas会:

  • 复用已构建的groupby分组结构,无需重复计算分组边界
  • 对sum/mean这类内置聚合函数,直接调用Cython实现的高效向量化内核,针对整个分组的所有列做批量滚动计算
  • 中间结果的内存布局更紧凑,减少了对象创建和数据拷贝的额外开销

2. 传函数列表(Vs1)的执行逻辑

当传入函数列表时,Pandas的内部处理是嵌套遍历模式:

  • 先遍历每个分组的每个滚动窗口,再遍历每个列,最后遍历每个函数
    这种细碎的嵌套结构会产生大量调度开销,且无法复用单函数调用时的向量化优化——相当于把每个函数的计算拆成了无数子任务,额外增加了中间对象的创建与销毁成本。在多列(100列)+多函数的场景下,这种开销会被急剧放大,直接导致性能暴跌。

测试代码

import datetime
import time

import pandas as pd

df = pd.DataFrame(
    1,
    index=pd.MultiIndex.from_product(
        [["a", "b", "c"], list(range(10000))], names=["letter", "int"]
    ),
    columns=range(100),
)
df_g = df.groupby(["letter"])

func_list = ["sum", "count", "mean", "std"]

# Vs 1 - 传入函数列表
start_time = time.time()
res_df_list = df_g.rolling(100, min_periods=1).aggregate(func_list)
end_time = time.time()
time_lapsed = end_time - start_time
print(f"Vs1: {str(datetime.timedelta(seconds=time_lapsed))}")

# Vs 2 - 循环遍历每个函数
start_time = time.time()
res_list = []
for func in func_list:
    res = df_g.rolling(100, min_periods=1).aggregate(func)
    res.columns = pd.MultiIndex.from_product([res.columns.to_list(), [func]])
    res_list.append(res)
res_df_loop = pd.concat(res_list, axis=1)
end_time = time.time()
time_lapsed = end_time - start_time
print(f"Vs2: {str(datetime.timedelta(seconds=time_lapsed))}")

优化建议

对于多函数同列的滚动聚合场景,优先使用循环单函数再拼接的方式(Vs2),可以充分利用Pandas的向量化优化,避免不必要的调度开销。如果需要为不同列指定不同函数,可以使用agg的字典参数形式(如agg({'col1': ['sum', 'mean'], 'col2': ['std']})),但性能仍可能略逊于单函数循环。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 21:50:40