Pandas GroupBy性能问题求助:大内存工作站处理千万级数据集慢于R
我太懂这种糟心的感觉了——手里握着190G内存的工作站,处理770万条数据的GroupBy居然慢到离谱,而同逻辑的R代码不到1小时就跑完了,换谁都得挠头。结合你的数据集结构(2个ID列、typeid、日期列+3-5个分类列),给你几个针对性的优化方案,亲测能大幅提升速度:
1. 先给数据“瘦个身”——优化数据类型是基础
Pandas默认的object类型(字符串)和大整数类型会吃掉大量内存,而且GroupBy时的计算效率极低,先把能转的列都转成高效类型:
- ID列(ID1、ID2):如果是整数类型,用
pd.to_numeric(..., downcast='integer')转成最小能容纳的整数类型(比如int32甚至int16,只要数值范围够);如果是字符串但重复率高(毕竟是GroupBy键,肯定有大量重复),直接转成category类型,内存能省一大半。 - 分类列(typeid、cat1/cat2/cat3):毫不犹豫转成
category!Pandas对category类型的GroupBy有专门优化,尤其是基数不高的分类列,不仅内存占用骤降,计算速度能提升好几倍。 - date_列:务必转成
datetime64类型,别留成字符串,日期类型的GroupBy效率比字符串高太多。
举个实操代码片段:
# 批量转换分类列 cat_cols = ['typeid', 'cat1', 'cat2', 'cat3'] df[cat_cols] = df[cat_cols].astype('category') # 压缩ID列内存 df['ID1'] = pd.to_numeric(df['ID1'], downcast='integer') df['ID2'] = pd.to_numeric(df['ID2'], downcast='integer') # 标准化日期列 df['date_'] = pd.to_datetime(df['date_'])
2. 调整GroupBy的参数,避免做无用功
加上observed=True(针对分类列)
如果你的GroupBy键包含category类型列,默认情况下Pandas会计算所有可能的类别组合(哪怕数据里不存在),加上observed=True会只计算实际存在的组合,能大幅减少计算量:
# 替换成你的聚合规则 agg_rules = { 'agg_col1': ['sum', 'mean'], 'agg_col2': 'max' } result = df.groupby( ['ID1', 'ID2', 'typeid', 'date_', 'cat1', 'cat2', 'cat3'], observed=True ).agg(agg_rules)
提前排序+关闭GroupBy内排序
如果你的最终结果不需要严格排序,先对GroupBy键排序,然后在GroupBy时加sort=False——Pandas对有序数据的GroupBy有优化,能避免重复排序的开销:
# 先按GroupBy键排序 df = df.sort_values(['ID1', 'ID2', 'typeid', 'date_', 'cat1', 'cat2', 'cat3']) # 关闭GroupBy内的自动排序 result = df.groupby(..., sort=False, observed=True).agg(agg_rules)
明确指定聚合规则,别让Pandas瞎猜
不要用groupby().sum()这种全列聚合,而是用字典形式明确指定要聚合的列和方式,避免Pandas对所有列做不必要的计算:
# 只聚合你需要的列,减少计算量 agg_rules = { 'sales': 'sum', 'user_count': 'mean', 'order_id': 'nunique' } result = df.groupby(..., observed=True).agg(agg_rules)
3. 换用并行计算引擎,榨干工作站性能
单线程的Pandas在千万级数据面前确实乏力,试试这些和Pandas语法兼容的并行引擎:
Dask Pandas——零学习成本的并行工具
Dask会把数据分成小块,利用多CPU核心并行计算,语法和Pandas几乎完全一致,适合你的大内存工作站:
import dask.dataframe as dd # 读取数据时直接指定高效类型,避免后续转换 ddf = dd.read_csv( 'your_data.csv', dtype={ 'ID1': 'int32', 'ID2': 'int32', 'typeid': 'category', 'cat1': 'category', 'cat2': 'category', 'cat3': 'category' }, parse_dates=['date_'] ) # 执行GroupBy(和Pandas语法一样) result_ddf = ddf.groupby( ['ID1', 'ID2', 'typeid', 'date_', 'cat1', 'cat2', 'cat3'], observed=True ).agg(agg_rules) # 触发实际计算,得到Pandas DataFrame result = result_ddf.compute()
Dask的并行计算能把GroupBy时间压缩到原来的几分之一,尤其是当你的工作站有多核心CPU时,效果更明显。
PySpark——如果数据接近内存极限
如果3000万条数据加上聚合后内存压力大,PySpark可以用磁盘缓存+分布式计算,不过学习成本比Dask高一点,但性能也很强。如果Dask能解决问题,优先选Dask,毕竟和Pandas的语法更贴近。
4. 删掉无关列,减少计算负担
在GroupBy之前,先把不需要的列全部删掉!比如你只需要聚合3-5个数值列,那把其他无关列drop掉,既能减少内存占用,又能降低计算量:
# 只保留GroupBy键和需要聚合的列 keep_cols = ['ID1', 'ID2', 'typeid', 'date_', 'cat1', 'cat2', 'cat3'] + ['agg_col1', 'agg_col2', 'agg_col3'] df = df[keep_cols]
5. 对比R的实现,排查逻辑差异
很多时候R的快是因为用了data.table而不是base R的group_by,data.table的GroupBy是高度优化的。你可以检查下自己的Pandas代码是不是做了额外操作,比如用了groupby().apply()(apply是Pandas里最慢的操作之一,能不用就不用),或者保留了不必要的多级索引,这些都会拖慢速度。
按上面的步骤优化下来,应该能把Pandas的GroupBy速度提升到和R差不多的水平,先从数据类型和GroupBy参数调整入手,这是最容易见效的!
内容的提问来源于stack exchange,提问作者skibee

