高效遍历Pandas DataFrame的方法

深入探讨遍历Pandas DataFrame的多种方法及其性能对比,推荐使用字典与数组方法。

原文标题:Pandas中高效的“For循环”

原文作者:数据派THU

冷月清谈:

本文探讨了四种遍历Pandas DataFrame的方法,分析了它们的性能,包括‘iterrows()’、‘itertuples()’、字典和数组方法。通过生成600万行的测试数据,我们实测了每种方法的运行时间,发现使用‘iterrows()’的性能最低,而将DataFrame转为字典和数组的方式效率最高,前者最快可达31秒,比‘iterrows()’快近11倍。文章还指出,尽管在小数据集上快速for循环可以代表一种选择,但在处理大型数据时,矢量化操作是最佳实践,能大幅降低运行时间。

怜星夜思:

1、Pandas中为何‘iterrows()’会如此慢?是否有其他优化的方式?
2、在处理大型数据集时,您更倾向于使用矢量化还是快速for循环?为什么?
3、除了文章提到的方法,您还知道哪种高效遍历Pandas DataFrame的技能?

原文内容

来源:DeepHub IMBA

本文约1500字,建议阅读5分钟

本文将探索遍历pandas dataframe的各种方法,检查每个循环方法的相关运行时


循环是我们编程技能中的一项固有技能。当我们熟悉任何编程语言时,循环就会成为一个基本的、易于解释的概念。

在这篇博文中,我们将探索遍历pandas dataframe的各种方法,检查每个循环方法的相关运行时。为了验证循环的有效性,我们将生成百万级别的数据,这也是我们在日常处理中经常遇到的数量级。


实验数据集


我们将生成一个包含600万行和4列的DataFrame。每一列将被分配一个0到50之间的随机整数。

import numpy as np
import pandas as pd
df = pd.DataFrame(np.random.randint(0, 50, size=(6000000, 4)), columns=('a','b','c','d'))
df.shape
# (6000000, 5)
df.head()

Iterrows


我们通过基于以下标准引入一个新的列' e '来扩展数据框架' df ':
如果' a '等于0,那么' e '取' d '的值。如果' a '在0(不包括)到25(包括)的范围内,' e '计算为' b '减去' c '。如果以上条件都不成立,则计算“e”为“b”+“c”。

首先我们使用pandas提供的' iterrows() '函数遍历DataFrame ' df '。' iterrows() '函数遍历DataFrame的行,在迭代期间返回(index, row)对。

import time
start = time.time()
# Iterating through DataFrame using iterrows
for idx, row in df.iterrows():
if row.a == 0:
df.at[idx,'e'] = row.d

elif (row.a <= 25) & (row.a > 0):
df.at[idx,‘e’] = (row.b)-(row.c)
else:
df.at[idx,‘e’] = row.b + row.c
end = time.time()
print(end - start)

time taken: 335.212792634964


iterrows()函数需要335秒(约5.5分钟)来实现对600万行的操作。

Itertuples


另一种遍历pandas DataFrame的方法是使用' itertuples ',它以命名元组的形式遍历DataFrame行。

下面代码说明了如何使用' itertuples '访问元素。生成的行对象将索引作为第一个字段,然后是数据框的列。

for row in df[:1].itertuples():
print(row) ## accessing the complete row - index following by columns
print(row.Index) ## accessing the index of the row
print(row.a) ## accessing the value of column 'a'
图片

使用下面的代码,使用itertuples()遍历DataFrame df。

start = time.time()
# Iterating through namedtuples
for row in df.itertuples():
if row.a == 0:
df.at[row.Index,'e'] = row.d

elif (row.a <= 25) & (row.a > 0):
df.at[row.Index,‘e’] = (row.b)-(row.c)
else:
df.at[row.Index,‘e’] = row.b + row.c

end = time.time()
print(end - start)

Time taken: 41 seconds


在DataFrame上执行所需的操作,itertuples()函数耗时约54秒,比iterrows()函数快6倍。

字典


迭代DataFrame行的另一种方法是将DataFrame转换为字典,这是一种轻量级的内置数据类型。我们遍历该字典以执行所需的操作,然后将更新后的字典转换回DataFrame。转换可以使用' to_dict() '函数来实现。

start = time.time()
# converting the DataFrame to a dictionary
df_dict = df.to_dict('records')
# Iterating through the dictionary
for row in df_dict[:]:
if row['a'] == 0:
row['e'] = row['d']

elif row[‘a’] <= 25 & row[‘a’] > 0:
row[‘e’] = row[‘b’]-row[‘c’]
else:
row[‘e’] = row[‘b’] + row[‘c’]

converting back to DataFrame

df4 = pd.DataFrame(df_dict)
end = time.time()
print(end - start)

Time taken: 31 seconds


字典方法大约需要31秒,大约比' itertuples() '函数快11倍。

数组列表


我们还可以将DataFrame转换为一个数组,遍历该数组以对每行(存储在列表中)执行操作,然后将该列表转换回DataFrame。

start = time.time()
# create an empty dictionary
list2 = []
# intialize column having 0s.
df['e'] = 0
# iterate through a NumPy array
for row in df.values:
if row[0] == 0:
row[4] = row[3]

elif row[0] <= 25 & row[0] > 0:
row[4] = row[1]-row[2]

else:
row[4] = row[1] + row[2]

append values to a list

list2.append(row)

convert the list to a dataframe

df2 = pd.DataFrame(list2, columns=[‘a’, ‘b’, ‘c’, ‘d’,‘e’])
end = time.time()
print(end - start)
#Time Taken: 21 seconds


花费的时间约为21秒(比iterrows快16倍),这与遍历字典所花费的时间非常接近。

字典和数组是内置的轻量级数据结构,因此迭代DataFrame所需的时间最少。


总结


在文探索了使用循环遍历DataFrame的四种不同方法。

' iterrows '函数在遍历DataFrame时显示出最高的时间消耗。与“iterrows”函数相比,使用“itertuples”函数可以使DataFrame迭代的速度提高6倍。在字典和数组上迭代被证明是最有效的方法,使用循环提供最快的迭代时间和最佳的数据操作。

当然,在处理大型数据集时,最佳实践是矢量化。向量化上述代码将执行时间减少到0.29秒(比遍历数组快72倍)。但是使用矢量化时会增加开发的成本,所以在一些时候为了我们开发方便,可以选择一个比较快速for循环来替代矢量化。当然,如果你对矢量化非常的了解,那还是推荐继续使用。

作者:Anmol Tomar
编辑:黄继彦



关于我们

数据派THU作为数据科学类公众号,背靠清华大学大数据研究中心,分享前沿数据科学与大数据技术创新研究动态、持续传播数据科学知识,努力建设数据人才聚集平台、打造中国大数据最强集团军。



新浪微博:@数据派THU

微信视频号:数据派THU

今日头条:数据派THU

我个人认为矢量化是首选,因为它充分利用了底层C语言的速度,能快速处理计算。但对于开发中的小调整,快速for循环更灵活,适合快速原型开发。

根据具体情况吧!如果开发初期,数据量不是特别大,快速for循环可能会让调试和维护更容易,但一旦数据规模上升,矢量化一定是必须的选择。

哈哈,如果是在轻度数据分析的时候,我会先用for循环简单快速试错,但等真要上线再考虑矢量化实现!

‘iterrows()’在每次迭代时会返回一个Series对象,而Series的构造和索引访问都比较耗时。为了优化,可以考虑直接使用NumPy数组进行操作,避免频繁的链式访问。

我认为除了用NumPy,可以考虑其他库,比如Dask来处理大数据集,Dask能够并行处理,并且语法和Pandas很类似。

另外,不要忽视矢量化操作,它对性能提升有巨大帮助。虽然开发成本可能增加,但长远来看,具有更好的可维护性和性能。

我之前使用过apply()方法,它通过将函数应用于DataFrame的行列来进行操作,通常效率挺高的,但对函数的实现要求稍高。

有个建构函数的方法,也不错,用numpy.frompyfunc()可以将Python函数映射到numpy的操作中,效率还是可以的。

其实还有另一种思路,结合Cython来编译一些关键的计算逻辑,能够大幅度提升速度哦!这是一种虽然复杂但值得尝试的路径。