如何加速Pandas代码执行?百万级DataFrame查询优化求助
问题描述
我有一个包含1,917,866条记录的address_objects DataFrame,列与数据类型如下:
{'Id': dtype('int64'), 'ObjectId': dtype('int64'), 'ObjectGuid': dtype('O'), 'ChangeId': dtype('int64'), 'Name': dtype('O'), 'TypeName': dtype('O'), 'Level': dtype('int64'), 'OperationType': dtype('int64'), 'PrevId': dtype('int64'), 'NextId': dtype('int64'), 'UpdateDate': dtype('O'), 'StartDate': dtype('O'), 'EndDate': dtype('O'), 'IsActual': dtype('int64'), 'IsActive': dtype('int64'), 'changed_name': dtype('O')}
另有一个bests_paths字典,示例如下:
{'78545 78727 74726 86145 84657': 4, '78545 78727 81007 101612304': 4, '78545 78727 81007 101612305': 4, '78545 78727 81007 101612306': 3, '78545 78727 81007 101612307': 3}
字典的键是由上述DataFrame中ObjectId以空格分隔组成的字符串(对应某个邮寄地址),值为数字。
我的任务是根据字典的键从address_objects中获取对应地址,执行简单计算后将结果写入新DataFrame。但当前代码性能很差:处理815个字典键值对需约15秒,处理2000个需约40秒,无法满足需求。
当前代码如下:
for path, power in bests_paths.items(): path = list(map(int, path.split())) address = address_objects.loc[address_objects['ObjectId'].isin(path)] sort = address.sort_values(by=['IsActual', 'IsActive'], ascending=[False, False]) idx = sort['ObjectId'].drop_duplicates(keep='first').index address = sort.loc[idx, :] missing_components = a + b #some simple calculations address['Path'] = '.'.join(map(str, path)) address['PowerPath'] = power - missing_components address['MissingComponent'] = missing_components addresses_administrative_hierarchy = pd.concat([addresses_administrative_hierarchy, address])
性能瓶颈分析
- 循环内重复全表查询:每次循环都用
isin(path)扫描190万行的DataFrame,isin是线性扫描操作,815次循环就会重复815次全表扫描,这是最大性能消耗点。 - 重复排序与去重:每个循环都要对筛选出的小数据集做排序、去重,这类操作本身有计算开销,重复执行完全没必要。
- 多次
pd.concat拼接:每次循环都用concat生成新DataFrame,会触发内存复制,循环次数越多,内存开销和时间成本线性增长。 - 逐次修改临时DataFrame:每次循环给临时
address添加列,累积起来也会增加不必要的开销。
优化方案
1. 预建索引加速查询
给address_objects的ObjectId列建立索引,把单条记录查询速度从O(n)降到O(1):
address_objects = address_objects.set_index('ObjectId', drop=False)
2. 一次性提取所有需要的ObjectId
把bests_paths里所有ObjectId收集起来,一次性查询出所有相关记录,避免多次全表扫描:
# 收集所有ObjectId和对应的路径信息 all_object_ids = [] path_info_list = [] for path_str, power in bests_paths.items(): path = list(map(int, path_str.split())) all_object_ids.extend(path) # 计算当前路径的缺失组件数(替换成你实际的计算逻辑) missing_components = a + b path_dot = '.'.join(map(str, path)) # 给每个ObjectId绑定路径信息 for obj_id in path: path_info_list.append({ 'ObjectId': obj_id, 'Path': path_dot, 'PowerPath': power - missing_components, 'MissingComponent': missing_components }) # 转成DataFrame方便后续关联 path_info_df = pd.DataFrame(path_info_list) # 一次性查询所有相关地址记录 filtered_addresses = address_objects.loc[all_object_ids].copy()
3. 批量排序与去重
对一次性提取的所有记录,只做一次排序和去重,替代循环内的重复操作:
# 按优先级排序,确保有效记录排在前面 filtered_addresses_sorted = filtered_addresses.sort_values(by=['IsActual', 'IsActive'], ascending=[False, False]) # 全局去重,保留每个ObjectId的第一条有效记录 filtered_addresses_unique = filtered_addresses_sorted.drop_duplicates(subset='ObjectId', keep='first')
4. 关联路径信息生成最终结果
用merge把去重后的地址记录和路径信息关联,一次性得到最终DataFrame:
addresses_administrative_hierarchy = filtered_addresses_unique.merge(path_info_df, on='ObjectId', how='left')
额外优化提示
- 如果
missing_components的计算依赖路径长度,可提前在收集路径时计算,比如missing_components = len(path) - 某个固定值(根据实际逻辑调整)。 - 确保
a和b的计算是批量操作,不要放在循环里重复计算。
优化效果说明
这种方式把原来O(kn)的循环操作变成O(n + km)(k为字典键数量,m为单路径平均ObjectId数),所有查询、排序、去重只做一次,能大幅压缩处理时间,2000个键值对的处理时间可降到几秒以内。
内容的提问来源于stack exchange,提问作者Александр Попов
相关产品推荐
相关产品推荐

