如何用Python比较无法载入内存的超大数据框并获取行交集
行交集查找优化方案
现有代码问题
你当前的实现采用逐行apply比对逻辑,时间复杂度为O(N*M),当数据量达到1e6行时会出现严重的性能问题,完全无法适配大数据场景,必须替换为哈希比对的O(N+M)复杂度方案。
内存可加载场景Pandas实现(优先推荐)
基础单向查找实现
首先对齐两个DataFrame的公共列,将行转换为可哈希的元组做集合比对,性能比原有实现高3个数量级以上:
import pandas as pd def row_intersection_fast(df1, df2, na_placeholder=object()): # 取公共列作为比对基准,适配维度不同的场景 common_cols = list(set(df1.columns) & set(df2.columns)) # 处理NA值,用全局唯一占位符避免和现有值冲突 df2_common = df2[common_cols].fillna(na_placeholder) df2_rows = set(tuple(row) for row in df2_common.to_numpy()) df1_common = df1[common_cols].fillna(na_placeholder) return [tuple(row) in df2_rows for row in df1_common.to_numpy()] # 测试用例 a = pd.DataFrame({'a':[ 1, 2, 3, 4], 'b':[ 1, pd.NA, 13, 14], 'c':['w', 'x', 'y', 'z']}) b = pd.DataFrame({'a':[3, 2, 4], 'b':[1, pd.NA, 14], 'c':['y', 'x', 'z']}) print(row_intersection_fast(a,b)) # 输出:[False, True, False, True]
单次遍历获取双向掩码实现
无需两次调用比对函数,仅遍历两个DataFrame各一次即可同时拿到双向存在标记:
def get_bidirectional_mask(df1, df2, na_placeholder=object()): common_cols = list(set(df1.columns) & set(df2.columns)) row_map = {} a_in_b = [False] * len(df1) b_in_a = [False] * len(df2) # 第一次遍历df1,记录行与索引的映射 for idx, row in enumerate(df1[common_cols].fillna(na_placeholder).to_numpy()): row_tuple = tuple(row) row_map[row_tuple] = idx # 第二次遍历df2,直接匹配生成双向掩码 for idx, row in enumerate(df2[common_cols].fillna(na_placeholder).to_numpy()): row_tuple = tuple(row) if row_tuple in row_map: a_in_b[row_map[row_tuple]] = True b_in_a[idx] = True return a_in_b, b_in_a # 用法 a_in_b, b_in_a = get_bidirectional_mask(a,b) print(a_in_b) # [False, True, False, True] print(b_in_a) # [False, True, True]
超内存场景分块处理方案
当数据无法全部加载到内存时,使用Pandas分块读取功能,内存占用仅取决于单块大小:
import pandas as pd # 配置项,可根据内存大小调整 CHUNKSIZE = 100000 # 单块10万行 NA_PLACEHOLDER = object() # 提前确定公共列,避免每块重复计算 COMMON_COLS = ['a', 'b', 'c'] # 第一步:分块读取df2,构建行哈希集合(1e6行的集合仅占用几十MB内存) df2_row_set = set() for chunk in pd.read_csv('df2.csv', chunksize=CHUNKSIZE): chunk_common = chunk[COMMON_COLS].fillna(NA_PLACEHOLDER) for row in chunk_common.to_numpy(): df2_row_set.add(tuple(row)) # 第二步:分块读取df1,逐块计算掩码并输出结果 a_in_b_result = [] # 如果需要保存交集行,直接追加写文件即可 with open('intersection_result.csv', 'w', encoding='utf-8') as f: a.head(0).to_csv(f, index=False) # 写表头 for chunk in pd.read_csv('df1.csv', chunksize=CHUNKSIZE): chunk_common = chunk[COMMON_COLS].fillna(NA_PLACEHOLDER) chunk_mask = [tuple(row) in df2_row_set for row in chunk_common.to_numpy()] a_in_b_result.extend(chunk_mask) # 保存交集行 chunk[chunk_mask].to_csv('intersection_result.csv', mode='a', header=False, index=False)
其他工具对比(Vaex,超大数据场景更优)
Vaex基于内存映射实现,无需加载全量数据到内存,底层为C++优化,比Pandas分块方案速度高2-3倍,代码更简洁:
import vaex # 直接读取大文件,不会加载到内存 df1 = vaex.read_csv('df1.csv') df2 = vaex.read_csv('df2.csv') common_cols = list(set(df1.columns) & set(df2.columns)) # 直接计算双向掩码 df1['a_in_b'] = df1[common_cols].isin(df2[common_cols]) df2['b_in_a'] = df2[common_cols].isin(df1[common_cols]) # 导出交集结果 intersection = df1[df1['a_in_b']] intersection.export_csv('vaex_intersection_result.csv')
内容的提问来源于stack exchange,提问作者shortorian
相关产品推荐
相关产品推荐

