如何基于公共标识列合并两个 DataFrame 并优先保留第二张表的重复行

本文介绍一种高效、可扩展的方法,使用 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])]]) 是解决“主从式去重合并”的简洁、健壮且高性能的标准范式,兼顾可读性与工程可维护性。