
在 Polars 中对分组数据取每组 k=1 的最大值对应行字段时,直接组合 top_k_by("x", 1).over("A") 会报错;根本原因在于 top_k_by(..., k=1) 返回长度为 1 的 Series,无法自动广播匹配分组内行数,需显式调用 .first() 实现标量广播。
在 polars 中对分组数据取每组 `k=1` 的最大值对应行字段时,直接组合 `top_k_by("x", 1).over("a")` 会报错;根本原因在于 `top_k_by(..., k=1)` 返回长度为 1 的 series,无法自动广播匹配分组内行数,需显式调用 `.first()` 实现标量广播。
当你希望按列 A 分组,并在每组中找出列 x 最大值所对应的列 B 值(即“argmax + get”语义),直觉上会尝试使用 top_k_by 配合 over:
import polars as pl
df = pl.DataFrame({
'A': ['a0', 'a0', 'a1', 'a1'],
'B': ['b1', 'b2', 'b1', 'b2'],
'x': [0, 10, 5, 1]
})
# ❌ 错误写法:会触发 ComputeError
# df.with_columns(pl.col("B").top_k_by("x", 1).over("A").alias("y"))运行上述代码会抛出:
ComputeError: the length of the window expression did not match that of the group
该错误的本质是:top_k_by("x", 1) 对每个分组返回一个长度为 1 的 Series(例如 ["b2"]),而 over("A") 要求窗口表达式结果能逐行广播至该组所有行(如 a0 组有 2 行,就需要输出长度为 2 的结果)。但 Polars 默认不自动广播单元素 Series —— 它期待你明确告知:“这个结果应作为标量复用”。
✅ 正确解法是:先用 .first() 将 top_k_by 的结果转为标量(scalar),再由 over 自动完成广播:
result = df.with_columns(
pl.col("B").top_k_by("x", 1).first().over("A").alias("y")
)
print(result)输出:
shape: (4, 4) ┌─────┬─────┬─────┬─────┐ │ A ┆ B ┆ x ┆ y │ │ --- ┆ --- ┆ --- ┆ --- │ │ str ┆ str ┆ i64 ┆ str │ ╞═════╪═════╪═════╪═════╡ │ a0 ┆ b1 ┆ 0 ┆ b2 │ │ a0 ┆ b2 ┆ 10 ┆ b2 │ │ a1 ┆ b1 ┆ 5 ┆ b1 │ │ a1 ┆ b2 ┆ 1 ┆ b1 │ └─────┴─────┴─────┴─────┘
? 补充说明:
-
k=2时看似“能工作”,是因为top_k_by("x", 2)对两行组返回长度为 2 的 Series,恰好与组大小一致,从而隐式对齐;但这属于巧合,不可依赖。 - 更稳健的替代方案(推荐用于生产)是使用
arg_max+get组合:df.with_columns( pl.col("B").get(pl.col("x").arg_max()).over("A").alias("y") )语义更清晰,且无广播歧义。
⚠️ 注意事项:
- 所有返回非标量(即长度 > 1 或不确定)的聚合/筛选表达式,在
over中使用前,若目标是为每组填充同一值,都应考虑.first()(或.last()、.min()等标量聚合)。 - 若
k > 1且需保留多个值(如 top-3 的B列列表),则应配合list聚合:top_k_by("x", 3).over("A").list()。
总之,这不是 bug,而是 Polars 对表达式形状(shape)严格校验的设计体现 —— 显式优于隐式,.first() 是解决此类广播问题的标准范式。

















