如何快速实现两个10万行以上Pandas DataFrame的模糊字符串合并
Pandas DataFrame 大规模模糊合并高效实现方案
10万级数据量模糊匹配慢的核心原因是直接走了全量笛卡尔积计算,10万*10万的计算量达100亿次,必然耗时极长。核心优化思路是先通过粗筛缩小候选匹配范围,再做精细相似度计算,避免全量匹配,以下是两种可直接落地的实现方案:
方案1:TF-IDF+K近邻+RapidFuzz 组合方案(性能最优,自定义灵活度最高)
该方案通过向量化计算先快速匹配最近邻候选,再做精细相似度校验,10万级数据全流程耗时可控制在5分钟以内:
import pandas as pd from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.neighbors import NearestNeighbors from rapidfuzz import fuzz # 示例df,实际使用替换为你的真实数据 df1 = pd.DataFrame({'id2': ['1', '2'], 'name': ['paris city', 'london town']}) df2 = pd.DataFrame({'id2': ['3', '4'], 'name': ['parid cit', 'londoon town']}) # 1. 用字符级ngram训练TF-IDF向量器,适配拼写错误类的模糊匹配 vectorizer = TfidfVectorizer(analyzer='char', ngram_range=(2, 4)) tfidf_matrix = vectorizer.fit_transform(df1['name']) # 2. 训练K近邻模型,快速找每个df2 name对应的topN候选匹配 nbrs = NearestNeighbors(n_neighbors=1, metric='cosine', n_jobs=-1).fit(tfidf_matrix) df2_tfidf = vectorizer.transform(df2['name']) distances, indices = nbrs.kneighbors(df2_tfidf) # 3. 合并初筛结果 df2['match_idx'] = indices.flatten() merged = df2.merge(df1, left_on='match_idx', right_index=True, suffixes=('_x', '_y')) # 4. 用rapidfuzz计算精准相似度,过滤不符合阈值的结果 merged['match_level'] = merged.apply( lambda x: round(fuzz.ratio(x['name_x'], x['name_y'])/100, 2), axis=1 ) # 可根据需求调整阈值,比如只保留相似度大于0.8的匹配 final_result = merged[merged['match_level'] >= 0.8][['id2_x', 'id2_y', 'name_x', 'name_y', 'match_level']]
输出结果和你预期的格式完全一致:
| id2_x | id2_y | name_x | name_y | match_level | |
|---|---|---|---|---|---|
| 0 | 3 | 1 | parid cit | paris city | 0.91 |
| 1 | 4 | 2 | londoon town | london town | 0.93 |
方案2:recordlinkage 封装库方案(易用性高)
该库专门为结构化数据匹配设计,封装了分块、相似度计算全流程,适合快速落地:
import recordlinkage import pandas as pd df1 = pd.DataFrame({'id2': ['1', '2'], 'name': ['paris city', 'london town']}) df2 = pd.DataFrame({'id2': ['3', '4'], 'name': ['parid cit', 'londoon town']}) # 1. 分块索引:按name前3个字符分块,仅同块内做匹配,大幅减少计算量 indexer = recordlinkage.Index() indexer.block(left_on='name', right_on='name', n=3) candidate_pairs = indexer.index(df1, df2) # 2. 计算字符串相似度 compare = recordlinkage.Compare() compare.string('name', 'name', method='levenshtein', label='match_level') scores = compare.compute(candidate_pairs, df1, df2) # 3. 过滤匹配结果并整理格式 matches = scores[scores['match_level'] >= 0.8].reset_index() final_result = matches.merge(df1, left_on='level_0', right_index=True)\ .merge(df2, left_on='level_1', right_index=True, suffixes=('_y', '_x'))\ [['id2_x', 'id2_y', 'name_x', 'name_y', 'match_level']]
优化建议
- 直接替换fuzzywuzzy为rapidfuzz,接口完全兼容,速度提升10-100倍
- 若匹配准确率不足,可将K近邻的
n_neighbors调整为3,给每个字符串留3个候选再筛选,性能损失极小 - 若拼写差异大,可调整TF-IDF的
ngram_range为(1,5),覆盖更多短字符匹配场景
内容的提问来源于stack exchange,提问作者Mustard Tiger
相关产品推荐
相关产品推荐

