Pandas统计每月末未结清发票的应收账款总额
最优实现方案
针对该需求最高效的实现方式为事件增量累计法,时间复杂度为O(N + M)(N为发票总条数、M为统计月份总数),远优于逐行逐月匹配的O(N*M)方案,对百万级发票量也能快速出结果。
核心逻辑
每张发票只会对应两个影响应收余额的时间节点:
- 开票当月月末,应收余额增加对应开票金额
- 结清当月月末,应收余额减少对应开票金额
按月份汇总所有节点的增减金额后做前缀累加,就能直接得到每个月末的未结清应收总额,完全匹配你的业务规则。
实现代码
第一步:日期格式统一预处理
import pandas as pd # 先将两个日期字段转为datetime类型 df_invoice['INVOICED_DATE'] = pd.to_datetime(df_invoice['INVOICED_DATE']) df_invoice['CLOSED_DATE'] = pd.to_datetime(df_invoice['CLOSED_DATE']) # 提取日期对应的月末日期,与你预先生成的结果表的月份维度对齐 df_invoice['invoice_month_end'] = df_invoice['INVOICED_DATE'] + pd.offsets.MonthEnd(0) df_invoice['close_month_end'] = df_invoice['CLOSED_DATE'] + pd.offsets.MonthEnd(0)
第二步:生成增减事件表
# 开票增量事件:金额为正 add_events = df_invoice[['invoice_month_end', 'AMOUNT_INVOICED']].rename( columns={'invoice_month_end': 'month_end', 'AMOUNT_INVOICED': 'delta'} ) # 结清减量事件:金额为负 sub_events = df_invoice[['close_month_end', 'AMOUNT_INVOICED']].rename( columns={'close_month_end': 'month_end', 'AMOUNT_INVOICED': 'delta'} ) sub_events['delta'] = -sub_events['delta'] # 合并所有事件 all_events = pd.concat([add_events, sub_events], ignore_index=True)
第三步:合并到结果表计算最终值
假设你预先生成的结果表命名为df_result,且表中已存在month_end字段存储每个统计月的月末日期:
# 按月份汇总所有事件的增减额 monthly_delta = all_events.groupby('month_end', as_index=False)['delta'].sum() # 与结果表合并,无事件的月份增减额填0 df_result = df_result.merge(monthly_delta, on='month_end', how='left').fillna({'delta': 0}) # 前缀累加得到每个月末的未结清总额 df_result['Total Outstanding AR'] = df_result['delta'].cumsum()
小数据量简化写法
如果你的发票总条数小于10万,也可以用更直观的逐行判断写法,代码更简洁但性能更低:
df_result['Total Outstanding AR'] = df_result['month_end'].apply( lambda end_date: df_invoice.loc[ (df_invoice['INVOICED_DATE'] <= end_date) & (df_invoice['CLOSED_DATE'] > end_date), 'AMOUNT_INVOICED' ].sum() )
验证说明
两种写法都符合你给出的计算规则:你举的8月末例子中,9月结清的发票会在8月末计入增量、9月末计入减量,因此8月未结清总额会包含这部分金额,和你的示例计算结果完全一致。
内容的提问来源于stack exchange,提问作者ksan
相关产品推荐
相关产品推荐

