如何优化Pandas中的不等值连接?日期范围关联性能优化
优化类交叉连接的日期范围展开操作
问题分析
原代码的核心性能瓶颈在于循环中反复调用pd.concat——每次concat都会创建新DataFrame并复制数据,随着日期范围扩大,时间复杂度会急剧上升(近似O(n²))。同时每日单独筛选的操作没有利用pandas的向量化优势,导致大数据集下完全无法高效运行。
高效向量化解决方案
以下两种实现方式完全避免循环,性能提升几个数量级,且代码更简洁优雅:
方法一:逐行生成日期序列后展开(内存友好型)
核心思路是为每条有效记录生成其覆盖的、且在目标日期范围内的所有日期,再将日期序列展开与原记录合并:
from datetime import datetime import pandas as pd beginning = pd.to_datetime('14/09/2021', dayfirst=True) today = pd.to_datetime(datetime.today()) date_range = pd.date_range(start=beginning, end=today) date_min, date_max = date_range.min(), date_range.max() block = pd.DataFrame( {'Identifier': ['4913151F', 'F4E9124A', '31715888', 'D0C57FCA', '57B4D7EB', 'E46F1E5D', '99E0A2F8', 'D77E342E', 'C596D233', 'D0EED63F', 'D0C57FCA'], 'start_date': ['03/11/2020', '05/07/2022', '22/12/2016', '17/03/2024', '14/10/2022', '08/08/2022', '04/11/2020', '13/03/2023', '05/11/2021', '12/27/2022', '13/06/2022'], 'end_date': ['11/07/2023', '11/04/2023', '14/12/2018', '20/01/2025', '15/06/2023', '09/01/2023', '16/07/2022', '19/05/2024', '24/09/2022', '17/11/2023', '13/06/2023'], 'color': ['red', 'green', 'magenta', 'yellow', 'light_blue', 'dark_blue', 'black', 'white', 'pink', 'orange', 'yellow']}) # 转换日期格式并去重 block.start_date = pd.to_datetime(block.start_date, dayfirst=True, format='mixed') block.end_date = pd.to_datetime(block.end_date, dayfirst=True, format='mixed') block_uniques = block.drop_duplicates(['Identifier', 'start_date']) # 提前过滤完全不在目标日期范围内的记录,减少计算量 mask = (block_uniques.end_date >= date_min) & (block_uniques.start_date <= date_max) filtered = block_uniques[mask].copy() # 为每条记录生成交集日期序列:取记录起止日期与目标范围的重叠部分 filtered['Record_Date'] = filtered.apply( lambda row: pd.date_range(start=max(row.start_date, date_min), end=min(row.end_date, date_max), freq='D'), axis=1 ) # 展开日期序列并整理结果 frame = filtered.explode('Record_Date').reset_index(drop=True) frame = frame.sort_values(['Record_Date', 'Identifier']).reset_index(drop=True) print(frame)
方法二:全交叉连接+布尔筛选(代码简洁型)
核心思路是先做日期与记录的全交叉连接,再通过向量化布尔索引筛选符合条件的记录,适合日期和记录数不是极端庞大的场景:
from datetime import datetime import pandas as pd beginning = pd.to_datetime('14/09/2021', dayfirst=True) today = pd.to_datetime(datetime.today()) date_range = pd.date_range(start=beginning, end=today) # 创建日期DataFrame dates_df = pd.DataFrame({'Record_Date': date_range}) block = pd.DataFrame( {'Identifier': ['4913151F', 'F4E9124A', '31715888', 'D0C57FCA', '57B4D7EB', 'E46F1E5D', '99E0A2F8', 'D77E342E', 'C596D233', 'D0EED63F', 'D0C57FCA'], 'start_date': ['03/11/2020', '05/07/2022', '22/12/2016', '17/03/2024', '14/10/2022', '08/08/2022', '04/11/2020', '13/03/2023', '05/11/2021', '12/27/2022', '13/06/2022'], 'end_date': ['11/07/2023', '11/04/2023', '14/12/2018', '20/01/2025', '15/06/2023', '09/01/2023', '16/07/2022', '19/05/2024', '24/09/2022', '17/11/2023', '13/06/2023'], 'color': ['red', 'green', 'magenta', 'yellow', 'light_blue', 'dark_blue', 'black', 'white', 'pink', 'orange', 'yellow']}) block.start_date = pd.to_datetime(block.start_date, dayfirst=True, format='mixed') block.end_date = pd.to_datetime(block.end_date, dayfirst=True, format='mixed') block_uniques = block.drop_duplicates(['Identifier', 'start_date']) # 添加临时key实现全交叉连接 dates_df['key'] = 1 block_uniques['key'] = 1 cross_join = pd.merge(dates_df, block_uniques, on='key').drop('key', axis=1) # 向量化筛选符合日期范围的记录 frame = cross_join[(cross_join.Record_Date >= cross_join.start_date) & (cross_join.Record_Date <= cross_join.end_date)] # 排序并整理索引 frame = frame.sort_values(['Record_Date', 'Identifier']).reset_index(drop=True) print(frame)
性能对比与优化建议
- 原循环方案:1000天+1000条记录的场景下,运行时间可能超过10分钟
- 方法一:相同场景下运行时间通常在10秒以内,内存占用更优(仅生成必要日期)
- 方法二:代码更直观,适合中小规模数据集,若数据量过大可能占用较多内存
额外优化点:
- 始终确保日期列是
datetime64类型,利用pandas的向量化日期运算 - 提前过滤无效记录,减少后续计算量
- 避免不必要的深拷贝,使用
.copy(deep=False)减少内存开销
内容的提问来源于stack exchange,提问作者Gorgonzola
相关产品推荐
相关产品推荐

