groupby()性能瓶颈主要来自排序和索引重建;关闭sort=false、as_index=false可显著提速,优先用agg()而非apply(),慎用dropna=false和group_keys=true,并按数据局部性优化分组列顺序。

groupby() 性能瓶颈主要来自排序和索引重建
默认情况下 groupby() 会开启 sort=True,对分组键做全量排序;同时 as_index=True 会把分组列转为索引,触发索引重建。这两步在千万行以上数据中极易成为性能杀手。
- 关闭排序:
df.groupby('col', sort=False)—— 若业务不依赖分组结果顺序,这是最直接的提速手段 - 禁用索引:
df.groupby('col', as_index=False)—— 避免后续频繁调用reset_index(),也减少内存拷贝 - 慎用
group_keys=True(默认值):当配合apply()时,它会把分组键塞进结果索引,若你不需要该信息,设为False可省掉一次拼接操作
agg() 比 apply() 快一个数量级,别乱用 apply
apply() 是通用接口,但每次调用都会把单个分组转成 DataFrame 或 Series 再执行函数,开销极大;agg() 则走底层向量化路径,支持多列多函数批量计算。
- 正确写法:
df.groupby('city').agg({'sales': ['sum', 'mean'], 'order_id': 'count'}) - 避免写法:
df.groupby('city').apply(lambda x: pd.Series({'sales_sum': x['sales'].sum(), 'sales_mean': x['sales'].mean()})) - 自定义函数想提速?封装成 ufunc 或用
numba.jit加速,再传给agg(),而不是塞进apply()
dropna=False 可能导致意外分组膨胀
默认 dropna=True,会直接丢弃分组键为 NaN 的行;设为 False 后,所有 NaN 会被统一归入一个分组 —— 看似合理,但在高基数列(如用户 ID)中,若存在大量缺失,这个“NaN 组”可能占满 30%+ 行数,拖慢整个聚合。
- 先检查缺失率:
df['user_id'].isna().mean() - 若缺失率 > 5%,建议显式填充或过滤,而非依赖
dropna=False - 分类列用
observed=True:当分组列为category类型时,它只对实际出现的类别建组,跳过未出现的空类别,显著降低内存占用
多列分组时,顺序影响缓存局部性
写 df.groupby(['region', 'product']) 和 df.groupby(['product', 'region']) 逻辑等价,但底层 hash 分组过程对列顺序敏感。若数据在磁盘/内存中按 region 排序存储(比如 Parquet 分区),前者能更好利用 CPU 缓存行连续访问。
- 观察原始数据分布:用
df[['region', 'product']].head(1000).duplicated().sum()粗略判断高频组合是否局部聚集 - 高频低基数列放前面(如
status只有 3 个值),低频高基数列放后面(如order_id) - 避免无意义多列分组:
df.groupby(['id', 'timestamp'])几乎等于没分组,直接报错或返回原样
sort 和 as_index 这两个布尔开关——它们不报错、不警告,但会让同样逻辑的脚本在百万行数据上慢 3–5 倍。动手前先看一眼你的分组键是否已有序、是否真需要索引。Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











