如何在Polars中预计算高开销表达式(含group_by及通用场景)
Polars分组聚合高效复用高开销计算的优化方案
一、GroupBy场景的优化方案
1. 简化列表聚合写法(最直接的优化)
你的预计算思路是对的,但可以把冗余的.list.eval(pl.element().<aggfunc>()).arr.first()简化为Polars内置的列表聚合函数,可读性和效率都更好:
%%timeit ( df .group_by('column_0') .agg(expensive.alias('expensive')) # 仅计算一次高开销的expensive .select( 'column_0', pl.col('expensive').list.sum().alias('sum'), pl.col('expensive').list.median().alias('median'), *[pl.col('expensive').list.max().alias(f'max{x}') for x in range(10)] ) )
这种写法去掉了冗余的list.eval和arr.first(),直接用list.sum()/list.median()等方法,逻辑更直观,性能和你之前的预计算版本几乎一致。
2. 使用map_groups实现更清晰的逻辑
如果追求代码的可读性和逻辑连贯性,可以用map_groups,在每个分组的子DataFrame中仅计算一次expensive,然后直接执行各种聚合操作,完全避免列表操作:
%%timeit ( df .group_by('column_0') .map_groups(lambda group_df: ( group_df .with_columns(expensive) # 子DF内仅计算一次expensive .select( pl.col('column_0').first(), pl.col('expensive').sum().alias('sum'), pl.col('expensive').median().alias('median'), *[pl.col('expensive').max().alias(f'max{x}') for x in range(10)] ) )) )
这种写法的逻辑和传统的Pandas分组处理更接近,代码可读性拉满。需要注意的是,map_groups的性能略低于纯表达式写法,但对于大多数场景来说完全够用,且逻辑更清晰。
3. 进阶:用pl.struct封装计算(适合复杂场景)
如果你的高开销计算需要生成多个中间结果,还可以用pl.struct把它们封装起来,一次性计算后再拆分使用:
# 假设需要同时计算两个高开销指标 complex_expensive = pl.struct( e1=pl.col('column_1').cum_prod().ewm_std(span=10), e2=pl.col('column_2').cum_sum().ewm_mean(span=5) ).alias('complex_expensive') ( df .group_by('column_0') .agg(complex_expensive) .select( 'column_0', pl.col('complex_expensive').list.struct.field('e1').list.sum().alias('e1_sum'), pl.col('complex_expensive').list.struct.field('e2').list.median().alias('e2_median'), ) )
二、Select场景下的复用方案
如果不需要分组聚合,而是要在全局或窗口范围内复用高开销计算,可以通过以下方式实现:
1. 全局/逐行复用
直接在select中先计算一次高开销列,再基于它生成多个衍生列,最后可选择删除原列:
df_result = df.select( '*', expensive.alias('expensive'), # 基于expensive生成多个衍生列 pl.col('expensive').sum().alias('global_sum'), pl.col('expensive').rank().alias('rank'), pl.col('expensive').sqrt().alias('sqrt_expensive') ).drop('expensive') # 不需要保留中间列时删除
2. 窗口范围内复用
如果需要在窗口(如按column_0分区)内复用计算,可以结合over窗口函数,仅计算一次窗口内的expensive,再生成多个聚合结果:
df_result = df.select( '*', expensive.alias('expensive'), pl.col('expensive').sum().over('column_0').alias('sum_over_col0'), pl.col('expensive').median().over('column_0').alias('median_over_col0'), pl.col('expensive').max().over('column_0').alias('max_over_col0') ).drop('expensive')
性能对比总结
- 纯表达式列表聚合:性能最优,适合对效率要求极高的场景
map_groups:可读性最优,性能略逊但足够应对多数业务场景- 窗口函数方案:适合需要保留原行数据的场景,避免分组后再合并
内容的提问来源于stack exchange,提问作者iliya malecki
相关产品推荐
相关产品推荐

