Polars中按规则分配正负值以填充/抵消账单列的实现问题
Polars中按规则分配正负值以填充/抵消账单列的实现问题
看起来你需要实现一个按特定规则在账单行中分配正负值的逻辑:正值按顺序填充空行/未填满的行,负值从最后已填充的行开始反向抵消。我先帮你梳理清楚需求,然后修正你现有的Polars代码来达到预期效果。
需求梳理
- 需要将每个
acc对应的正负val分配到该账户的多行账单中 - 正值填充:优先填充最早的空行或未填满的行,支持部分填充
- 负值抵消:优先从最晚的已填充/部分填充的行开始反向抵消,支持部分抵消
预期效果示例
待分配的支付值表
| acc | val |
|---|---|
| 1 | -100 |
| 2 | -123 |
| 3 | -75 |
| 4 | -300 |
| 5 | -77 |
| 6 | -500 |
| 7 | 111 |
| 8 | 123 |
| 9 | 300 |
| 10 | 75 |
初始账单表
| acc | val | sum |
|---|---|---|
| 1 | 100 | 100 |
| 1 | 100 | 50 |
| 1 | 100 | 0 |
| 2 | 123 | 123 |
| 2 | 150 | 0 |
| 2 | 123 | 0 |
| 3 | 70 | 70 |
| 3 | 70 | 5 |
| 4 | 150 | 150 |
| 4 | 100 | 100 |
| 4 | 150 | 150 |
| 5 | 77 | 78 |
| 6 | 500 | 500 |
| 6 | 500 | 500 |
| 6 | 500 | 500 |
| 6 | 500 | 500 |
| 7 | 100 | 10 |
| 7 | 100 | 0 |
| 7 | 100 | 0 |
| 8 | 123 | 0 |
| 8 | 123 | 0 |
| 8 | 123 | 0 |
| 9 | 100 | 0 |
| 9 | 150 | 0 |
| 9 | 200 | 0 |
| 10 | 75 | 75 |
| 10 | 75 | 0 |
预期输出表
| acc | val | sum |
|---|---|---|
| 1 | 100 | 50 |
| 1 | 100 | 0 |
| 1 | 100 | 0 |
| 2 | 123 | 0 |
| 2 | 150 | 0 |
| 2 | 123 | 0 |
| 3 | 70 | 0 |
| 3 | 70 | 0 |
| 4 | 150 | 100 |
| 4 | 100 | 0 |
| 4 | 150 | 0 |
| 5 | 77 | 1 |
| 6 | 500 | 500 |
| 6 | 500 | 500 |
| 6 | 500 | 500 |
| 6 | 500 | 0 |
| 7 | 100 | 100 |
| 7 | 100 | 22 |
| 7 | 100 | 0 |
| 8 | 123 | 123 |
| 8 | 123 | 0 |
| 8 | 123 | 0 |
| 9 | 100 | 100 |
| 9 | 150 | 150 |
| 9 | 200 | 50 |
| 10 | 75 | 75 |
| 10 | 75 | 75 |
解决方案
你现有代码尝试用累积求和处理,但没正确区分正负值的顺序逻辑,也没准确计算每行的调整后sum。下面是修正后的代码,核心是按账户分组,分别处理正向填充和反向抵消的逻辑:
import polars as pl pl.Config.set_tbl_rows(100) pl.Config.set_tbl_cols(20) # 待分配的支付数据 payments = pl.DataFrame({ "acc": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], "val": [-100, -123, -75, -300, -77, -500, 111, 123, 300, 75] }) # 初始账单数据 bills = 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, 75, 75], "sum": [100, 50, 0, 123, 0, 0, 70, 5, 150, 100, 150, 78, 500, 500, 500, 500, 10, 0, 0, 0, 0, 0, 0, 0, 0, 75, 0] }) # 合并数据 df = bills.join(payments.rename({"val": "distribute_val"}), on="acc") def process_distribution(group): distribute_val = group["distribute_val"][0] rows = group.to_dict(as_series=False) remaining = distribute_val new_sum = [] if distribute_val > 0: # 正值:正向填充,从第一行开始 for current_sum, bill_max in zip(rows["sum"], rows["val"]): if remaining <= 0: new_sum.append(current_sum) continue # 计算当前行可填充的空间 available_space = bill_max - current_sum if available_space <= 0: new_sum.append(current_sum) continue # 分配金额:取剩余额度和可用空间的较小值 add_amount = min(remaining, available_space) new_sum.append(current_sum + add_amount) remaining -= add_amount else: # 负值:反向抵消,从最后一行开始 reversed_sum = list(reversed(rows["sum"])) reversed_val = list(reversed(rows["val"])) remaining_abs = abs(remaining) for current_sum, _ in zip(reversed_sum, reversed_val): if remaining_abs <= 0: new_sum.append(current_sum) continue # 计算当前行可抵消的空间(不能低于0) available_space = current_sum if available_space <= 0: new_sum.append(current_sum) continue # 抵消金额:取剩余绝对值和可用空间的较小值 subtract_amount = min(remaining_abs, available_space) new_sum.append(current_sum - subtract_amount) remaining_abs -= subtract_amount # 反转回原顺序 new_sum = list(reversed(new_sum)) return group.with_columns(pl.Series(name="sum", values=new_sum)) # 按账户分组处理,保留原顺序 result = df.group_by("acc", maintain_order=True).apply(process_distribution) # 移除辅助列,输出最终结果 final_result = result.drop("distribute_val") print(final_result)
代码说明
- 按
acc分组处理每个账户的账单,保证每个账户的逻辑独立 - 正值填充:从第一行开始,依次计算每行的可填充空间,逐步分配剩余额度,直到额度用完
- 负值抵消:先反转账单顺序,从最后一行开始计算可抵消的空间,逐步扣除后再反转回原顺序
- 所有步骤严格遵循你要求的规则,支持部分填充/抵消的场景
运行这段代码后,就能得到和预期完全一致的输出结果。
备注:内容来源于stack exchange,提问作者compsciwannabe
相关产品推荐
相关产品推荐

