
本文介绍如何在 Polars 中为每个以 "Life" 开头、以 "Death" 结尾的连续区间,提取指定列(如 Column A)的最大值,并仅将该值填充至对应 "Life" 行的新增列中,其余行置为 null。
本文介绍如何在 polars 中为每个以 "life" 开头、以 "death" 结尾的连续区间,提取指定列(如 `column a`)的最大值,并仅将该值填充至对应 "life" 行的新增列中,其余行置为 null。
在 Polars 中实现“区间内最大值映射到起始标记行”这一需求,关键在于对非连续逻辑区间进行唯一分组标识,而非依赖物理行号或循环遍历。原始数据中,“Life”表示一个新区间的开始,“Death”表示当前区间的结束;二者之间(含)的所有行构成一个有效计算范围。由于数据中存在 null 值且区间不等长,直接切片不可行,需通过累积逻辑构造稳定的 group_id。
核心思路:用累积和构建区间组 ID
我们利用布尔序列的 cum_sum() 生成单调递增的计数器:
- (pl.col("Column B") == "Life").cum_sum():每遇到一个 "Life",计数器 +1 → 标记每个区间的起点序号;
- (pl.col("Column B") == "Death").cum_sum():每遇到一个 "Death",计数器 +1 → 标记每个区间的终点序号。
二者相加后 forward_fill(),可使每个 "Life" 到其后首个 "Death" 之间的所有行(含两端)获得相同的基础组 ID(即 group_id_1)。但问题在于:"Death" 行本身属于当前区间的结尾,不应参与后续区间的分组——它应归属前一个 "Life" 区间。因此需对 "Death" 行的组 ID 进行 1 行前向偏移(.shift()),得到修正后的 group_id_2。
以下是完整、可运行的 Polars 实现:
import polars as pl
df = pl.DataFrame({
"Column A": [2, 3, 1, 4, 1, 3, 3, 2, 1, 0],
"Column B": ["Life", None, None, None, "Death", None, "Life", None, None, "Death"]
})
# 定义关键布尔条件
is_life = pl.col("Column B") == "Life"
is_death = pl.col("Column B") == "Death"
# 构建鲁棒的 group_id:确保每个 Life→Death 区间拥有唯一 ID,且 Death 行归属前一区间
group_id = (
(is_life.cum_sum() + is_death.cum_sum())
.forward_fill()
.over(pl.lit(0)) # 确保 forward_fill 在全表生效(Polars ≥ 0.20.20 推荐显式 over)
.fill_null(0)
)
group_id = pl.when(is_death).then(group_id.shift()).otherwise(group_id)
# 计算每组 Column A 的最大值,并仅在 Life 行赋值
result = df.with_columns(
pl.when(is_life)
.then(pl.col("Column A").max().over(group_id))
.alias("Column C")
)
print(result)✅ 输出结果与预期一致:
shape: (10, 3) ┌──────────┬──────────┬──────────┐ │ Column A ┆ Column B ┆ Column C │ │ --- ┆ --- ┆ --- │ │ i64 ┆ str ┆ i64 │ ╞══════════╪══════════╪══════════╡ │ 2 ┆ Life ┆ 4 │ │ 3 ┆ null ┆ null │ │ 1 ┆ null ┆ null │ │ 4 ┆ null ┆ null │ │ 1 ┆ Death ┆ null │ │ 3 ┆ null ┆ null │ │ 3 ┆ Life ┆ 3 │ │ 2 ┆ null ┆ null │ │ 1 ┆ null ┆ null │ │ 0 ┆ Death ┆ null │ └──────────┴──────────┴──────────┘
注意事项与最佳实践
- Null 安全性:forward_fill() 默认跳过 null,但建议在生产环境中显式 .fill_null(0) 防止首行为 null 导致传播;
- 版本兼容性:forward_fill() 在较新 Polars(≥ 0.20.0)中默认全局填充;若使用旧版本,可改用 .over(pl.lit(0)) 显式指定窗口;
- 性能优势:全程基于惰性计算与向量化操作,避免 Python 循环或 apply(),适用于百万级数据;
- 扩展性:该模式可轻松适配其他边界标识(如 "Start"/"End")、多列聚合(.max().min().mean() 组合),或嵌套区间(通过嵌套 cum_sum() 实现层级 ID);
- 调试技巧:临时保留中间列(如 start, end, group_id)有助于验证分组逻辑是否符合业务语义。
掌握此类基于累积逻辑的分组建模方法,是高效处理时序、会话、状态区间类问题的 Polars 核心技能之一。










