如何加速Python中数十万组合记录的DataFrame匹配嵌套循环
问题背景
我有两个DataFrame:GL_df和RE_df,需要执行以下匹配逻辑:
- 找出两表中
ISMATCHED字段值为0的ID的所有可能组合,单组合内最多包含4个ID,且组合金额和不为0; - 筛选规则:跳过已匹配(
ISMATCHED=1)的记录;组合内所有记录的年月(格式%m/%Y)需唯一,且GL_df组合与RE_df组合的年月一致;当GL_df组合金额和与RE_df组合金额和的绝对值之和≤Variance(值为1)时,标记这些记录为匹配并跳过后续匹配。
原代码逻辑符合预期,但执行耗时长达数小时,需优化至秒级或分钟级。
原代码性能瓶颈分析
- 组合爆炸:使用
itertools.combinations生成所有1-4元组合,当未匹配ID数量较多时,组合数呈指数级增长(例如100个ID的4元组合可达392万+),直接导致后续循环量剧增。 - 重复DataFrame查询:每次循环多次查询
GL_df/RE_df获取ISMATCHED、日期、金额信息,这类O(n)操作重复执行会累积大量耗时。 - 嵌套循环遍历:双重循环遍历所有GL和RE组合,时间复杂度为O(M*N),当组合数达百万级时完全无法高效执行。
优化方案
核心思路为缩小匹配范围+预计算+哈希快速查找,具体步骤如下:
- 按年月分组处理,避免跨年月的无效比对;
- 预生成分组内的有效组合并计算金额和,减少重复计算;
- 用哈希表存储RE侧组合的金额信息,实现GL组合的快速匹配;
- 批量标记已匹配记录,实时移除待匹配集合中的已匹配ID,避免重复处理。
优化后的代码
import pandas as pd import numpy as np from itertools import combinations Max_per_com = 4 Variance = 1 # 预处理:添加年月列,过滤未匹配数据 GL_df['YEAR_MONTH'] = GL_df['GL_DATE'].dt.strftime('%m/%Y') RE_df['YEAR_MONTH'] = RE_df['RE_DATE'].dt.strftime('%m/%Y') gl_unmatched = GL_df[GL_df['ISMATCHED'] == 0].copy() re_unmatched = RE_df[RE_df['ISMATCHED'] == 0].copy() # 按年月分组处理 for ym in gl_unmatched['YEAR_MONTH'].unique(): # 获取当前年月的未匹配数据 gl_group = gl_unmatched[gl_unmatched['YEAR_MONTH'] == ym] re_group = re_unmatched[re_unmatched['YEAR_MONTH'] == ym] if len(gl_group) == 0 or len(re_group) == 0: continue # 生成GL侧有效组合:预存组合ID列表和金额和 gl_combinations = [] gl_id_amount = gl_group.set_index('GL_ID')['GL_AMOUNT'].to_dict() for k in range(1, Max_per_com+1): for combo in combinations(gl_id_amount.keys(), k): sum_amt = sum(gl_id_amount[id] for id in combo) if sum_amt != 0: gl_combinations.append((combo, sum_amt)) # 生成RE侧组合并构建哈希映射:键为金额值,值为对应组合列表 re_combo_map = {} re_id_amount = re_group.set_index('RE_ID')['RE_AMOUNT'].to_dict() for k in range(1, Max_per_com+1): for combo in combinations(re_id_amount.keys(), k): sum_amt = sum(re_id_amount[id] for id in combo) if sum_amt != 0: if sum_amt not in re_combo_map: re_combo_map[sum_amt] = [] re_combo_map[sum_amt].append(combo) # 遍历GL组合,查找匹配的RE组合 matched_gl_ids = set() matched_re_ids = set() for gl_combo, gl_sum in gl_combinations: # 跳过包含已匹配ID的组合 if any(id in matched_gl_ids for id in gl_combo): continue # 计算符合条件的RE金额范围:abs(gl_sum + re_sum) ≤1 → re_sum ∈ [-gl_sum-1, -gl_sum+1] target_min = -gl_sum - Variance target_max = -gl_sum + Variance # 在哈希映射中查找符合范围的RE组合 for re_sum in re_combo_map: if target_min <= re_sum <= target_max: for re_combo in re_combo_map[re_sum]: if any(id in matched_re_ids for id in re_combo): continue # 最终确认金额符合条件 if abs(gl_sum + re_sum) <= Variance: # 批量标记匹配 GL_df.loc[GL_df['GL_ID'].isin(gl_combo), 'ISMATCHED'] = 1 RE_df.loc[RE_df['RE_ID'].isin(re_combo), 'ISMATCHED'] = 1 GL_df.loc[GL_df['GL_ID'].isin(gl_combo), 'MATCH_TABLE'] = 'tbl$matched$entry' GL_df.loc[GL_df['GL_ID'].isin(gl_combo), 'MATCH_ID'] = str(re_combo) # 更新已匹配ID集合 matched_gl_ids.update(gl_combo) matched_re_ids.update(re_combo) break else: continue break # 更新未匹配数据集,移除已匹配ID gl_unmatched = gl_unmatched[~gl_unmatched['GL_ID'].isin(matched_gl_ids)] re_unmatched = re_unmatched[~re_unmatched['RE_ID'].isin(matched_re_ids)]
额外优化建议
- 金额整数化:若金额为小数,可先乘以100转为整数(避免浮点精度问题),匹配完成后再转回,提升哈希映射的精准度。
- 优先小组合匹配:先处理1元组合(一对一匹配),再依次处理2元、3元、4元组合,快速减少待处理ID数量。
- 并行分组处理:若年月分组较多,可使用
multiprocessing对不同年月分组并行处理,进一步缩短耗时。
内容的提问来源于stack exchange,提问作者Sabarish R
相关产品推荐
相关产品推荐

