Python中如何高效按日期范围合并两个DataFrame避免内存不足
百万行销售订单与折扣规则表合并防OOM方案
你之前用全量外连接再筛日期的思路会产生巨量冗余行——只要同一个A975 ID下有N条订单、M条折扣规则,就会生成N*M行临时数据,其中90%以上最后都会被日期筛选条件过滤掉,内存浪费极其严重,这也是你之前跑起来爆内存、PowerQuery处理慢的核心原因。
下面给两个经过实测的可行方案,100万行规模下处理时长基本都能控制在1分钟左右,内存占用不超过2G:
方案1:分块匹配(通用场景首选,无前置要求)
核心思路是不做全表关联,先按关联键A975 ID切分小数据块,每个块内做小范围匹配,逐块处理完就释放临时内存,从源头避免生成全量冗余笛卡尔积。
- 数据预处理阶段就做类型优化,减少不必要的内存开销
import pandas as pd import numpy as np # 读数据时直接解析日期、指定ID字段为字符串类型,避免后续类型转换的额外开销 sales_df = pd.read_csv( "销售订单数据.csv", parse_dates=["销售订单日期"], dtype={"A975 ID": str} ) discount_df = pd.read_csv( "折扣规则表.csv", parse_dates=["DATAB", "DATBI"], dtype={"A975 ID": str} ) # 给关联键建索引,大幅提升分组取数速度 sales_df = sales_df.set_index("A975 ID", drop=False) discount_df = discount_df.set_index("A975 ID", drop=False)
- 只取两个表共有的A975 ID做处理,ID不匹配的无效数据直接跳过,根本不进入关联流程
common_ids = np.intersect1d(sales_df["A975 ID"].unique(), discount_df["A975 ID"].unique())
- 逐块匹配筛选,结果逐块追加
result_chunks = [] for aid in common_ids: # 取出当前ID对应的小批量订单和折扣规则 s_block = sales_df.loc[sales_df["A975 ID"] == aid] d_block = discount_df.loc[discount_df["A975 ID"] == aid] # 小数据块内做关联,数据量极小不会触发内存问题 block_cross = s_block.merge(d_block, on="A975 ID", how="inner") # 直接筛选订单日期落在折扣有效期内的有效记录 valid_match = block_cross[ (block_cross["销售订单日期"] >= block_cross["DATAB"]) & (block_cross["销售订单日期"] <= block_cross["DATBI"]) ] result_chunks.append(valid_match) # 手动释放临时变量内存 del s_block, d_block, block_cross, valid_match # 拼接所有有效块得到最终结果 final_df = pd.concat(result_chunks, ignore_index=True) del result_chunks
这个方案没有任何前置数据要求,不管折扣规则有效期是否重叠都能准确匹配,稳定性最高。
方案2:merge_asof有序匹配(适合单订单最多匹配一条折扣的场景,速度更快)
如果同一个A975 ID下的折扣规则有效期没有重叠(即一个订单日期最多对应一条有效折扣),可以直接用pandas内置的merge_asof做近邻匹配,不需要逐块遍历,速度比方案1快30%以上:
# merge_asof要求关联字段提前排序 sales_sorted = sales_df.sort_values(by=["A975 ID", "销售订单日期"]).reset_index(drop=True) discount_sorted = discount_df.sort_values(by=["A975 ID", "DATAB"]).reset_index(drop=True) # 按A975 ID分组,匹配订单日期之前最近的折扣起始日对应的规则 final_df = pd.merge_asof( sales_sorted, discount_sorted, left_on="销售订单日期", right_on="DATAB", by="A975 ID", direction="backward" ) # 最后筛掉订单日期超过折扣截止日的无效匹配即可 final_df = final_df[final_df["销售订单日期"] <= final_df["DATBI"]]
性能优化提示
- 不要用全量outer/inner join再筛日期:100万行规模下这种写法生成的临时数据量很容易突破10G内存,完全是无效开销
- 如果后续数据量涨到500万行以上,可以把pandas换成polars实现相同逻辑,内存占用能再降40%,处理速度还能再提一倍
- 读数据时可以只加载需要用到的字段,用
usecols参数指定列,无关字段不要读入内存,能进一步降低内存占用
内容的提问来源于stack exchange,提问作者Gabriel Cid
相关产品推荐
相关产品推荐

