用 rapidfuzz 或 recordlinkage 实现模糊合并:前者适合小规模,通过相似度阈值筛选配对;后者适合中大规模,采用索引+比较+分类三步流程提升效率,并需处理未匹配、多匹配及阈值调优问题。

用 fuzzywuzzy + pd.merge 做基础模糊合并
直接用 pd.merge 无法处理“张三”和“张珊”这类近似字符串匹配,必须先生成相似度得分,再按阈值筛选配对。最常用的是 fuzzywuzzy(现名 rapidfuzz),它提供 fuzz.ratio、fuzz.partial_ratio 等函数,返回 0–100 的整数得分。
实操建议:
立即学习“Python免费学习笔记(深入)”;
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
- 安装用
pip install rapidfuzz(推荐新库,比原fuzzywuzzy快且无依赖问题) - 对左表每行,在右表中遍历计算
fuzz.ratio(left_name, right_name),取最高分且 ≥80 的匹配项(阈值需根据数据调) - 避免全量笛卡尔积:先用首字+长度做粗筛(如
left['name'].str[0] == right['name'].str[0]),再对子集算相似度 - 示例关键逻辑:
from rapidfuzz import fuzz<br>def get_best_match(x, candidates):<br> scores = [fuzz.ratio(x, c) for c in candidates]<br> idx = np.argmax(scores)<br> return candidates[idx] if scores[idx] >= 80 else None
用 recordlinkage 处理中等规模批量模糊连接
当两张表都超过 1 万行,手写循环会极慢。recordlinkage 是专为实体解析设计的库,支持索引(blocking)+ 比较(comparing)+ 分类(classification)三步流程,能显著减少比较次数。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 先用
indexing.Block('city')或indexing.SortedNeighbourhood('name', window=3)缩小候选对范围 - 比较阶段可同时用多个指标:
compare.String('name', 'name', method='jarowinkler', threshold=0.85)+compare.Exact('gender', 'sex') - 输出是
pd.MultiIndex,需转成 DataFrame 后用.join()或pd.concat()补全原始字段 - 注意:默认不处理 NaN,需提前用
fillna(''),否则jarowinkler会报错
性能陷阱:为什么 apply + fuzz 在大表上慢得离谱
df1['name'].apply(lambda x: get_best_match(x, df2['name'])) 看似简洁,但实际是 O(N×M) 时间复杂度,1 万 × 1 万 就要算 1 亿次字符串比对——CPU 占满且内存飙升。
更现实的做法:
- 用
rapidfuzz.process.extractOne(query, choices, scorer=fuzz.token_sort_ratio),它底层用 C 实现,比纯 Python 循环快 5–10 倍 - 把右表 name 构建成
rapidfuzz.process.Extractor实例,复用索引结构(适用于多次查询) - 真到 10 万+ 行,考虑导出为 DuckDB,用 SQL +
levenshtein()函数(DuckDB 0.10+ 内置),再读回 Pandas - 别信“向量化就能快”——
fuzz类函数本质无法真正向量化,所谓 vectorize 只是语法糖,底层仍是循环
合并后怎么保留未匹配行和多匹配冲突
模糊匹配天然存在一对多或零匹配,pd.merge 默认丢弃未匹配行,而真实业务常需标记“未找到”或“疑似重复”。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 先生成匹配结果 DataFrame(含 left_id、right_id、score),再用
pd.merge(df1, result, on='id', how='left')保证左表完整 - 对 score 为空的行,显式赋值
result['match_status'] = np.where(result['score'].isna(), 'unmatched', 'matched') - 检测多匹配:用
result.groupby('left_id').size().max() > 1,若存在,加一列is_ambiguous = result.groupby('left_id')['score'].rank(method='min', ascending=False) == 1标出最高分唯一项 - 别直接用
how='outer'—— 它会把所有右表行都拉进来,包括完全无关的,导致结果膨胀不可控
真正难的不是跑通代码,而是确定“多高才算匹配”:80 分可能漏掉方言变体,95 分又会卡死在简繁体转换上。先拿 100 行样本手工验几轮,比调参重要得多。

















