
本文介绍一种高效、可扩展的方法,使用 pd.concat() 配合布尔索引实现两表按指定列(支持单列或多列)合并:保留 df2 的全部行,仅补充 df1 中在 df2 中完全不存在的行(含重复),避免 combine_first 导致的重复膨胀问题。
在 Pandas 数据处理中,当需要“以 df2 为主、df1 为辅”合并两个结构相同的数据框(即:保留 df2 的所有行,同时只追加 df1 中在 df2 中完全未出现过的记录(包括重复行)),直接使用 combine_first 或 merge 往往会因索引对齐逻辑导致重复行被错误广播——正如示例中 A=123 的行从 df2 的 4 行 + df1 的 2 行,意外膨胀为 8 行。
正确解法是:显式分离“覆盖部分”与“补充部分”,再垂直拼接:
✅ 推荐方案:concat + 布尔索引(高效、清晰、支持多列)
import pandas as pd
from io import StringIO
# 示例数据
csv1_data = """A,B,C,D
123,xyz,S1,1111
123,xyz,S1,1111
234,mno,S3,2222
999,abc,S9,9999
999,abc,S9,9999"""
csv2_data = """A,B,C,D
123,abc,S1,1234
123,abc,S1,1234
123,abc,S1,1234
123,cde,S2,2345
234,nop,S3,
567,pqr,S5,5555"""
df1 = pd.read_csv(StringIO(csv1_data), dtype=str, keep_default_na=False)
df2 = pd.read_csv(StringIO(csv2_data), dtype=str, keep_default_na=False)
# ✅ 核心逻辑:取 df2 全部 + df1 中 A 值不在 df2.A 中的行(保留原始重复)
key_col = 'A'
result = pd.concat([
df2,
df1[~df1[key_col].isin(df2[key_col])]
], ignore_index=True)
print(result)输出:
A B C D 0 123 abc S1 1234 1 123 abc S1 1234 2 123 abc S1 1234 3 123 cde S2 2345 4 234 nop S3 5 567 pqr S5 5555 6 999 abc S9 9999 7 999 abc S9 9999
? 支持多列联合标识(动态适配)
当关键标识不止一列(如 ['A', 'B'] 或 ['A', 'A1', 'A2']),只需将列名列表传入,并利用 MultiIndex.isin() 进行精确匹配:
key_cols = ['A'] # 可动态替换为 ['A', 'B']、['A', 'A1', 'A2'] 等 # 构建 MultiIndex 进行成员判断 df1_mask = ~df1.set_index(key_cols).index.isin(df2.set_index(key_cols).index) result = pd.concat([df2, df1[df1_mask]], ignore_index=True)
? 性能提示:该方法时间复杂度为 O(n + m),远优于基于索引重排的 combine_first(易触发内部广播),特别适合百万级数据场景。
⚠️ 注意事项与最佳实践
- 不要用 set_index 后 combine_first:它会将 df1 中每个匹配索引的所有行与 df2 中对应索引的所有行做笛卡尔式填充,导致重复爆炸;
- isin 判断基于值而非位置:确保 key 列类型一致(如都为字符串),避免因 int/str 混合导致匹配失败;
-
空值(NaN)需预处理:isin() 对 NaN 默认返回 False,若业务允许 NaN 作为有效键,建议先用 fillna() 统一占位符(如 "
"); - 内存友好:全程不修改原 DataFrame,无需 .copy(),ignore_index=True 保证结果索引连续。
综上,pd.concat([df2, df1[~df1[key].isin(df2[key])]]) 是解决“主从式去重合并”的简洁、健壮且高性能的标准范式,兼顾可读性与工程可维护性。










