Pandas DataFrame数据处理性能深度评测:iloc、at、loc与底层数组提取效率对比

夏知夏
夏知夏
发布时间:2026-10-14 | 6人浏览过
原创

本文针对500万行级别的大规模数据,系统对比了Pandas中iloc、at、apply及Series直接处理的速度差异。实测显示,iloc耗时约5小时,at优化至10分钟,而转为Series处理仅需37秒。此外,文章通过数值实验深入剖析了loc、at、iloc、iat及直接访问values数组在读取与修改场景下的性能表现,指出直接获取二维数组再定位是最高效方案,并提供了相应的代码实现与优化建议。

数据操作的核心通常涉及增加、删除、修改及差异计算,本文旨在对比几种常见写法在处理大量数据时产生的显著时间差异。

测试数据规模约为500万行,对应文件大小为100M。

1. iloc方法

iloc是一种速度极慢的操作方式。以下示例展示了如何对每个CSV文件中的每一行进行循环,并使用iloc进行处理:


for index in range(len(df)):
   df.iloc['attr'][index] = xxx

使用该方法处理500万行数据大约需要5个小时,效率非常低。

2. at方法

at方法相比iloc有显著的性能提升,同样基于for循环处理,具体示例代码如下:


for i in range(len(df)):
  if df.at[i,'attr'] > 0:
    sum_positive += df.at[i,'attr']
  else:
    sum_negetive += df.at[i,'sttr']

在我的程序环境中,at和iloc可以互换使用。采用at方法后,程序速度大幅提升至约10分钟,但仍未达到理想状态。

3. apply(lambda x:...)方法

考虑到for循环速度过慢,我尝试通过apply函数进行优化。网上有观点认为apply能大幅提升速度,但经过实际测试,在我的程序中,apply的性能与for循环相差无几,整体表现一般。

4. 直接使用Series处理

这才是真正优化for循环的有效途径。以上述at方法的程序为例,可改写为:


sum_positive += df['attr'][df.attr > 0].sum()
sum_negative += df['attr'][df.attr < 0].sum()

将程序全部改为Series处理后,运行速度显著加快。最终处理500万行数据仅需约37秒,基本符合预期目标。

此处简要讨论DataFrame的属性筛选,即上述df.attr > 0部分。Pandas的属性筛选功能强大且便捷。

其次,在进行属性筛选时,应避免直接修改属性本身,而应修改比较对象。例如,不建议这样编写代码:

float(df.attr )> 0

而应改为:df.attr > str(0),因为df.attr作为属性对象不宜随意变动。

补充:Pandas中DataFrame单个数据提取与修改效率分析

目标

在使用Pandas处理金融数据及建模时,常需按时间序列顺序遍历DataFrame数据,读取或修改特定位置的数据。经验表明,此类操作比直接处理二维列表或np.array格式数据慢得多,原因可能涉及index及columns层级的查找(两个字典结构,非连续数组,每次定位均需耗时)以及DataFrame内部数据的内存布局,后续有机会将深入研究。

此处进行一组数值实验,以对比几种方法的效率。

生成测试数据

首先生成一个包含随机数的二维数组作为DataFrame数据,为具有一般性,将列名和行名设置为标记顺序的字符串。


import numpy as np
import pandas as pd

from copy import deepcopy
from time import time

np.random.seed(20000)
I = 900
df = pd.DataFrame(np.random.standard_normal((I, I)),
   columns=['c'+str(_) for _ in range(I)],
         index=['i'+str(_) for _ in range(I)])

随后在限定范围内随机生成取值位置,为便于对比,将随机坐标与字符串名称对应起来。


columns_num = np.floor(np.random.uniform(0, 1, I) * I).astype(int)
index_num = np.floor(np.random.uniform(0, 1, I) * I).astype(int)

columns_str = ['c'+str(_) for _ in columns_num]
index_str = ['i'+str(_) for _ in index_num]

读取性能测试

首先采用传统方法,直接通过columns及index中的名称进行定位:


t0 = time()
for m in columns_str:
  for n in index_str:
    c = df[m][n]
print(time()-t0)

6.789840459823608

先获取columns列名,再在values中取行坐标,速度有所提升:


t0 = time()
for m in columns_str:
  for n in index_num:
    c = df[m].values[n]
print(time()-t0)

1.9697318077087402

使用loc方法,其速度与直接通过columns及index名称定位相当:


t0 = time()
for m in columns_str:
  for n in index_str:
    c = df.loc[n, m]
print(time()-t0)

5.661889314651489

使用at方法,比loc稍快,因为loc支持切片操作:


t0 = time()
for m in columns_str:
  for n in index_str:
    c = df.at[m, n]
print(time()-t0)

3.3770089149475098

假设已知具体横纵坐标,我们进一步比较:

先从values开始取值,速度依然较慢,看来每次从df中提取values非常耗时:


t0 = time()
for m in columns_num:
  for n in index_num:
    c = df.values[n][m]
print(time()-t0)

6.041872024536133

尝试使用iloc,结果无明显区别:


t0 = time()
for m in columns_num:
  for n in index_num:
    c = df.iloc[n, m]
print(time()-t0)

6.103677034378052

使用iat进行对比,提升幅度不大,令人稍感失望:


t0 = time()
for m in columns_num:
  for n in index_num:
    c = df.iat[n, m]
print(time()-t0)

4.375299692153931

最后,最高效的方法仍是先获取二维数组再进行定位:


t0 = time()
b = df.values
for m in columns_num:
  for n in index_num:
    c = b[n][m]
print(time()-t0)

0.6402544975280762

修改性能测试

重复上述过程,将对应值修改为0作为简单测试,注意需备份原始数据:

通过columns及index中的名称进行定位:


df_backup = deepcopy(df)
t0 = time()
for m in columns_str:
  for n in index_str:
    df_backup[m][n] = 0.0
print(time()-t0)

41.99269938468933

先获取columns列名,再在values中取行坐标:


df_backup = deepcopy(df)
t0 = time()
for m in columns_str:
  for n in index_num:
    df_backup[m].values[n] = 0.0
print(time()-t0)

2.215076208114624

使用loc方法:


df_backup = deepcopy(df)
t0 = time()
for m in columns_str:
  for n in index_str:
    df_backup.loc[n, m] = 0.0
print(time()-t0)

134.39290761947632

使用at方法,在修改数值方面竟然比loc快很多:


df_backup = deepcopy(df)
t0 = time()
for m in columns_str:
  for n in index_str:
    df_backup.at[n, m] = 0.0
print(time()-t0)

4.7453413009643555

在values上直接修改,效果也不错,与读取速度相近,看来主要耗时仍在于每次提取values:


df_backup = deepcopy(df)
t0 = time()
for m in columns_num:
  for n in index_num:
    df.values[n][m] = 0.0
print(time()-t0)

6.346027612686157

使用iloc方法:


df_backup = deepcopy(df)
t0 = time()
for m in columns_num:
  for n in index_num:
    df.iloc[n, m] = 0.0
print(time()-t0)

122.33384037017822

使用iat方法:


df_backup = deepcopy(df)
t0 = time()
for m in columns_num:
  for n in index_num:
    df.iat[n, m] = 0.0
print(time()-t0)

5.381632328033447

先获取二维数组再进行定位:


df_backup = deepcopy(df)
t0 = time()
b = df.values
for m in columns_num:
  for n in index_num:
    c = b[n][m]
print(time()-t0)

0.4298992156982422

总结

从效率角度看,直接获取数值是最优方案,此次系统性比较旨在记录。代码编写略显繁琐,但便于复制实验。在建模级别的代码中,我倾向于使用第2种方法,主要基于代码的可读性、维护性和修改便利性。代码中的key能明确指示数据含义,直观易读。

此前为提高运行效率,也曾尝试先提取二维数组,但当columns较多时操作繁琐,重新读取还需转换。当然,也可将数据封装为类,但感觉与Pandas融合度不佳,从建模和研究效率来看,尚未找到完美解决方案。后续将寻找时间深入研究DataFrame内部机制。

以上为个人经验总结,希望能提供参考,也希望大家多多支持本站。如有错误或未考虑周全之处,欢迎批评指正。

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表