如何在pandas中对大型DataFrame高效应用多参数函数实现关联匹配
性能瓶颈根因
- 你当前的写法是双层Python原生循环:外层遍历30万行df1,内层遍历4万行df2,最坏情况要执行120亿次判断,Python解释器执行循环的效率本身极低,这个数据量跑几天都属于正常情况
- 循环内反复调用
pd.to_datetime()做日期转换,存在大量无意义的重复计算 - 逐行用
iat取值、赋值的写法完全没用到pandas底层C实现的向量化优化,所有计算都跑在低速的Python层,没有发挥pandas的性能优势。
高效实现方案
首先第一步,提前统一做日期类型转换,全流程只转换一次,避免重复计算:
import pandas as pd # dayfirst参数适配dd-mm-yyyy的日期格式,根据你的实际格式调整即可 df1['date'] = pd.to_datetime(df1['date'], dayfirst=True) df2['start_date'] = pd.to_datetime(df2['start_date'], dayfirst=True) df2['end_date'] = pd.to_datetime(df2['end_date'], dayfirst=True)
根据你使用的pandas版本选下面任意一种方案即可,30万行规模的数据都能在数秒到十几秒内跑完,比原循环方案快1000倍以上。
方案1:pandas 2.2+ 原生条件连接(写法最直观)
pandas 2.2版本开始原生支持非等值连接,直接编写匹配规则即可,全程走向量化运算:
# 先按ID做等值连接,再过滤符合日期区间的记录 matched = df1.merge(df2, on='ID', how='left').query('start_date <= date <= end_date') # 把匹配结果合并回原表,填充未匹配记录的默认值 df1 = df1.merge(matched[['ID', 'date', 'status']], on=['ID', 'date'], how='left') df1['status'] = df1['status'].fillna('Not Found')
注意:如果单个ID下df2的记录数特别多,等值连接会产生较大的中间笛卡尔积,内存不足的话优先选下面的方案2。
方案2:merge_asof 时序匹配(兼容所有版本,性能最高)
merge_asof是pandas专为时序匹配设计的API,底层用二分查找实现,不会产生大的中间表,内存占用低、速度最快,是这类区间匹配场景的最优解:
# merge_asof要求提前对匹配键、排序键做排序 df1 = df1.sort_values(by=['ID', 'date']).reset_index(drop=True) df2 = df2.sort_values(by=['ID', 'start_date']).reset_index(drop=True) # 按ID分组,匹配每个日期之前最近的start_date对应的记录 df1 = pd.merge_asof( df1, df2, left_on='date', right_on='start_date', by='ID', direction='backward' ) # 排除日期超出end_date的无效匹配,填充默认值 df1.loc[df1['date'] > df1['end_date'], 'status'] = 'Not Found' df1['status'] = df1['status'].fillna('Not Found')
避坑提醒
- 不要用
df.apply()加自定义函数的写法,本质还是Python层逐行循环,性能比原生向量化API差几十上百倍 - 如果df2里存在同一个ID下的日期区间重叠,先提前做去重,保留你需要的那条记录,避免匹配后出现重复行
- 日期格式转换一定要提前一次性做完,不要写在匹配逻辑里重复计算
内容的提问来源于stack exchange,提问作者Diego Luchetti
相关产品推荐
相关产品推荐

