
本文介绍如何使用 polars 对分组数据进行条件筛选,精准定位每组中 `seq_grp` 与 `match` 不匹配但 `score` 最高的记录,并提取其 `match` 值与分数,适用于推荐系统、异常检测等需“次优匹配”分析的场景。
在 Polars 中处理分组后需基于嵌套列表(如 match: list[str] 和 score: list[i64])进行索引对齐操作时,直接使用 .list.get() 易出错——因为 match 和 score 列虽同组聚合,但顺序未必严格对应原始行序(尤其经 filter 或 agg 后)。更健壮且高效的方式是:避免提前展开为列表,而是在分组前用布尔条件过滤 + arg_max() 定位索引,再通过 .get() 提取目标字段值。
以下是以原始数据为例的完整实现流程:
import polars as pl
df = pl.DataFrame(
{
"seq": "foo bar bar duk duk baz baz baz zed".split(),
"seq_grp": "aa bb bb dd dd cc cc cc zz".split(),
"match": "aa cc bb dd dd ff cc cc yy".split(),
"score": [10, 8, 20, 8, 7, 5, 6, 4, 6],
}
)
# ✅ 正确做法:找出每组中 match != seq_grp 的最高分记录
result = (
df.lazy()
.filter(pl.col("match") != pl.col("seq_grp")) # 先排除完全匹配项
.group_by(["seq", "seq_grp"], maintain_order=True)
.agg(
best_non_match=pl.col("match").get(pl.col("score").arg_max()),
top_score=pl.col("score").max(),
# 可选:同时获取原始索引或其它字段
# original_idx=pl.arg_max("score"),
)
.collect()
)
print(result)输出结果为:
shape: (3, 4) ┌─────┬─────────┬────────────────┬───────────┐ │ seq ┆ seq_grp ┆ best_non_match ┆ top_score │ │ --- ┆ --- ┆ --- ┆ --- │ │ str ┆ str ┆ str ┆ i64 │ ╞═════╪═════════╪════════════════╪═══════════╡ │ bar ┆ bb ┆ cc ┆ 8 │ │ baz ┆ cc ┆ ff ┆ 5 │ │ zed ┆ zz ┆ yy ┆ 6 │ └─────┴─────────┴────────────────┴───────────┘
? 关键点解析:pl.col("score").arg_max() 返回该组内 score 最大值的相对索引位置(从 0 开始),安全且无需预知列表长度;.get(...) 直接作用于同组内的 match 列,确保索引对齐;使用 filter(...).group_by(...).agg(...) 链式操作,避免中间生成冗余 list 类型列,性能更优、语义更清晰;maintain_order=True 保证分组结果顺序与原始数据一致,便于调试与验证。
⚠️ 注意事项:
- 若某组内所有 match == seq_grp(即无非匹配项),该组将被 filter 完全剔除,不会出现在结果中。如需保留空值,可改用 when/then/otherwise 结合 list.arg_max 处理;
- arg_max() 在存在多个最大值时返回第一个出现位置,符合多数业务逻辑;若需随机或全部,应改用 list.eval().filter(...).first() 等方式;
- 所有操作均支持 lazy() 模式,建议大数据量时始终启用以获得查询优化与延迟执行优势。
总结:Polars 的表达能力核心在于「向量化索引+惰性计算」。面对分组内跨列取值问题,优先考虑 arg_max/arg_min + get 组合,而非手动展开列表——既简洁可靠,又贴近底层计算逻辑,是真正符合 Polars 设计哲学的最佳实践。










