You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Polars中实现最优雅的连续计数(Streak Counter)

在Polars中实现连续计数的优雅方案

一、连续相同值的计数(对应你的示例需求)

你的示例需求是按person_id分组、按order_id排序,统计每个用户连续购买相同item的次数,当item变化时重置计数。Polars可以用窗口函数+运行长度编码(RLE)分组的方式实现,代码比PySpark更紧凑:

import polars as pl

# 构造示例数据
df = pl.DataFrame({
    "person_id": ["alice", "bob", "alice", "bob", "alice", "bob", "alice", "bob"],
    "order_id": [1, 2, 3, 4, 5, 6, 7, 8],
    "item": ["A", "B", "B", "B", "A", "B", "A", "A"]
})

# 核心实现
result = (
    df
    .sort("person_id", "order_id")  # 先按用户和订单号排序,保证顺序正确
    .with_columns(
        # 生成streak分组ID:当当前item与前一个不同时,计数+1
        streak_id=pl.col("item").ne(pl.col("item").shift()).over("person_id").cumsum()
    )
    .with_columns(
        # 在每个用户+streak分组内,按顺序计数
        streak_counter=pl.col("order_id").cum_count().over(["person_id", "streak_id"])
    )
    .sort("order_id")  # 恢复原订单顺序
    .drop("streak_id")  # 移除中间分组ID
)

print(result)

输出结果完全匹配你的示例:

shape: (8, 4)
┌───────────┬──────────┬──────┬────────────────┐
│ person_id ┆ order_id ┆ item ┆ streak_counter │
│ ---       ┆ ---      ┆ ---  ┆ ---            │
│ str       ┆ i64      ┆ str  ┆ u32            │
╞═══════════╪══════════╪══════╪════════════════╡
│ alice     ┆ 1        ┆ A    ┆ 1              │
│ bob       ┆ 2        ┆ B    ┆ 1              │
│ alice     ┆ 3        ┆ B    ┆ 1              │
│ bob       ┆ 4        ┆ B    ┆ 2              │
│ alice     ┆ 5        ┆ A    ┆ 1              │
│ bob       ┆ 6        ┆ B    ┆ 3              │
│ alice     ┆ 7        ┆ A    ┆ 2              │
│ bob       ┆ 8        ┆ A    ┆ 1              │
└───────────┴──────────┴──────┴────────────────┘

二、满足指定条件的连续行数统计

如果需要统计满足自定义条件的连续行数(比如连续购买item='A'的次数,不满足时重置),只需调整条件判断逻辑即可:

# 示例:按person_id分组,统计连续购买item='A'的次数,非A时计数重置为0
result_cond = (
    df
    .sort("person_id", "order_id")
    .with_columns(
        # 标记是否满足条件
        is_target=pl.col("item") == "A",
        # 生成streak分组ID:当当前状态与前一个不同时,分组ID+1
        streak_id=pl.col("is_target").ne(pl.col("is_target").shift()).over("person_id").cumsum()
    )
    .with_columns(
        # 仅在满足条件的分组内计数,否则设为0
        streak_counter=pl.when(pl.col("is_target"))
                        .then(pl.col("order_id").cum_count().over(["person_id", "streak_id"]))
                        .otherwise(0)
    )
    .sort("order_id")
    .drop("is_target", "streak_id")
)

print(result_cond)

输出:

shape: (8, 4)
┌───────────┬──────────┬──────┬────────────────┐
│ person_id ┆ order_id ┆ item ┆ streak_counter │
│ ---       ┆ ---      ┆ ---  ┆ ---            │
│ str       ┆ i64      ┆ str  ┆ u32            │
╞═══════════╪══════════╪══════╪════════════════╡
│ alice     ┆ 1        ┆ A    ┆ 1              │
│ bob       ┆ 2        ┆ B    ┆ 0              │
│ alice     ┆ 3        ┆ B    ┆ 0              │
│ bob       ┆ 4        ┆ B    ┆ 0              │
│ alice     ┆ 5        ┆ A    ┆ 1              │
│ bob       ┆ 6        ┆ B    ┆ 0              │
│ alice     ┆ 7        ┆ A    ┆ 2              │
│ bob       ┆ 8        ┆ A    ┆ 1              │
└───────────┴──────────┴──────┴────────────────┘

与PySpark实现的对比

Polars的实现无需显式定义多个WindowSpec,通过over()直接指定分区范围,结合cumsum()生成分组ID、cum_count()完成连续计数,代码更简洁直观,同时保持了逻辑的清晰性——核心思路和PySpark一致(标记重置点→生成分组→分组内计数),但语法更紧凑。

内容的提问来源于stack exchange,提问作者David

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 08:39:55