如何基于Pandas的isin方法实现发票数据中信用记录及对应单笔收费行的精准删除?
解决方法:精准删除与信用记录对应的单笔收费行
首先得明确:你用的df1[~df1.isin(df2)].dropna()方法没法实现只删除匹配行一次——因为isin会把所有和信用记录内容匹配的收费行都标记为需要删除,不管重复多少次。比如Case115的两笔53收费都会被删掉,但你只想删其中一笔。
要实现你的需求,核心思路是给重复的收费行加上组内序号,这样即使行内容完全一致,也能通过序号区分开,然后只删除和信用记录序号对应的那一笔。下面是具体的实现步骤,结合你的示例数据:
步骤1:准备数据(模拟你的charge和credit数据集)
先把示例数据转换成pandas DataFrame,和你的拆分逻辑保持一致:
import pandas as pd # 原始发票数据 invoice_data = [ [111, "2G", 53.00], [112, "7G", 25.00], [112, "7G", 25.00], [113, "8G", 20.00], [113, "8G", -20.00], [114, "9G", 15.00], [115, "2G", 53.00], [115, "2G", 53.00], [115, "2G", -53.00] ] invoice_df = pd.DataFrame(invoice_data, columns=["Case", "Part_Number", "Cost"]) # 拆分收费和信用数据 charge_df = invoice_df[invoice_df["Cost"] > 0].reset_index(drop=True) credit_df = invoice_df[invoice_df["Cost"] < 0].reset_index(drop=True)
步骤2:给重复行添加组内序号
我们给每个收费组(按Case、Part_Number、Cost分组)的行分配递增序号,让重复行拥有唯一标识:
# 给收费数据添加组内序号,从0开始计数 charge_df["group_seq"] = charge_df.groupby(["Case", "Part_Number", "Cost"]).cumcount()
处理后charge_df的group_seq列会帮你区分重复行:
| Case | Part_Number | Cost | group_seq |
|---|---|---|---|
| 111 | 2G | 53.00 | 0 |
| 112 | 7G | 25.00 | 0 |
| 112 | 7G | 25.00 | 1 |
| 113 | 8G | 20.00 | 0 |
| 114 | 9G | 15.00 | 0 |
| 115 | 2G | 53.00 | 0 |
| 115 | 2G | 53.00 | 1 |
接着处理信用数据:把Cost转成绝对值(和收费金额对齐),同样添加组内序号(每个信用组只有1行,所以序号都是0):
credit_df_matched = credit_df.copy() # 信用记录的Cost取绝对值,和收费金额匹配 credit_df_matched["Cost"] = credit_df_matched["Cost"].abs() # 给信用数据添加组内序号 credit_df_matched["group_seq"] = credit_df_matched.groupby(["Case", "Part_Number", "Cost"]).cumcount()
处理后credit_df_matched:
| Case | Part_Number | Cost | group_seq |
|---|---|---|---|
| 113 | 8G | 20.00 | 0 |
| 115 | 2G | 53.00 | 0 |
步骤3:精准筛选需要保留的收费行
现在通过Case、Part_Number、Cost、group_seq四个字段的组合来匹配,只删除和信用记录完全对应的那一行:
# 把两个DataFrame的关键字段转成索引,用于匹配 charge_index = charge_df.set_index(["Case", "Part_Number", "Cost", "group_seq"]).index credit_index = credit_df_matched.set_index(["Case", "Part_Number", "Cost", "group_seq"]).index # 保留不在信用索引中的收费行,删除临时的group_seq列 filtered_charge = charge_df[~charge_index.isin(credit_index)].drop(columns="group_seq")
最终结果
运行后filtered_charge就是你想要的输出:
| Case | Part_Number | Cost |
|---|---|---|
| 111 | 2G | 53.00 |
| 112 | 7G | 25.00 |
| 112 | 7G | 25.00 |
| 114 | 9G | 15.00 |
| 115 | 2G | 53.00 |
为什么原来的方法不行?
isin()是检查整行是否存在于另一个DataFrame中,对于重复行(比如Case115的两笔53),它会把所有匹配行都标记为True,所以~isin()会删掉所有重复行,没法控制只删除一次。而添加组内序号后,我们给每一行加了唯一标识,就能精准定位到需要删除的那一笔。
内容的提问来源于stack exchange,提问作者Link157
相关产品推荐
相关产品推荐

