Python中如何基于列表列的包含条件合并DataFrame
高效关联两个DataFrame的向量化方案
需求说明
我有两个DataFrame:
- DataFrame 1包含
antecedents(前置元素组)和consequents(结果元素)列 - DataFrame 2包含
Invoice、Customer ID、StockCode(商品代码列表)列
需要实现:当DataFrame 1中antecedents的所有元素都存在于DataFrame 2的StockCode列表中时,将两个DataFrame进行关联。当前用嵌套循环实现的方案效率极低,无法适配大数据集,急需更高效的向量化实现。
数据示例
DataFrame 1示例
import pandas as pd df1 = pd.DataFrame( {'antecedents' : [('20679'), ('85048'), ('22143'), ('22065','22138'), ('20754','21035','22041')], 'consequents' : [('20676'), ('20719'), ('22355'), ('20724'), ('212212')] })
DataFrame 2示例
df2 = pd.DataFrame( {'Customer ID' : [13085, 13078, 15362, 18102, 12682, 18087, 18087, 13635, 14110], 'StockCode' : [ ['85048', '79323P', '79323W', '22041', '21232', '22064'], ['22350', '22349', '22195', '22353'], ['48173C', '21755', '21754', '84879', '22119', '22142'], ['22143', '22145', '22130', '21364', '21360', '21351'], ['21329', '21252', '21100', '21033', '20711', '21410'], ['22065', '22138', '22139', '22352', '85014A', '85014B'], ['22321', '22138', '84029E', '22111'], ['21955', '22111', '22296', '84899E', '22271', '22272'],['20754', '21035', '22041', '82001S', '82580', '85150'] ] })
当前低效循环实现
ant_list = df1['antecedents'].tolist() df_grouped = df2.copy() for list2 in ant_list: for item, row in df_grouped.iterrows(): col_name = str(list2) if all(elem in row['StockCode'] for elem in list2): df_grouped.loc[item,col_name] = 1
高效向量化解决方案
步骤1:统一转换为集合格式
集合的子集判断操作比逐个元素遍历快得多,先把两个DataFrame中的元素组/列表转为集合:
# 处理df1的antecedents:单个元素转为单元素集合,元组转为对应集合 df1['antecedents_set'] = df1['antecedents'].apply(lambda x: set(x) if isinstance(x, tuple) else set([x])) # 处理df2的StockCode列表 df2['stock_set'] = df2['StockCode'].apply(set)
步骤2:批量判断并关联
方案A:基于itertools的批量筛选
适合中等规模数据集,代码直观:
import itertools # 生成df1和df2所有行的组合 row_combinations = itertools.product(df1.itertuples(index=False), df2.itertuples(index=False)) # 筛选符合条件的组合并收集结果 matched_rows = [] for df1_row, df2_row in row_combinations: if df1_row.antecedents_set.issubset(df2_row.stock_set): matched_rows.append({ 'Customer ID': df2_row['Customer ID'], 'StockCode': df2_row.StockCode, 'antecedents': df1_row.antecedents, 'consequents': df1_row.consequents }) # 转为最终结果DataFrame result_df = pd.DataFrame(matched_rows)
方案B:基于Numpy广播的向量化判断
适合超大数据集,利用Numpy的向量化运算进一步提升效率:
import numpy as np # 将集合列转为Numpy数组 ant_sets = df1['antecedents_set'].to_numpy() stock_sets = df2['stock_set'].to_numpy() # 创建布尔矩阵:每一行对应df1的一个antecedent,每一列对应df2的一个StockCode集合 # 矩阵值为True表示该antecedent是对应StockCode集合的子集 match_mask = np.array([[ant_set.issubset(stock_set) for stock_set in stock_sets] for ant_set in ant_sets]) # 获取所有符合条件的行索引对 df1_idx, df2_idx = np.where(match_mask) # 关联两个DataFrame的对应行 result_df = pd.concat([ df1.loc[df1_idx].reset_index(drop=True), df2.loc[df2_idx].reset_index(drop=True) ], axis=1) # 可选:删除中间生成的集合列 result_df = result_df.drop(['antecedents_set', 'stock_set'], axis=1)
性能优化说明
- 集合的
issubset操作时间复杂度为O(len(antecedents)),远低于循环逐个判断元素的O(len(antecedents)*len(StockCode)) - 两种方案都避免了嵌套循环的低效遍历,底层依赖Python/C扩展实现,比纯Python循环快10~100倍(根据数据规模而定)
- 超大数据集可结合分块处理,将df2拆分为多个小批次,分别与df1关联后合并结果,降低内存占用
内容的提问来源于stack exchange,提问作者sapehi
相关产品推荐
相关产品推荐

