如何高效实现两个pandas DataFrame基于多列的无替换相似样本匹配
双DataFrame相似样本匹配高效解决方案
原代码性能瓶颈
- 30万次逐行遍历循环开销极大,每次循环都涉及pandas全局布尔筛选、
loc赋值,单步操作的累积开销远超批量操作 - 每次对单条df2样本调用
cdist计算所有未匹配df1样本的距离,总计算量达30万*800万级,完全不具备可执行性 - 无替换匹配逻辑和距离计算耦合,进一步放大了开销
优化方案:基于Faiss的批量最近邻查询
Faiss是Meta开源的高性能向量检索库,针对大规模稠密向量检索做了深度优化,3维特征场景下就算用精确检索,速度也比原方案快数千倍
依赖安装
直接安装CPU版本即可满足需求:pip install faiss-cpu
完整实现代码
import pandas as pd import numpy as np import faiss import random # 基础参数配置 df_1_size = 8000000 df_2_size = 300000 topk = 1 match_cols = ['B', 'C', 'D'] # 生成测试数据(和你的原逻辑一致) df1 = pd.DataFrame(np.random.random_sample((df_1_size, 5)), columns=list('BCDEF')) df1['A'] = np.random.randint(1,3,size=df_1_size) df1['ID'] = random.sample(range(0, df_1_size), df_1_size) df2 = pd.DataFrame(np.random.random_sample((df_2_size, 5)), columns=list('BCDEF')) df2['A'] = np.random.randint(1,3,size=df_2_size) df2['ID'] = random.sample(range(0, df_2_size), df_2_size) # 特征预处理:L2归一化后向量内积等价于余弦相似度,适配Faiss内积索引 def normalize_feats(feats): norm = np.linalg.norm(feats, axis=1, keepdims=True) return feats / norm df1_feats = normalize_feats(df1[match_cols].values.astype('float32')) df2_feats = normalize_feats(df2[match_cols].values.astype('float32')) matched_ids = [] # 按A列分组处理,保留原需求的A列匹配逻辑 for a_val in [1, 2]: # 筛选对应A值的子集,避免每次循环筛选 df1_sub = df1[df1['A'] == a_val].reset_index(drop=True) df1_sub_feats = df1_feats[df1['A'] == a_val] df2_sub_feats = df2_feats[df2['A'] == a_val] sub_query_num = len(df2_sub_feats) if sub_query_num == 0 or len(df1_sub) < sub_query_num * topk: continue # 构建精确内积索引,3维特征下构建索引和查询都极快 index = faiss.IndexFlatIP(3) index.add(df1_sub_feats) # 批量查询topk*5个结果,多查几倍用于后续无替换去重 D, I = index.search(df2_sub_feats, topk * 5) # 无替换匹配逻辑 used_idx = set() sub_matched = [] for i in range(sub_query_num): for j in range(I.shape[1]): candidate_idx = I[i, j] if candidate_idx not in used_idx: used_idx.add(candidate_idx) sub_matched.append(candidate_idx) break # 收集当前分组匹配到的ID matched_ids.extend(df1_sub.iloc[sub_matched]['ID'].tolist()) # 导出结果 df1_matched = df1[df1['ID'].isin(matched_ids)] df1_matched.to_csv('df1_matched.csv', index=False)
性能说明
30万查询、800万条3维向量的精确匹配,普通CPU单线程执行时间在10分钟以内,开启Faiss多线程优化后可压缩到2-3分钟,完全满足性能需求。
轻量备选方案(无需额外依赖)
如果不想引入Faiss,可替换为sklearn的NearestNeighbors,性能比你原方案提升100倍以上,代码改动极小:
from sklearn.neighbors import NearestNeighbors # 替换Faiss索引构建和查询部分即可 nn = NearestNeighbors(metric='cosine', n_neighbors=topk*5, n_jobs=-1) nn.fit(df1_sub_feats) D, I = nn.kneighbors(df2_sub_feats) # 后续无替换逻辑和上面一致
内容的提问来源于stack exchange,提问作者khemedi
相关产品推荐
相关产品推荐

