pandas如何通过滚动函数实现客户发票付款状态校验?
针对发票付款状态校验的pandas高效实现方案
你这类同分组内跨相邻行交叉字段对比的需求,完全不需要硬套rolling()实现,反而用向量化的分组移位、累计聚合的方法,性能会比自定义滚动函数高几个量级,也是pandas生态里处理这类问题的标准做法。
前置数据预处理
你当前的日期字段是day X格式的字符串,直接做大小比较会走字典序匹配,结果不准,第一步先把可比较的日期数值抽出来,再按客户、发票开具时间排序:
import pandas as pd import numpy as np # 加载原始测试数据 data = [ ['day 1','day 2', 2, 50], ['day 2','day 4', 2, 60], ['day 3','day 3', 1, 45], ['day 4','day 7', 2, 45], ['day 5','day 10', 3, 90], ['day 6','day 7', 3, 10], ['day 7','day 8', 2, 10] ] columns = ['invoicedate', 'paymentdate', 'clientid', 'amounts'] df = pd.DataFrame(data=data, columns=columns) # 预处理:从日期字符串里提取纯数字,转成可比较的整数 df['inv_day'] = df['invoicedate'].str.extract(r'(\d+)').astype(int) df['pay_day'] = df['paymentdate'].str.extract(r'(\d+)').astype(int) # 按客户ID、发票开单日排序,保证同客户的发票按开单先后顺序排列 df = df.sort_values(['clientid', 'inv_day']).reset_index(drop=True)
核心需求实现:校验开新票时上一张发票是否已完成付款
你的校验逻辑本质是:对每个客户按开单顺序排列的发票记录,拿当前行的开单日,对比同客户上一张发票的付款日——如果上一张的付款日早于当前新票的开单日,就说明开新票时旧账已经结清。
这个逻辑完全可以用分组移位实现,不需要滚动窗口:
# 按客户分组,提取同客户上一张发票的付款日,对齐到当前新票行 client_group = df.groupby('clientid') df['prev_invoice_pay_day'] = client_group['pay_day'].shift(1) # 校验规则:上一张发票的付款完成日 < 当前新票的开单日 → 满足「开新票前旧账已结清」 df['is_prev_paid_before_new_invoice'] = df['prev_invoice_pay_day'] < df['inv_day']
实现说明
groupby.shift(1)是纯向量化操作,对百万级数据的处理速度比滚动窗口加自定义函数快10倍以上- 每个客户的第一张发票因为没有上一张记录,移位后的值为空,属于正常的初始状态
- 你之前尝试用
rolling()没跑通,大概率是两个常见问题:- 没有按客户分组单独开滚动窗口,直接在全表上滚动,导致跨客户对比了无关记录
- pandas默认的单列滚动函数只能传入当前列的序列值,没法在窗口内跨多列取值,写自定义函数传多列的开销会非常大
拓展场景:开新票时检查所有历史发票是否存在未结清
如果你的需求升级为不管隔了多少张旧发票,只要开新票的时间点还有任何一张旧发票没付完,就算存在风险,这时候也不需要硬写滚动函数,用累计最大值的向量化方法就能搞定:
# 计算同客户累计到当前行的历史最大付款日(移位后就变成「上一张及更早的所有发票的最大付款日」) df['hist_max_pay_day'] = client_group['pay_day'].cummax().shift(1) # 如果历史最大付款日 > 当前新票开单日 → 说明有旧发票的付款时间晚于新票开单时间,存在未结清 df['has_unpaid_old_invoice'] = df['hist_max_pay_day'] > df['inv_day']
这种实现和全量滚动逐行检查的结果完全一致,但全程没有用循环或者自定义apply,计算效率比rolling方案高两个量级。
内容的提问来源于stack exchange,提问作者Bernar van Tongeren
相关产品推荐
相关产品推荐

