为何传入多函数的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
相关产品推荐
相关产品推荐

