如何不用iterrows实现DataFrame的模糊匹配合并?
问题
我正在用pandas编写函数合并两个DataFrame:df_ref(固定大表,50万行50列)和df_to_merge(每次调用都会变化的小表,列是df_ref的子集)。合并规则如下:
- 精确匹配列(
col_exact):必须完全匹配 - 模糊匹配列(
col_approx):每个列对应专属相似度计算方法(编辑距离/数值距离,得分范围0-1),仅当所有模糊列得分均超过对应阈值的行才保留,最终取相似度总分最高的匹配行
目前用iterrows()嵌套遍历实现,速度极慢,虽做了提前过滤但效率仍不理想,代码如下:
def merge_dfs(df_to_match, df_ref, col_exact, col_approx): """Merges both dataframes. col_exact are name of columns that have to match exactly, col_approx approximately""" matches_found = 0 for index, row in df_to_match.iterrows(): best_matches = [] # 过滤精确匹配的行 filtered_df = df_ref.copy() for column_name in col_exact: filtered_df = filtered_df[filtered_df[column_name] == row[column_name]] # 遍历过滤后的行计算相似度 for index2, row2 in filtered_df.iterrows(): similarity_scores_row = {} for column_name in col_approx: check_similarity = comparison_methods[column_name] similarity_score = check_similarity(row[column_name], row2[column_name]) if similarity_score >= similarity_thresholds[column_name]: similarity_scores_row[column_name] = similarity_score else: break # 所有模糊列达标才保留 if len(similarity_scores_row) == len(col_approx): overall_score = sum(similarity_scores_row.values()) / len(col_approx) best_matches.append((row2, overall_score)) if overall_score == 1: # 找到完全匹配就终止 break # 更新匹配结果 if best_matches: matches_found += 1 best_match = max(best_matches, key=lambda t: t[1])[0] for col_name in df_ref.columns: if col_name not in col_approx: df_to_match.at[index, col_name] = best_match[col_name] return df_to_match
代码可正常运行,但并非pandas高效用法,求更优实现。
示例说明(col_exact为空):
- 参考表
df_ref:
A B C 0 1 foo 100 1 2 bar 256 2 3 baz 32 3 4 qux 44
- 待匹配表
df_to_merge:
Name A B 0 Paul 0.7 floo 1 John 1.6 baz 2 Ringo 4.2 qux_ 3 Georges 2.0 baar
- 相似度配置(修正原B列得分范围为0-1):
from Levenshtein import distance comparison_methods = {'A': lambda x,y: 1 - abs(x-y)/(x+y), 'B': lambda x,y: 1 - distance(x,y)/max(len(x), len(y))} similarity_thresholds = {'A': 0.7, 'B': 0.6} col_exact = [] col_approx = ['A', 'B']
- 期望合并结果:
Name A B C 0 Paul 0.7 floo 100.0 1 John 1.6 baz 32.0 2 Ringo 4.2 qux_ 44.0 3 Georges 2.0 baar 256.0
优化方案
核心思路是用向量化操作替代嵌套循环,利用pandas/numpy的批量计算能力,同时针对df_ref固定的特点做预处理,大幅减少重复计算。
1. 预处理df_ref:按精确匹配列分组
因df_ref固定,提前按col_exact列分组,后续匹配时直接取对应分组,避免每次全表过滤:
from collections import defaultdict import pandas as pd import numpy as np from Levenshtein import distance as lev_dist # 预处理df_ref:按精确匹配列分组 def preprocess_ref(df_ref, col_exact): if not col_exact: return defaultdict(lambda: df_ref) # 生成多列分组键(元组形式) df_ref['_group_key'] = df_ref[col_exact].apply(tuple, axis=1) return df_ref.groupby('_group_key') # 全局预处理(仅执行一次,因df_ref固定) ref_groups = preprocess_ref(df_ref, col_exact)
2. 向量化计算相似度得分
避免逐行遍历,对每个待匹配行,在对应分组的df_ref子集中批量计算所有模糊列的相似度,再筛选达标行并取最高分:
def vectorized_similarity(row, ref_subset, col_approx, comparison_methods, thresholds): scores = pd.DataFrame() valid = pd.Series([True]*len(ref_subset), index=ref_subset.index) for col in col_approx: func = comparison_methods[col] # 数值列用numpy批量计算,提升效率 if pd.api.types.is_numeric_dtype(ref_subset[col]) and pd.api.types.is_numeric_dtype(row[col]): ref_vals = ref_subset[col].values row_val = row[col] scores[col] = func(row_val, ref_vals) # 字符串列批量计算编辑距离并转换为0-1得分 else: scores[col] = ref_subset[col].apply(lambda x: func(row[col], x)) # 筛选超过阈值的行 valid = valid & (scores[col] >= thresholds[col]) # 无达标行则返回None valid_scores = scores[valid] if len(valid_scores) == 0: return None # 计算总分并取最高分对应的行 valid_scores['_total'] = valid_scores.mean(axis=1) best_idx = valid_scores['_total'].idxmax() return ref_subset.loc[best_idx] def optimized_merge(df_to_merge, ref_groups, col_exact, col_approx, comparison_methods, thresholds): result = df_to_merge.copy() # 补充df_ref中存在但df_to_merge缺失的列(排除模糊匹配列) missing_cols = [col for col in df_ref.columns if col not in df_to_merge.columns and col not in col_approx] for col in missing_cols: result[col] = np.nan for idx, row in df_to_merge.iterrows(): # 获取精确匹配的分组 if col_exact: group_key = tuple(row[col] for col in col_exact) if group_key not in ref_groups.groups: continue ref_subset = ref_groups.get_group(group_key) else: ref_subset = ref_groups[()] # 批量计算相似度并获取最佳匹配 best_match = vectorized_similarity(row, ref_subset, col_approx, comparison_methods, thresholds) if best_match is not None: # 更新缺失列的值 for col in missing_cols: result.at[idx, col] = best_match[col] return result
3. 进阶优化:用更高效的向量化库
- 数值相似度计算直接用numpy数组操作,比lambda快10-100倍
- 字符串编辑距离可改用
rapidfuzz库的cdist接口,批量计算距离矩阵,效率远超逐行调用Levenshtein
优化后代码调用示例
# 配置相似度函数 comparison_methods = { 'A': lambda x, y: 1 - np.abs(x - y)/(x + y), # numpy批量计算数值相似度 'B': lambda x, y: 1 - lev_dist(x, y)/max(len(x), len(y)) } similarity_thresholds = {'A': 0.7, 'B': 0.6} col_exact = [] col_approx = ['A', 'B'] # 预处理参考表(仅执行一次) ref_groups = preprocess_ref(df_ref, col_exact) # 执行合并 merged_df = optimized_merge(df_to_merge, ref_groups, col_exact, col_approx, comparison_methods, similarity_thresholds) print(merged_df)
性能提升原因
- 减少重复过滤:
df_ref按精确列分组后,每次匹配直接取对应子集,避免重复全表扫描 - 向量化计算:利用pandas/numpy的C底层实现批量计算相似度,比Python循环快几个数量级
- 保留高效终止逻辑:仅在批量筛选后取最高分,避免逐行冗余判断
内容的提问来源于stack exchange,提问作者arounet
相关产品推荐
相关产品推荐

