如何利用groupby transform高效计算2018-2019年各分组的平均比率(适用于超大型数据集)
嘿,你的需求我完全get到了——处理200M行的超大型数据集,还想尽量用transform或者更高效的方式替代嵌套循环,甚至希望有简洁的一行式写法对吧?先不说别的,你原来的循环+transform+嵌套loc的方法虽然能跑,但对于千万级别的数据来说肯定会慢到离谱,因为每次lambda里的索引操作都是重复计算,嵌套循环还会进一步放大时间成本。
下面给你几个更高效的优化方案,从适合大数据的最优实践,到你想要的transform优化版,再到简洁的一行式,按需选择:
方案一:聚合+合并(大数据集最优解)
这是pandas处理这类分组统计需求的常规操作——先一次性聚合出所有需要的统计量,再合并回原数据。相比transform循环,它的效率提升几个数量级,因为聚合是批量计算,避免了对每一行重复执行操作:
# 1. 筛选2018-2019年数据,按group+year聚合foo/bar的总和 agg_data = df[df['year'].isin([2018, 2019])].groupby(['group', 'year'])[['foo', 'bar']].sum() # 2. 计算每个分组每年的比率 agg_data['rate'] = agg_data['foo'] / agg_data['bar'] # 3. 转成宽格式(把年份从行转成列),方便后续合并 wide_agg = agg_data.unstack('year').swaplevel(axis=1).sort_index(axis=1) # 重命名列,让命名更直观(比如foo_2018、rate_2019) wide_agg.columns = [f'{col}_{year}' for year, col in wide_agg.columns] # 4. 计算每个分组两年的平均比率 wide_agg['2018_2019_avg_rate'] = wide_agg[['rate_2018', 'rate_2019']].mean(axis=1) # 5. 合并回原数据,每个group的所有行都会带上对应的统计值 df = df.merge(wide_agg, on='group', how='left')
这个方案全程没有循环,所有操作都是pandas的向量化原生操作,对于200M行的数据集,性能会比你原来的方法好太多。
方案二:优化后的transform写法
如果你坚持想用transform实现,我把你原来的代码做了简化,去掉了lambda里嵌套loc的低效操作,直接在分组后的列上过滤求和:
# 批量处理foo和bar的年度总和 for col in ['foo', 'bar']: for year in [2018, 2019]: df[f'{col}_{year}_total'] = df.groupby('group')[col].transform( lambda x: x[df.loc[x.index, 'year'] == year].sum() ) # 计算年度比率和两年平均比率 for year in [2018, 2019]: df[f'rate_{year}'] = df[f'foo_{year}_total'] / df[f'bar_{year}_total'] df['2018_2019_avg_rate'] = df[['rate_2018', 'rate_2019']].mean(axis=1)
这个写法相比你原来的版本,去掉了where和dropna的额外开销,直接对分组后的目标列过滤求和,效率会有明显提升。
方案三:一行式(满足简洁需求)
如果你追求极致简洁,这里把方案一的步骤用链式调用串成了一行,结果和方案一完全一致,适合喜欢简洁代码的场景:
df = df.merge( df[df['year'].isin([2018,2019])].groupby(['group','year'])[['foo','bar']].sum() .assign(rate=lambda x: x['foo']/x['bar']) .unstack('year').swaplevel(axis=1).sort_index(axis=1) .pipe(lambda x: x.set_axis([f'{c}_{y}' for y,c in x.columns], axis=1)) .assign(2018_2019_avg_rate=lambda x: x[['rate_2018','rate_2019']].mean(axis=1)), on='group', how='left' )
补充:你原来的方法效率低的原因
你原来的transform写法里,lambda x: df.loc[x.where(x==y).dropna().index, col].sum()存在几个关键问题:
- 对每个分组的每一行都重复执行
loc索引,200M行的话会产生巨量的冗余计算 - 嵌套的列循环+年份循环进一步放大了时间成本
where和dropna的操作完全可以通过直接过滤分组后的列来避免
对于200M行的超大型数据集,我强烈推荐方案一的聚合+合并法,这是pandas处理这类分组统计需求的最优实践,既能保证效率,又能保证代码的可读性。
备注:内容来源于stack exchange,提问作者amance

