如何利用pd.DataFrame批量过滤另一个pd.DataFrame?
高效批量过滤DataFrame的优化方案
问题背景
现有两个DataFrame:
data_df:存储业务数据,行数可达数十万至数百万filter_df:每行代表一条过滤规则,格式为a列匹配值 +b列的区间[b0, b1],行数在10-1000之间
示例数据:
import pandas as pd data_df = pd.DataFrame([{'a':i%10, 'b':i%15} for i in range(30)]) filter_df = pd.DataFrame({'a':[3,4,5], 'b0':[5,6,8], 'b1':[15,10,11]})
需求是将所有过滤规则应用到data_df,等价于手动拼接多个条件筛选结果:
pd.concat([ data_df[(data_df.a==3) & data_df.b.between(5,15)], data_df[(data_df.a==4) & data_df.b.between(6,10)], data_df[(data_df.a==5) & data_df.b.between(8,11)] ])
现有apply方案需要拼接结果,存在性能和简洁性问题,需要更优实现。
优化方案
方案1:合并DataFrame后批量筛选(推荐,性能最优)
利用merge将两个DataFrame按a列关联,然后直接用向量化操作判断b是否在对应区间,最后去重(避免多规则匹配同一行的重复情况):
# 按a列关联两个表 merged = data_df.merge(filter_df, on='a', how='inner') # 筛选符合区间条件的行 mask = merged['b'].between(merged['b0'], merged['b1']) # 提取原数据列并去重 result = merged.loc[mask, data_df.columns].drop_duplicates()
优势:完全向量化操作,避免循环/apply的逐行计算,大数据量下性能远超apply方案,代码简洁易读。
方案2:用query构造批量条件
将filter_df的规则转化为query可识别的字符串条件,一次性执行筛选:
# 构造每个规则的条件字符串 conditions = [f"(a=={row['a']}) & b.between({row['b0']}, {row['b1']})" for _, row in filter_df.iterrows()] # 拼接所有条件(逻辑或) query_str = ' | '.join(conditions) # 执行筛选 result = data_df.query(query_str)
优势:代码简洁直观,适合规则数量不多的场景(1000条以内完全没问题),性能接近向量化方案。
方案3:分组后批量应用区间筛选
先按a列对data_df分组,再匹配filter_df的规则批量处理:
# 构建a到区间的映射字典 a_to_range = filter_df.set_index('a')[['b0', 'b1']].to_dict('index') # 分组后筛选 groups = [] for a, group in data_df.groupby('a'): if a not in a_to_range: continue b0, b1 = a_to_range[a]['b0'], a_to_range[a]['b1'] groups.append(group[group['b'].between(b0, b1)]) result = pd.concat(groups)
优势:减少不必要的判断,只处理filter_df中存在的a值分组,适合data_df中a的取值范围远大于filter_df的场景。
性能对比
- 百万行
data_df+1000条规则:方案1(merge+向量筛选)最快,耗时约0.1-0.3秒;方案2(query)次之;方案3(分组)略慢于前两者,但远优于原apply方案(耗时约5-10秒)。 - 小数据量场景:三种方案差异不大,可根据代码可读性选择。
内容的提问来源于stack exchange,提问作者Matthias Schilling
相关产品推荐
相关产品推荐

