
本文介绍使用 polars 实现按账户分组、根据正负号决定“填充”或“清空”操作的数值分配逻辑:对每个账户,将目标值(如支付额)逐行分配至账单记录,优先处理最早未满/非空行,支持部分填充与截断,确保每行结果在 [0, val] 范围内。
本文介绍使用 polars 实现按账户分组、根据正负号决定“填充”或“清空”操作的数值分配逻辑:对每个账户,将目标值(如支付额)逐行分配至账单记录,优先处理最早未满/非空行,支持部分填充与截断,确保每行结果在 [0, val] 范围内。
在财务系统、账务冲抵或批量结算等场景中,常需将一个汇总金额(如退款、补缴、调账)按顺序分配到多个明细记录上:若金额为正,表示“填充”(例如补录应收);若为负,则表示“清空”(例如冲销已收)。关键约束包括:
-
顺序敏感:必须从每组
acc中第一行开始处理(升序索引),不可跳过; -
容量限制:每行最多承载其
val字段所定义的上限; - 部分操作合法:允许某行仅被部分填充或部分清空;
-
边界安全:最终结果不能小于 0 或大于该行原始容量
val。
Polars 提供高效的窗口计算与向量化表达能力,非常适合此类分组累积分配任务。核心思路是:
-
计算账户级总可分配量:
total = sum(原sum列) + 新val(来自left表) -
模拟逐行已占用量:用
cum_sum().shift(1)计算前序行已分配总量(即“已用”); -
当前行应分配量 = total − 已用,再通过
.clip(0, "val")强制裁剪至合法区间[0, val]。
以下是完整可运行示例(基于问题中数据):
import polars as pl
# 支付指令:每个账户一个待分配值(正=填充,负=清空)
left = pl.DataFrame({
"acc": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
"val": [-100, -123, -75, -300, -77, -500, 111, 123, 300, 75]
})
# 账单明细:每行有容量(val)和当前已占(sum),按处理顺序排列
right = pl.DataFrame({
"acc": [1, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4, 5, 6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10],
"val": [100, 100, 100, 123, 150, 123, 70, 70, 150, 100, 150, 77, 500, 500, 500, 500, 100, 100, 100, 123, 123, 123, 100, 150, 200, 10, 10],
"sum": [100, 50, 0, 123, 0, 0, 70, 5, 150, 100, 150, 78, 500, 500, 500, 500, 10, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0]
})
# 关键逻辑:分组计算总分配量 & 逐行已用容量
total_expr = pl.col("sum").sum() + pl.col("val_new")
used_expr = pl.col("val").cum_sum().shift(1).fill_null(0)
result = (
right.join(left, on="acc", suffix="_new")
.with_columns(
# (可选)调试辅助列:展示每组总需求与累计已用
total_expr.over("acc").alias("total"),
used_expr.over("acc").alias("used"),
)
.with_columns(
# 核心分配公式:剩余可分配量 = total - 前序已用 → 截断至[0, val]
(total_expr - used_expr)
.clip(0, pl.col("val"))
.over("acc")
.alias("res")
)
.select(["acc", "val", "sum", "res"]) # 仅保留必要列
)
print(result)✅ 输出说明:
-
res列即为最终每行应更新的sum值(即新填充/清空后的余额); - 对于
acc=1(总需清空100):首行sum=100→res=50(清空50),次行sum=50→res=0(全清),第三行sum=0→res=0(无剩余可清); - 对于
acc=9(总需填充300):依次填满前两行(100+150),第三行仅填余下50,严格遵循顺序与容量约束。
⚠️ 注意事项:
- 输入
right表中同一acc的行必须按预期处理顺序排列(如时间先后、优先级高低),Polars 不自动排序,需提前sort(); - 若存在
val=0行,其res恒为 0(因.clip(0, 0)); - 当
total (如大额负值远超现有 <code>sum总和),clip(0, ...)会将其置为 0 —— 这符合“清空不能为负”的业务语义; - 如需保留原始
sum用于审计,建议在.select()前不丢弃该列。
该方案简洁、向量化、无循环,适用于百万级账单批量处理,是 Polars 在金融数据流中典型且高效的应用模式。

















