Pandas按多列最相似值匹配合并不同长度DataFrame的方法
不等长Pandas DataFrame 多维度模糊匹配合并方案
核心思路
先通过age、department两个字段做精确分块,把匹配范围缩小到同年龄同部门的记录池内,再做name字段的模糊匹配,从根源避免同名跨部门/跨年龄错配;匹配过程增加空结果判断和相似度阈值,解决直接调用get_close_matches触发的索引报错问题,同时自动过滤无匹配记录。
实现步骤
- 导入依赖
import pandas as pd from difflib import get_close_matches import re
- 定义name标准化规则
针对name存在的标点、后缀(如III、Jr.)差异做统一清洗,提升模糊匹配准确率:
def name_standardize(s: str) -> str: # 移除标点 s = re.sub(r'[.,]', '', s) # 移除姓名后缀 s = re.sub(r'\s+(III|Jr|Sr|II)$', '', s, flags=re.IGNORECASE) return s.strip().lower()
- 编写匹配函数
设置相似度阈值,分块匹配,空结果直接跳过,避免索引报错:
def fuzzy_merge(df_left: pd.DataFrame, df_right: pd.DataFrame, sim_cutoff: float = 0.6) -> pd.DataFrame: match_result = [] # 按精确匹配字段分块,缩小匹配范围 for (age, dept), left_group in df_left.groupby(["age", "department"]): # 筛选右表中同年龄同部门的记录作为当前匹配池 right_pool = df_right[(df_right["age"] == age) & (df_right["department"] == dept)].copy() if right_pool.empty: continue # 预生成匹配池的标准化姓名、原始姓名、其余字段映射 pool_std_names = right_pool["name"].apply(name_standardize).tolist() pool_origin_names = right_pool["name"].tolist() pool_other_data = right_pool.drop(columns=["name", "age", "department"]).to_dict("records") # 遍历左表当前分块的每条记录做匹配 for _, left_row in left_group.iterrows(): left_std_name = name_standardize(left_row["name"]) # 取相似度最高的1条结果,低于阈值直接返回空 top_match = get_close_matches(left_std_name, pool_std_names, n=1, cutoff=sim_cutoff) if not top_match: # 无匹配直接跳过,解决IndexError问题 continue match_pos = pool_std_names.index(top_match[0]) # 拼接匹配结果 row_data = left_row.to_dict() row_data["name_y"] = pool_origin_names[match_pos] row_data.update(pool_other_data[match_pos]) match_result.append(row_data) return pd.DataFrame(match_result)
使用示例
构造测试数据验证效果:
# 测试用df1 df1 = pd.DataFrame({ "name": ["DJ Griffin", "Harris Smith", "Li Hua", "Wang Ming"], "age": [28, 33, 25, 40], "department": ["Marketing", "Tech", "HR", "Finance"] }) # 测试用df2,包含同名不同年龄记录、无匹配记录 df2 = pd.DataFrame({ "name": ["D.J. Griffin III", "Harris Smith", "Harris Smith", "Miles Jones", "Li H.", "Wang M."], "age": [28, 33, 45, 30, 25, 40], "department": ["Marketing", "Tech", "Tech", "Admin", "HR", "Finance"], "salary": [18000, 25000, 32000, 12000, 15000, 28000] }) # 执行合并 final_df = fuzzy_merge(df1, df2, sim_cutoff=0.6)
效果说明
- 基础匹配场景:
DJ Griffin会准确匹配到同年龄同部门的D.J. Griffin III,无匹配的Miles Jones会被自动过滤,结果中仅保留匹配成功的记录 - 同名重复场景:分块匹配机制下,33岁的
Harris Smith只会在同年龄的Tech组匹配池内查找,不会错配到45岁的同名记录 - 报错解决:通过空结果判断逻辑,不会出现
get_close_matches返回空列表时取[0]触发的IndexError: list index out of range问题
参数调整建议
sim_cutoff:相似度阈值,取值范围0-1,可根据实际数据的姓名差异程度调整,常规场景0.6-0.8即可,值越高匹配规则越严格name_standardize:可根据实际数据的姓名格式差异新增清洗规则,比如移除中间名缩写、统一拼音拼写规范等,进一步提升匹配准确率
内容的提问来源于stack exchange,提问作者Giskard
相关产品推荐
相关产品推荐

