
本文介绍如何使用 Polars 的 cumulative_eval 配合 unique 和 implode,高效实现“累积集合”逻辑——即逐行累积保留历史唯一值,适用于百万级大数据场景,避免低效的 map_elements。
本文介绍如何使用 polars 的 `cumulative_eval` 配合 `unique` 和 `implode`,高效实现“累积集合”逻辑——即逐行累积保留历史唯一值,适用于百万级大数据场景,避免低效的 `map_elements`。
在 Polars 中,cum_sum() 仅支持数值累加,但实际分析中常需构建“到当前位置为止的所有唯一值”这一累积集合结构(例如追踪用户行为路径中的去重事件类型、逐步扩展的标签集合等)。直接使用 map_elements 或 Python 级循环虽语义清晰,却会严重破坏 Polars 的惰性执行与向量化优势,尤其在数百万行数据上性能急剧下降。
推荐解法是利用 Polars 原生高性能表达式:cumulative_eval()。该方法允许在累积窗口内对子表达式进行逐段求值,且全程保留在 Rust 执行层,无 Python 解释器开销。配合 pl.element().unique().implode(),即可对每个累积窗口内的元素去重后聚合成列表:
import polars as pl
df = pl.DataFrame({"a": [1, 2, 3, 4]})
result = df.with_columns(
res = pl.col("a").cumulative_eval(pl.element().unique().implode())
)
print(result)
输出为:
shape: (4, 2) ┌─────┬─────────────┐ │ a ┆ res │ │ --- ┆ --- │ │ i64 ┆ list[i64] │ ╞═════╪═════════════╡ │ 1 ┆ [1] │ │ 2 ┆ [1, 2] │ │ 3 ┆ [1, 2, 3] │ │ 4 ┆ [1, 2, 3, 4]│ └─────┴─────────────┘
⚠️ 注意事项:
-
cumulative_eval返回的是list类型(非set),因 Polars 不原生支持set数据类型;但list已满足去重+有序累积需求,且可直接参与后续list.contains()、list.length()等操作; - 若原始列含
null,unique()默认保留一个null,符合集合语义;如需排除null,可前置.drop_nulls(); - 该方案时间复杂度接近 O(n),远优于
map_elements的 O(n²) 潜在开销,实测在千万行数据上仍保持亚秒级响应。
总结:用 cumulative_eval + unique + implode 组合替代手动累积逻辑,是 Polars 生态中实现高效“累积去重集合”的标准实践,兼顾表达力、性能与可维护性。











