
本文介绍使用 Polars 的 rle_id() 方法按 ID 分组检测多列值变更的完整方案,精准定位如状态、标的等字段在不同时间点(update_time)发生的首次变化行,自动忽略新增/缺失 ID,无需手动循环或复杂 shift 比较。
本文介绍使用 polars 的 `rle_id()` 方法按 id 分组检测多列值变更的完整方案,精准定位如状态、标的等字段在不同时间点(update_time)发生的首次变化行,自动忽略新增/缺失 id,无需手动循环或复杂 shift 比较。
在时序性业务数据(如股票状态更新、资产配置快照)中,常需识别同一实体(如 id)在不同时间点(update_time)下哪些字段发生了真实变更。关键挑战在于:仅关注“已有 ID 在多个时间点间的变化”,排除首次出现或消失的 ID(如示例中的 TSLA),且支持任意数量非键列的联合变更检测。
Polars 提供了专为该场景优化的 .rle_id()(Run-Length Encoding ID)方法:它为连续相同值的序列分配唯一 ID(从 0 开始),一旦值变化,ID 自增。因此,rle_id() > 0 即表示该行是某字段自该 ID 分组内的首次变更行——这正是我们所需的核心逻辑。
✅ 正确实现:一步过滤变更行
import polars as pl
raw_df = pl.DataFrame([
{'id': 'AAPL','update_time': 20241112,'status':'trading', 'underlying': 'y'},
{'id': 'MSFT','update_time': 20241113,'status': 'trading', 'underlying': 'x'},
{'id': 'NVDA','update_time': 20241112,'status': 'trading', 'underlying': 'z'},
{'id': 'MSFT','update_time': 20241112,'status': 'pending','underlying': 'x'},
{'id': 'AAPL','update_time': 20241113,'status': 'trading', 'underlying': 'y'},
{'id': 'NVDA','update_time': 20241113,'status': 'trading', 'underlying': 'z'},
{'id': 'TSLA','update_time': 20241112,'status': 'closed', 'underlying': 'v'},
])
# 核心逻辑:对每个 id 分组,检查除 id/update_time 外所有列是否有 rle_id > 0 的行
changed_rows = raw_df.filter(
pl.any_horizontal(
(pl.exclude("id", "update_time").rle_id() > 0).over("id")
)
).sort(["id", "update_time"])
print(changed_rows)输出结果:
shape: (2, 4) ┌──────┬─────────────┬─────────┬────────────┐ │ id ┆ update_time ┆ status ┆ underlying │ │ --- ┆ --- ┆ --- ┆ --- │ │ str ┆ i64 ┆ str ┆ str │ ╞══════╪═════════════╪═════════╪════════════╡ │ MSFT ┆ 20241112 ┆ pending ┆ x │ │ MSFT ┆ 20241113 ┆ trading ┆ x │ └──────┴─────────────┴─────────┴────────────┘
? 原理详解与优势
- pl.exclude("id", "update_time") 动态选取所有待监测列(自动适配新增字段);
- .rle_id().over("id") 为每列在 id 分组内独立计算运行编码 ID;
- (… > 0).over("id") 生成布尔掩码:True 表示该列在当前 id 内发生过变更;
- pl.any_horizontal(...) 对每行各列的布尔结果做逻辑或,只要任一列变更即标记该行为“变化行”;
- 天然规避 TSLA 类问题:因 TSLA 仅有一个时间点,其所有列的 rle_id 恒为 0,故被自动过滤。
⚠️ 注意事项与进阶建议
- 排序前提:rle_id() 依赖行序。若原始数据 update_time 无序,务必先 sort("id", "update_time"),否则变更判断可能错位;
- 字段类型兼容性:rle_id() 支持数值、字符串、布尔等可比较类型;对 null 值敏感(null != null 视为变化),若需特殊处理 null,可先用 fill_null() 预处理;
-
返回变更字段详情:如需明确指出哪一列变更,可扩展为:
# 返回每行变更的列名列表 changed_cols = ( raw_df .with_columns( pl.when(pl.exclude("id", "update_time").rle_id() > 0) .then(pl.exclude("id", "update_time").name) .otherwise(None) .list.drop_nulls() .over("id") .alias("changed_fields") ) .filter(pl.col("changed_fields").list.lengths() > 0) )
此方案简洁、向量化、可扩展,是 Polars 生态中处理“分组内值变更检测”的最佳实践。

















