Pandas差异行批量处理性能问题排查与优化咨询
回答
1. 如何排查代码中导致运行缓慢的语句?
咱们得先找准瓶颈在哪,常用的排查方法有这几个:
- 逐行计时测试:如果用Jupyter Notebook,直接用
%timeit魔法命令单独测循环里的每一步——比如测prepped_net_df.iloc[val]、给Series加Source标签,还有最关键的row_compare_df.append(net_series),很快就能发现哪个步骤拖慢了速度。 - 行级性能分析:用
line_profiler库,给你的代码套上@profile装饰器,运行后能看到每一行代码的执行时间占比,精准定位慢代码。 - 结合Pandas特性判断:Pandas的
append是非原地操作,每次调用都会创建新的DataFrame,把旧数据+新数据复制一遍——循环里反复干这个,数据量越大,复制次数越多,时间复杂度直接变成O(n²),这大概率就是你代码慢的核心原因。
2. 如何优化代码以适配大数据量场景?
原代码的问题根源就是「循环+反复append」,咱们直接改成批量提取+一次性合并的方式,效率能提升好几个数量级,具体优化如下:
优化思路:
- 批量提取两个DataFrame里的所有差异行,不用循环逐个取;
- 给每个提取后的子DataFrame统一加
Source标签列; - 用
pd.concat一次性合并两个子DataFrame,替代循环里的append。
优化后的代码:
import pandas as pd import time # 原数据定义(保留不变) net_dict = {'ACCTG_DATE': ['2012-01-01 00:00:00', '2012-01-02 00:00:00', '2012-01-03 00:00:00', '2012-01-04 00:00:00' ], 'JRNL_ID_NO': ['00349-CAS','00350-CAS','00351-CAS','00352-CAS' ], 'JRNL_SEQ_NO': [43970,43971,43972,43973], 'ACCT_CODE': [8500016,8500017,8500018,8500019], 'BAL_BOOK_CODE': [8591,8592,8593,8594], 'PROD_CODE': ['12F7', '12F8', '12F9', '12G0'], 'SUSPENSE_SEQ_NO': [0, 1, 2, 3 ], 'TRAN_AMT': [8900.29, 8901.29, 8902.29, 8903.29], 'CENTER_CODE': ['', '', '', ''], 'BASIS_TYPE': ['C', 'C', 'C', 'C'], 'UPDATE_TSTP':['2011-12-31 00:00:00', '2012-01-01 00:00:00', '2012-01-02 00:00:00', '2012-01-03 00:00:00']} ora_dict = {'ACCTG_DATE': ['2012-01-01 00:00:00', '2012-01-02 00:00:00', '2012-01-04 00:00:00', '2012-01-04 00:00:00' ], 'JRNL_ID_NO': ['00349-CAS','00350-CAS','00351-CAS','00353-CAS' ], 'JRNL_SEQ_NO': [43970,43971,43972,43973], 'ACCT_CODE': [8500016,8500017,8500018,8500019], 'BAL_BOOK_CODE': [8591,8592,8593,8594], 'PROD_CODE': ['12F7', '12F8', '12F9', '12G0'], 'SUSPENSE_SEQ_NO': [0, 1, 2, 3 ], 'TRAN_AMT': [8900.29, 8901.29, 8903, 8903.29], 'CENTER_CODE': ['', '', '', ''], 'BASIS_TYPE': ['C', 'C', 'C', 'C'], 'UPDATE_TSTP':['2011-12-31 00:00:00', '2012-01-01 00:00:00', '2012-01-02 00:00:00', '2012-01-03 00:00:00']} different_rows = [2, 3] prepped_net_df = pd.DataFrame(data=net_dict) prepped_ora_df = pd.DataFrame(data=ora_dict) # 可选:类型推断提前做一次即可,不用循环重复操作 prepped_net_df = prepped_net_df.infer_objects() prepped_ora_df = prepped_ora_df.infer_objects() row_compare_df = pd.DataFrame() if different_rows: # 直接判断列表非空,无需和None比较 start = time.perf_counter() # 替换Python 3.3+已废弃的time.clock() # 批量提取差异行并添加Source标签 net_diff_rows = prepped_net_df.iloc[different_rows].copy() net_diff_rows['Source'] = 'Netezza' ora_diff_rows = prepped_ora_df.iloc[different_rows].copy() ora_diff_rows['Source'] = 'Oracle' # 一次性合并两个子DataFrame row_compare_df = pd.concat([net_diff_rows, ora_diff_rows], ignore_index=True) end = time.perf_counter() - start print(f"Cell has run completely. It took {round(end, 2)} seconds") else: print("There were no rows reported with differences") print(row_compare_df)
优化生效的核心原因:
- 避免循环开销:批量提取
iloc[different_rows]是Pandas内部向量化操作,比循环逐个取行快得多; - 替换append为concat:
pd.concat是批量合并,只需要一次数据复制,时间复杂度为O(n);而循环append每次都要复制全部已有数据,时间复杂度是O(n²),数据量越大,效率差距越夸张; - 更准确的计时:用
time.perf_counter()替代了已废弃的time.clock(),计时精度更高。
额外优化建议:
- 如果
different_rows是超大列表,可以转成NumPy数组(np.array(different_rows)),Pandas处理数组索引比纯列表更快; - 如果不需要
infer_objects(),可以直接去掉这个步骤,减少不必要的类型推断开销; - 内存充足的情况下,可以提前给
Source列分配空间,不过上面的方法已经足够高效。
内容的提问来源于stack exchange,提问作者hfrog713
相关产品推荐
相关产品推荐

