如何在Pandas中基于指定列合并DataFrame并保留各自重复项
问题描述
有两个按日期排序的交易数据DataFrame:主DataFrame(df_master)包含额外列,更新DataFrame(df_update)包含主表的所有行及新行。需求如下:
- 基于非额外列(
date、notification、amount)将更新表的数据合并到主表; - 保留两个DataFrame各自内部的重复项;
- 移除两表之间的重叠重复项;
- 维持交易的日期顺序。
示例数据
主表(MASTER)
date notification amount added_info 0 15/10/2021 string1 30.00 XX 1 15/10/2021 string1 30.00 XX 2 15/10/2021 string2 25.00 XX 3 11/10/2021 string3 3.00 YY 4 07/10/2021 string1 10.00 5 05/10/2021 string2 12.50 6 30/09/2021 string2 12.50 XX
更新表(UPDATE)
date notification amount 0 24/12/2021 string2 20.00 1 20/12/2021 string1 12.00 2 15/10/2021 string1 30.00 3 15/10/2021 string1 30.00 4 15/10/2021 string1 30.00 5 15/10/2021 string2 25.00 6 11/10/2021 string3 3.00 7 07/10/2021 string1 10.00 8 06/10/2021 string2 10.00
预期结果
EXPECTED RESULT (UPDATED MASTER) date notification amount added_info 0 24/12/2021 string2 20.00 1 20/12/2021 string1 12.00 2 15/10/2021 string1 30.00 3 15/10/2021 string1 30.00 XX 4 15/10/2021 string1 30.00 XX 5 15/10/2021 string2 25.00 XX 6 11/10/2021 string3 3.00 YY 7 07/10/2021 string1 10.00 8 06/10/2021 string2 10.00 9 05/10/2021 string2 12.50 10 30/09/2021 string2 12.50 XX
说明:合并时不使用added_info列匹配,保留主表内部重复行,添加更新表中未与主表重叠的重复行。使用concat+drop_duplicates会移除所有重复项,不符合需求。
现有朴素解法
import pandas as pd df_master = pd.DataFrame({'date': {0: '15/10/2021', 1: '15/10/2021', 2: '15/10/2021', 3: '11/10/2021', 4: '07/10/2021', 5: '05/10/2021', 6: '30/09/2021'}, 'notification': {0: 'string1', 1: 'string1', 2: 'string2', 3: 'string3', 4: 'string1', 5: 'string2', 6: 'string2'}, 'amount': {0: 30.0, 1: 30.0, 2: 25.0, 3: 3.0, 4: 10.0, 5: 12.5, 6: 12.5}, 'added_info': {0: 'XX', 1: 'XX', 2: 'XX', 3: 'YY', 4: '', 5: '', 6: 'XX'}}) df_update = pd.DataFrame({'date': {0: '24/12/2021', 1: '20/12/2021', 2: '15/10/2021', 3: '15/10/2021', 4: '15/10/2021', 5: '15/10/2021', 6: '11/10/2021', 7: '07/10/2021', 8: '06/10/2021'}, 'notification': {0: 'string2', 1: 'string1', 2: 'string1', 3: 'string1', 4: 'string1', 5: 'string2', 6: 'string3', 7: 'string1', 8: 'string2'}, 'amount': {0: 20.0, 1: 12.0, 2: 30.0, 3: 30.0, 4: 30.0, 5: 25.0, 6: 3.0, 7: 10.0, 8: 10.0}}) df_update["Dupl"] = "" df_master_concat = pd.DataFrame() df_update_concat = pd.DataFrame() df_update_concat["string"] = df_update[["date", "notification", "amount"]].agg(lambda x: "".join(x.astype(str)), axis=1) df_master_concat["string"] = df_master[["date", "notification", "amount"]].agg(lambda x: "".join(x.astype(str)), axis=1) for indx, row in df_update_concat.iterrows(): if df_update_concat.iloc[indx]["string"] in df_master_concat["string"].values: df_update.at[indx , "Dupl"] = "X" df_master_concat.drop(df_master_concat.loc[df_master_concat['string'] == df_update_concat.iloc[indx]["string"]].iloc[0].name, inplace=True) # drop the first occurence df_update = df_update[df_update.Dupl != "X"] df_update.drop('Dupl', axis=1, inplace=True) df_master= pd.concat([df_master, df_update], ignore_index=True).fillna("") df_master["date"] = pd.to_datetime(df_master["date"], format="%d/%m/%Y") df_master.sort_values(by='date', inplace = True, ascending=False) df_master["date"] = df_master["date"].dt.strftime("%d/%m/%Y") df_master = df_master.reset_index(drop=True) print(df_master)
该解法支持扩展主表的额外列,但效率较低。请问是否有Pandas内置的高效实现方式,以及该朴素解法的优劣如何?
解决方案
一、高效实现方式(基于Pandas内置方法)
核心思路是给两个表的重复组添加计数标识,只保留更新表中计数超过主表对应组的部分,最后合并并排序,全程用向量化操作替代循环。
代码实现
import pandas as pd # 加载数据 df_master = pd.DataFrame({'date': {0: '15/10/2021', 1: '15/10/2021', 2: '15/10/2021', 3: '11/10/2021', 4: '07/10/2021', 5: '05/10/2021', 6: '30/09/2021'}, 'notification': {0: 'string1', 1: 'string1', 2: 'string2', 3: 'string3', 4: 'string1', 5: 'string2', 6: 'string2'}, 'amount': {0: 30.0, 1: 30.0, 2: 25.0, 3: 3.0, 4: 10.0, 5: 12.5, 6: 12.5}, 'added_info': {0: 'XX', 1: 'XX', 2: 'XX', 3: 'YY', 4: '', 5: '', 6: 'XX'}}) df_update = pd.DataFrame({'date': {0: '24/12/2021', 1: '20/12/2021', 2: '15/10/2021', 3: '15/10/2021', 4: '15/10/2021', 5: '15/10/2021', 6: '11/10/2021', 7: '07/10/2021', 8: '06/10/2021'}, 'notification': {0: 'string2', 1: 'string1', 2: 'string1', 3: 'string1', 4: 'string1', 5: 'string2', 6: 'string3', 7: 'string1', 8: 'string2'}, 'amount': {0: 20.0, 1: 12.0, 2: 30.0, 3: 30.0, 4: 30.0, 5: 25.0, 6: 3.0, 7: 10.0, 8: 10.0}}) # 1. 给两个表的每组重复行添加序号(从1开始) match_cols = ['date', 'notification', 'amount'] df_master['seq'] = df_master.groupby(match_cols).cumcount() + 1 df_update['seq'] = df_update.groupby(match_cols).cumcount() + 1 # 2. 获取主表每组的最大序号 master_max_seq = df_master.groupby(match_cols)['seq'].max().reset_index() # 3. 筛选更新表中需要新增的行:要么是主表没有的组,要么是组内序号超过主表最大值的行 df_update_new = df_update.merge( master_max_seq, on=match_cols, how='left', suffixes=('', '_master_max') ) df_update_new = df_update_new[ (df_update_new['seq'] > df_update_new['seq_master_max']) | df_update_new['seq_master_max'].isna() ].drop(columns=['seq_master_max', 'seq']) # 4. 合并主表和新增行,按日期排序 df_combined = pd.concat([df_master.drop(columns='seq'), df_update_new], ignore_index=True).fillna('') df_combined['date'] = pd.to_datetime(df_combined['date'], format='%d/%m/%Y') df_combined = df_combined.sort_values(by='date', ascending=False).reset_index(drop=True) df_combined['date'] = df_combined['date'].dt.strftime('%d/%m/%Y') print(df_combined)
二、朴素解法的优劣分析
优点
- 逻辑直观:通过拼接字符串生成唯一标识,逐行匹配移除重叠项,符合常规思维,新手容易理解。
- 兼容性强:不依赖复杂分组操作,支持主表添加任意额外列,无需修改核心逻辑。
缺点
- 效率极低:
iterrows()逐行循环+in判断+drop操作,时间复杂度为O(n²),数据量较大时性能会急剧下降。 - 稳定性差:字符串拼接可能出现冲突(比如不同数值拼接后字符串相同),导致错误匹配。
- 代码冗余:需要手动处理标识列、循环判断,代码量多,维护成本高。
内容的提问来源于stack exchange,提问作者ChienMouille
相关产品推荐
相关产品推荐

