
本文介绍如何在 Polars 中根据非空 call_id 值自动匹配其所在行与 id1/id2/id3 中任一列相等的列,并以此列为分组依据,对 call_id 执行前向传播式 backfill(即用后续首个非空值向下填充)。
本文介绍如何在 polars 中根据非空 `call_id` 值自动匹配其所在行与 `id1`/`id2`/`id3` 中任一列相等的列,并以此列为分组依据,对 `call_id` 执行前向传播式 backfill(即用后续首个非空值向下填充)。
在 Polars 中实现“条件式 backfill over”——即动态选择分组列(如 id1、id2 或 id3),需结合 pl.when().then() 的逻辑判断、正则列选择 ^id.*$、逐列比较与 backward_fill().over() 的组合使用。核心思路是:*找出 call_id 非空时与之相等的 `id列,并将该列作为backfill的分组键,从而让空值被同一组内最近的下方非空call_id` 填充。**
以下为推荐实现方案(兼容 Polars ≥ 0.20.0):
import polars as pl
# 示例数据构造(与问题一致)
df = pl.DataFrame({
"id1": [1, 1, 1, 2, 2, 2, 3, 3, 3, 3],
"id2": [4, 5, 5, 5, 6, 7, 7, 7, 7, 8],
"id3": [9, 9, 9, 9, 9, 10, 11, 12, 13, 13],
"call_id": [None, None, None, None, 2, None, None, None, 7, None]
})
# ✅ 关键逻辑:对每个 id* 列,判断其是否等于 call_id(非空时),并用 backward_fill().over() 推动匹配标识
# 然后 coalesce 多个候选列,取首个有效匹配(即实际起作用的分组列名)
result = df.with_columns(
call_id=pl.coalesce(
[
pl.when(
(pl.col(col) == pl.col("call_id")).backward_fill().over(col)
).then(pl.col(col))
for col in ["id1", "id2", "id3"]
]
)
)
print(result)? 原理详解:
(pl.col(col) == pl.col("call_id"))生成布尔序列,仅当某id*列值恰好等于当前行call_id(且call_id非空)时为True;.backward_fill().over(col)将该布尔序列按col分组,并在每组内从下往上填充True—— 这样,所有位于首个非空call_id下方(含该行)且属于同一col组的行都会标记为True;.then(pl.col(col))将这些标记为True的行,赋值为其对应id*列的值(即call_id应传播的目标值);pl.coalesce(...)按顺序尝试id1→id2→id3,返回第一个非空结果,确保优先级可控。
⚠️ 注意事项:
- 此方法假设
call_id的非空值*必然等于其所在行的某一个 `id列值**(如示例中call_id=2出现在id1==2的行,call_id=7出现在id2==7的行)。若存在不匹配情况,对应行call_id将保持null`; -
backward_fill().over(col)是关键:它不是直接 backfillcall_id,而是先识别“哪些行应继承哪个id*列的值”,再通过coalesce统一映射; - 若需严格限定仅使用
id1/id2/id3三列(避免正则误匹配),请显式列出列名(如上代码),而非依赖^id.*$正则(后者在列名含其他id前缀时可能出错); - 性能友好:全程基于表达式引擎,无 Python 循环或
apply,适用于大数据量场景。
✅ 最终输出与预期完全一致:call_id 空值被正确回填为所属 id* 组内首个出现的非空 call_id 值(例如第 4 行 id1=2 后续两行均填 2;第 9 行 id2=7 导致上方连续三行填 7),且末尾孤立空值(第 10 行)保持 null。

















