You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何不用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)

性能提升原因

  1. 减少重复过滤:df_ref按精确列分组后,每次匹配直接取对应子集,避免重复全表扫描
  2. 向量化计算:利用pandas/numpy的C底层实现批量计算相似度,比Python循环快几个数量级
  3. 保留高效终止逻辑:仅在批量筛选后取最高分,避免逐行冗余判断

内容的提问来源于stack exchange,提问作者arounet

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 15:07:32