Polars是基于Rust实现的高性能Python数据处理库,在大规模结构化数据的处理场景中具备显著的执行效率优势。在许多实际业务中,数据分析人员经常需要先按某个字段进行分组,然后在每个分组内部筛选出满足特定条件的记录,并进一步从这些记录中提取分数最高的一条所对应的目标值。本文以分组字段、匹配标志、分数和目标值四类列为例,介绍如何在Polars中高效完成这一操作。
示例数据与需求定义
为了便于说明,构造一个包含四个字段的DataFrame。group_id为分组字段,表示记录所属的组;match_flag为匹配标志,取值为1或0,1表示该记录满足后续筛选条件;score为数值型分数字段,用于在组内比较高低;target_value为目标值字段,是我们最终想要提取的内容。示例数据中一共包含三个分组,A组、B组和C组各有若干条记录。
import polars as pl
# 构造分组、匹配标志、分数与目标值示例数据
data = {
'group_id': ['A', 'A', 'A', 'B', 'B', 'B', 'C', 'C'],
'match_flag': [1, 0, 1, 1, 1, 0, 0, 1],
'score': [85, 90, 78, 92, 88, 76, 89, 95],
'target_value': ['val_a1', 'val_a2', 'val_a3', 'val_b1', 'val_b2', 'val_b3', 'val_c1', 'val_c2']
}
df = pl.DataFrame(data)
print('原始数据:')
print(df)
从原始数据可以看到,每个分组内既有匹配记录也有未匹配记录。例如A组包含三条记录,其中第一条和第三条的match_flag为1,第二条的match_flag为0。后续计算需要忽略match_flag为0的记录,只在匹配记录中按照score从高到低排序,并取得最高分所对应的target_value。
两种实现方式对比
在Polars中完成“分组后组内筛选并提取最高分对应值”的需求,通常有两种实现思路。第一种是在group_by之后的聚合上下文中,直接对目标列应用filter方法,先保留组内匹配记录,再根据分数字段排序并取出第一个值;第二种是先从整个DataFrame中筛选出所有match_flag为1的记录,然后将筛选结果按group_id分组,再对每个组取最高分对应的目标值。两种方式都能得到相同的结果,但在数据处理流程和性能上存在一定差异。
方法一:在group_by聚合中先筛选再排序取值
方法一将筛选逻辑放在聚合表达式内部。代码首先调用group_by('group_id')建立分组上下文,随后在agg中对target_value列调用filter。该filter接收一个布尔表达式pl.col('match_flag') == 1,在聚合过程中只会保留当前组内满足该条件的target_value元素,未匹配记录对应的目标值不会被纳入后续计算。接着对保留下来的目标值按score列进行降序排序,最后使用first取得排序后的第一个目标值,也就是最高分对应的target_value。
# 方法一:group_by后在聚合内部筛选并取组内最高分对应值
result = (
df.group_by('group_id')
.agg(
pl.col('target_value')
.filter(pl.col('match_flag') == 1)
.sort_by(pl.col('score'), descending=True)
.first()
.alias('max_score_target')
)
)
print('方法一结果:')
print(result)
这种写法的优点是把筛选、排序和取值集中在同一个聚合表达式中,逻辑边界清晰,便于在分组统计时一次性处理多个字段。需要注意,sort_by中的pl.col('score')与target_value列共享相同的分组上下文,因此排序时能够正确使用与目标值同一行的分数字段。
方法二:先全局筛选匹配记录再分组取值
方法二把筛选动作前置到分组之前。首先使用df.filter(pl.col('match_flag') == 1)过滤整个DataFrame,只保留所有组内match_flag为1的记录。经过这一步后,每一行都已经满足匹配条件,不再需要在聚合表达式中额外进行筛选。接着对过滤后的DataFrame按group_id分组,在agg中直接对target_value按score降序排序并取first。
# 方法二:先全局筛选匹配项,再分组提取最高分对应值
filtered_df = df.filter(pl.col('match_flag') == 1)
result2 = (
filtered_df.group_by('group_id')
.agg(
pl.col('target_value')
.sort_by(pl.col('score'), descending=True)
.first()
.alias('max_score_target')
)
)
print('方法二结果:')
print(result2)
方法二的思路更加简单直接,因为分组之前的数据量已经通过全局筛选得到缩减。尤其当满足匹配条件的记录只占原始数据很小比例时,先进行全局过滤能够显著减少后续分组聚合需要处理的数据行数,从而带来性能收益。
结果验证与性能优化建议
两种方法输出的结果保持一致,最终DataFrame包含三个分组对应的最高分目标值。对于示例数据而言,A组中match_flag为1的记录有两条,分数分别为85和78,其中85分更高,因此提取的值为val_a1;B组中匹配记录有两条,分数分别为92和88,最高分92对应val_b1;C组中只有一条匹配记录,分数为95,因此对应值为val_c2。
通过逐组核验可以看出,分组内先筛选再根据分数排序取首项的逻辑能够准确找到每组的最高分记录。若需要将最高分数值本身一并输出,可以在同一个agg调用中添加第二个聚合表达式。例如对score列先使用filter保留匹配记录,再调用max方法计算最大值,并命名为max_score。这样可以同时得到每组最高分对应的目标值和最高分本身,便于后续分析或报表展示。
# 在提取目标值的同时,将组内匹配记录的最高分也一并输出
result3 = (
df.group_by('group_id')
.agg(
pl.col('target_value')
.filter(pl.col('match_flag') == 1)
.sort_by(pl.col('score'), descending=True)
.first()
.alias('max_score_target'),
pl.col('score')
.filter(pl.col('match_flag') == 1)
.max()
.alias('max_score')
)
)
print('带最高分的结果:')
print(result3)
从性能角度看,两种方法的选择主要取决于数据分布。方法一在分组后再筛选,聚合上下文中的表达式更丰富,适合需要同时输出多个分组统计量的场景;方法二先全局筛选可以减少进入分组阶段的数据规模,当匹配项占比很低时优势明显。当数据量达到百万行以上时,建议在本地或测试环境中对两种写法进行基准测试,结合实际数据规模、内存限制和匹配比例选择最优方案。
还需要注意,Polars表达式的执行顺序会在内部进行优化,因此代码层面的调用顺序不一定等同于实际计算顺序。但无论采用哪种写法,只要表达式语义正确,最终结果都应与业务预期一致。对于更复杂的需求,例如按多个字段排序、提取第二名或返回完整行记录,可以基于sort_by、first、last以及filter等表达式组合扩展。
总结而言,在Polars中按组筛选匹配项并提取最高分对应值,可以选择“分组内筛选排序取首项”或“先全局筛选再分组取值”两种方法。前者在聚合表达式中完成全部逻辑,后者通过提前过滤缩减数据量。两者结果完全一致,实际使用时可以结合数据分布和性能测试进行选择。掌握这些表达式组合方式后,处理类似的分组统计与条件提取需求将更加高效。
PolarsDataFramegroup_byfiltermax_score修改时间:2026-07-14 13:45:40