Pandas遍历1.5万行DataFrame过慢问题优化求助
问题描述
我从Excel文件读取了一个包含15000条记录、20列的pandas DataFrame,使用以下代码读取耗时约4.13秒,当前pandas版本为2.0.2。
df = pd.read_excel(excel_path, sheet_name='Sheet 1', header=[2, 3]).astype(object).replace(np.nan, 'None')
我通过for循环遍历df.iloc,将每行数据转换为键名自定义的字典并组成列表,示例代码如下:
data = [] for i in df.iloc: mydict = {} mydict['col1'] = i['Column 1 Name'].values[0] mydict['col2'] = i['Column 2 Name'].values[0] mydict['doc_date'] = datetime.datetime.strftime(i['Doc Details']['Doc Date'], r'%d-%m-%Y') \ if isinstance(i['Doc Details']['Doc Date'], datetime.datetime) \ else i['Doc Details']['Doc Date'].replace('/', '-') # 17 more columns data.append(mydict)
该循环耗时约72秒,仅涉及键名修改和日期格式处理,无其他复杂逻辑。
请问:
- 如何更快地遍历DataFrame并生成指定格式的字典列表?
- 为何pandas读取同等数据仅需4秒,而遍历却耗时72秒?
注:需保留「每行对应一个字典、键名自定义」的输出格式,不能修改前后端交互契约。
解答
1. 提速遍历生成字典列表的方法
方法一:矢量化预处理+批量转字典
先对所有列做批量处理(重命名、日期格式化),再直接转字典列表,这是效率最高的方式:
import pandas as pd import datetime # 定义原多级列名到自定义键名的映射 col_mapping = { ('Column 1 Name', ''): 'col1', ('Column 2 Name', ''): 'col2', ('Doc Details', 'Doc Date'): 'doc_date', # 补充剩余17列的映射关系 } # 重命名列 df_renamed = df.rename(columns=col_mapping) # 批量格式化日期列 def format_date(x): if isinstance(x, datetime.datetime): return x.strftime('%d-%m-%Y') elif isinstance(x, str): return x.replace('/', '-') return x # 兼容异常数据 df_renamed['doc_date'] = df_renamed['doc_date'].apply(format_date) # 直接转成字典列表,orient='records'会按行生成字典 data = df_renamed.to_dict(orient='records')
方法二:用itertuples替代iloc遍历
如果必须保留逐行处理逻辑,itertuples返回的是轻量命名元组,访问开销远低于iloc生成的Series:
data = [] # index=False跳过行索引,只返回数据列 for row in df.itertuples(index=False): mydict = {} # 按列的位置取值(对应原多级索引的列顺序) mydict['col1'] = row[0] mydict['col2'] = row[1] # 处理日期:假设原('Doc Details', 'Doc Date')是第n列 doc_date_val = row[n] if isinstance(doc_date_val, datetime.datetime): mydict['doc_date'] = doc_date_val.strftime('%d-%m-%Y') else: mydict['doc_date'] = doc_date_val.replace('/', '-') # 补充其他列的赋值逻辑 data.append(mydict)
方法三:用swifter自动优化apply(可选)
如果部分列需要复杂自定义处理,swifter会自动选择矢量化或并行apply的最优方式:
import swifter df_renamed['doc_date'] = df_renamed['doc_date'].swifter.apply(format_date) data = df_renamed.to_dict(orient='records')
2. 读取与遍历耗时差异的原因
- 底层实现差异:
pd.read_excel依赖的openpyxl/xlrd库大部分逻辑是C/C++实现的矢量化批量操作,避开了Python解释器的循环开销;而你的逐行遍历完全在Python层面执行,每次迭代都要创建Series对象、处理多级索引取值,15000次循环的累计开销极大。 - 数据类型影响:读取时用
.astype(object)把所有列转为object类型,让pandas失去了对数值、日期类型的矢量化优化能力,逐行访问时的类型检查、转换开销进一步放大。 - 操作粒度差异:读取是一次性批量加载解析数据,而遍历是逐行做零散的键值赋值和格式转换,单步操作的重复开销被多次循环放大。
内容的提问来源于stack exchange,提问作者AllSolutions
相关产品推荐
相关产品推荐

