如何高效检测同一列中姓名的相似度(大数据量场景)
高效姓名相似度匹配解决方案
核心思路:避免全量两两比对
全量交叉合并会生成约1.6亿条记录(40000²),完全超出内存承载能力,必须通过减少待比对配对数来解决问题。
1. 预分组(Blocking)
先按姓名的特征分组,仅在组内进行比对,大幅削减配对数量:
- 按首字母/前n个字符分组:提取姓名的首字母或前几个字符作为分组键,只比对同组内的姓名
from fuzzywuzzy import fuzz import pandas as pd df = pd.DataFrame(data = ['John','gal britt','mona','diana','molly','merry','mony','molla','johnathon','dina'], columns=['Name']) # 生成分组键:统一转为小写后取首字母 df['block_key'] = df['Name'].str.lower().str[0] # 遍历组内生成配对并计算相似度 results = [] for key, group in df.groupby('block_key'): # 组内交叉合并并排除自身配对 pairs = group.merge(group, on='block_key', suffixes=('_1', '_2')) pairs = pairs[pairs['Name_1'] != pairs['Name_2']] # 计算模糊匹配相似度 pairs['similarity'] = pairs.apply(lambda x: fuzz.ratio(x['Name_1'], x['Name_2']), axis=1) results.append(pairs) final_df = pd.concat(results)[['Name_1', 'Name_2', 'similarity']] - 按分词/结构分组:如果姓名包含多部分(如"gal britt"),可以按姓氏或拆分后的单词分组,进一步缩小比对范围
2. 近似字符串索引库
利用专门的近似索引工具,快速定位相似字符串,无需全量比对:
- 使用
annoy构建向量索引:基于字符n-gram生成向量,通过近似最近邻算法快速找到相似项from annoy import AnnoyIndex import numpy as np from sklearn.feature_extraction.text import CountVectorizer from fuzzywuzzy import fuzz # 用字符2-gram生成姓名向量 vectorizer = CountVectorizer(analyzer='char', ngram_range=(2,3)) name_vectors = vectorizer.fit_transform(df['Name']).toarray() # 构建Annoy索引 dim = name_vectors.shape[1] index = AnnoyIndex(dim, 'angular') for i, vec in enumerate(name_vectors): index.add_item(i, vec) index.build(10) # 树的数量,平衡搜索速度和精度 # 查找每个姓名的近似匹配(排除自身) results = [] for i, name in enumerate(df['Name']): # 取前6个相似项,去掉自身(第1个是自己) similar_indices = index.get_nns_by_item(i, 6)[1:] for idx in similar_indices: sim = fuzz.ratio(name, df['Name'].iloc[idx]) results.append({'Name_1': name, 'Name_2': df['Name'].iloc[idx], 'similarity': sim}) final_df = pd.DataFrame(results) - 用
fuzzywuzzy.process批量提取:设置相似度阈值,只保留符合要求的匹配项from fuzzywuzzy import process results = [] # 对每个姓名,提取相似度≥80的匹配项(排除自身) for name in df['Name']: matches = process.extract(name, df['Name'], limit=5, scorer=fuzz.ratio) for match_name, sim, _ in matches: if name != match_name and sim >= 80: results.append({'Name_1': name, 'Name_2': match_name, 'similarity': sim}) final_df = pd.DataFrame(results).drop_duplicates(subset=['Name_1', 'Name_2'])
3. 分块处理
如果必须进行全量比对,将数据集拆分为小块,逐块比对后合并结果:
from fuzzywuzzy import fuzz import pandas as pd chunk_size = 1000 results = [] # 遍历所有块组合 for i in range(0, len(df), chunk_size): chunk1 = df.iloc[i:i+chunk_size] # 只和当前及后续块比对,避免重复配对 for j in range(i, len(df), chunk_size): chunk2 = df.iloc[j:j+chunk_size] pairs = chunk1.merge(chunk2, how='cross') # 同块内排除自身配对 if i == j: pairs = pairs[pairs['Name_x'] != pairs['Name_y']] # 计算相似度 pairs['similarity'] = pairs.apply(lambda x: fuzz.ratio(x['Name_x'], x['Name_y']), axis=1) results.append(pairs) final_df = pd.concat(results)[['Name_x', 'Name_y', 'similarity']]
搭配相似度阈值过滤,只保留高相似记录,可进一步减少内存占用。
4. 并行加速处理
结合分块和多进程,提升处理效率:
from fuzzywuzzy import fuzz import pandas as pd from multiprocessing import Pool def process_chunk(chunk_pair): chunk1, chunk2 = chunk_pair pairs = chunk1.merge(chunk2, how='cross') # 同块内排除自身配对 if chunk1.equals(chunk2): pairs = pairs[pairs['Name_x'] != pairs['Name_y']] pairs['similarity'] = pairs.apply(lambda x: fuzz.ratio(x['Name_x'], x['Name_y']), axis=1) return pairs # 生成块组合 chunk_size = 1000 chunks = [df.iloc[i:i+chunk_size] for i in range(0, len(df), chunk_size)] chunk_pairs = [] for i in range(len(chunks)): for j in range(i, len(chunks)): chunk_pairs.append((chunks[i], chunks[j])) # 多进程处理 with Pool(4) as p: results = p.map(process_chunk, chunk_pairs) final_df = pd.concat(results)[['Name_x', 'Name_y', 'similarity']]
内容的提问来源于stack exchange,提问作者Ismail Awad
相关产品推荐
相关产品推荐

