pandas的iterrows有性能问题吗?
我发现使用pandas中的iterrows时性能非常差。
这是iterrows特有的问题吗?对于某些数据大小(我在处理200到300万行数据)应该避免使用这个函数吗?
在GitHub上的这个讨论让我觉得这是因为在数据框中混合了不同的数据类型,但下面这个简单的例子显示,即使只使用一种数据类型(float64),问题依然存在。在我的电脑上,这个操作花了36秒:
import pandas as pd
import numpy as np
import time
s1 = np.random.randn(2000000)
s2 = np.random.randn(2000000)
dfa = pd.DataFrame({'s1': s1, 's2': s2})
start = time.time()
i=0
for rowindex, row in dfa.iterrows():
i+=1
end = time.time()
print end - start
为什么像apply这样的向量化操作要快得多?我想这其中也一定有逐行迭代的过程。
我现在还搞不清楚在我的情况下如何不使用iterrows(这个我会留到以后再问)。所以我很想知道你们是否能一直避免这种迭代。我是在根据不同数据框中的数据进行计算。
我想运行的简化版本是:
import pandas as pd
import numpy as np
#%% Create the original tables
t1 = {'letter':['a','b'],
'number1':[50,-10]}
t2 = {'letter':['a','a','b','b'],
'number2':[0.2,0.5,0.1,0.4]}
table1 = pd.DataFrame(t1)
table2 = pd.DataFrame(t2)
#%% Create the body of the new table
table3 = pd.DataFrame(np.nan, columns=['letter','number2'], index=[0])
#%% Iterate through filtering relevant data, optimizing, returning info
for row_index, row in table1.iterrows():
t2info = table2[table2.letter == row['letter']].reset_index()
table3.ix[row_index,] = optimize(t2info,row['number1'])
#%% Define optimization
def optimize(t2info, t1info):
calculation = []
for index, r in t2info.iterrows():
calculation.append(r['number2']*t1info)
maxrow = calculation.index(max(calculation))
return t2info.ix[maxrow]
9 个回答
另一个选择是使用 to_records()
,这个方法比 itertuples
和 iterrows
都要快。
不过在你的情况下,还有很多其他的改进空间。
这是我最终优化后的版本:
def iterthrough():
ret = []
grouped = table2.groupby('letter', sort=False)
t2info = table2.to_records()
for index, letter, n1 in table1.to_records():
t2 = t2info[grouped.groups[letter].values]
# np.multiply is in general faster than "x * y"
maxrow = np.multiply(t2.number2, n1).argmax()
# `[1:]` removes the index column
ret.append(t2[maxrow].tolist()[1:])
global table3
table3 = pd.DataFrame(ret, columns=('letter', 'number2'))
基准测试:
-- iterrows() --
100 loops, best of 3: 12.7 ms per loop
letter number2
0 a 0.5
1 b 0.1
2 c 5.0
3 d 4.0
-- itertuple() --
100 loops, best of 3: 12.3 ms per loop
-- to_records() --
100 loops, best of 3: 7.29 ms per loop
-- Use group by --
100 loops, best of 3: 4.07 ms per loop
letter number2
1 a 0.5
2 b 0.1
4 c 5.0
5 d 4.0
-- Avoid multiplication --
1000 loops, best of 3: 1.39 ms per loop
letter number2
0 a 0.5
1 b 0.1
2 c 5.0
3 d 4.0
完整代码:
import pandas as pd
import numpy as np
#%% Create the original tables
t1 = {'letter':['a','b','c','d'],
'number1':[50,-10,.5,3]}
t2 = {'letter':['a','a','b','b','c','d','c'],
'number2':[0.2,0.5,0.1,0.4,5,4,1]}
table1 = pd.DataFrame(t1)
table2 = pd.DataFrame(t2)
#%% Create the body of the new table
table3 = pd.DataFrame(np.nan, columns=['letter','number2'], index=table1.index)
print('\n-- iterrows() --')
def optimize(t2info, t1info):
calculation = []
for index, r in t2info.iterrows():
calculation.append(r['number2'] * t1info)
maxrow_in_t2 = calculation.index(max(calculation))
return t2info.loc[maxrow_in_t2]
#%% Iterate through filtering relevant data, optimizing, returning info
def iterthrough():
for row_index, row in table1.iterrows():
t2info = table2[table2.letter == row['letter']].reset_index()
table3.iloc[row_index,:] = optimize(t2info, row['number1'])
%timeit iterthrough()
print(table3)
print('\n-- itertuple() --')
def optimize(t2info, n1):
calculation = []
for index, letter, n2 in t2info.itertuples():
calculation.append(n2 * n1)
maxrow = calculation.index(max(calculation))
return t2info.iloc[maxrow]
def iterthrough():
for row_index, letter, n1 in table1.itertuples():
t2info = table2[table2.letter == letter]
table3.iloc[row_index,:] = optimize(t2info, n1)
%timeit iterthrough()
print('\n-- to_records() --')
def optimize(t2info, n1):
calculation = []
for index, letter, n2 in t2info.to_records():
calculation.append(n2 * n1)
maxrow = calculation.index(max(calculation))
return t2info.iloc[maxrow]
def iterthrough():
for row_index, letter, n1 in table1.to_records():
t2info = table2[table2.letter == letter]
table3.iloc[row_index,:] = optimize(t2info, n1)
%timeit iterthrough()
print('\n-- Use group by --')
def iterthrough():
ret = []
grouped = table2.groupby('letter', sort=False)
for index, letter, n1 in table1.to_records():
t2 = table2.iloc[grouped.groups[letter]]
calculation = t2.number2 * n1
maxrow = calculation.argsort().iloc[-1]
ret.append(t2.iloc[maxrow])
global table3
table3 = pd.DataFrame(ret)
%timeit iterthrough()
print(table3)
print('\n-- Even Faster --')
def iterthrough():
ret = []
grouped = table2.groupby('letter', sort=False)
t2info = table2.to_records()
for index, letter, n1 in table1.to_records():
t2 = t2info[grouped.groups[letter].values]
maxrow = np.multiply(t2.number2, n1).argmax()
# `[1:]` removes the index column
ret.append(t2[maxrow].tolist()[1:])
global table3
table3 = pd.DataFrame(ret, columns=('letter', 'number2'))
%timeit iterthrough()
print(table3)
最终版本的速度几乎比原始代码快了10倍。这个策略是:
- 使用
groupby
来避免重复比较值。 - 使用
to_records
来访问原始的 numpy.records 对象。 - 在收集完所有数据之前,不要对 DataFrame 进行操作。
不要使用 iterrows!
...或者 iteritems
,或者 itertuples
。真的,不要。尽量让你的代码实现 向量化。如果你不相信我,可以问问 Jeff。
我承认在某些情况下,遍历一个数据框是有其合理性的,但相比于 iter*
这类函数,还有更好的选择,主要有:
很多初学 pandas 的人常常会问与 iterrows
相关的问题。由于这些新用户可能不太了解向量化的概念,他们会想当然地认为解决问题的代码需要用到循环或其他迭代方式。因为他们也不知道怎么迭代,通常会找到这个问题,结果学到的都是错误的东西。
支持论据
文档页面上关于迭代的部分有一个巨大的红色警告框,上面写着:
遍历 pandas 对象通常很慢。在很多情况下,手动遍历行并不是必要的 [...]。
如果这还不能说服你,可以看看我在这里发的关于向量化和非向量化技术在添加两列 "A + B" 时的性能比较。

基准测试代码,供你参考。iterrows
是最糟糕的选择,值得一提的是,其他迭代方法也没好到哪里去。
底部的那一行测量的是用 numpandas 写的一个函数,这是一种与 NumPy 深度结合的 Pandas 风格,以获得最佳性能。除非你知道自己在做什么,否则应该避免写 numpandas 代码。尽量使用 API(也就是说,优先选择 vec
而不是 vec_numpy
)。
总结
总是要尽量实现向量化。有时候,根据你的问题或数据的性质,这并不总是可能,所以要寻找比 iterrows
更好的迭代方法。除了在处理极少行时的方便外,几乎没有合理的使用场景,否则你可能会面临代码运行几个小时的等待。
查看下面的链接,找出解决你代码的最佳方法/向量化例程。
10分钟了解 pandas,以及 基本功能介绍 - 有用的链接,帮助你了解 Pandas 及其向量化*/cython 化函数库。
提升性能 - 文档中关于提升标准 Pandas 操作的介绍。
这是解决你问题的方法。这个方法是完全向量化的。
In [58]: df = table1.merge(table2,on='letter')
In [59]: df['calc'] = df['number1']*df['number2']
In [60]: df
Out[60]:
letter number1 number2 calc
0 a 50 0.2 10
1 a 50 0.5 25
2 b -10 0.1 -1
3 b -10 0.4 -4
In [61]: df.groupby('letter')['calc'].max()
Out[61]:
letter
a 25
b -1
Name: calc, dtype: float64
In [62]: df.groupby('letter')['calc'].idxmax()
Out[62]:
letter
a 1
b 2
Name: calc, dtype: int64
In [63]: df.loc[df.groupby('letter')['calc'].idxmax()]
Out[63]:
letter number1 number2 calc
1 a 50 0.5 25
2 b -10 0.1 -1
Numpy和pandas中的向量操作比普通Python中的标量操作要快得多,原因有几个:
类型查找的节省:Python是一种动态类型的语言,这意味着在处理数组中的每个元素时都会有额外的时间开销。但是,Numpy(以及pandas)是在C语言中进行计算的(通常通过Cython)。数组的类型只在开始迭代时确定;光这一点就能节省不少时间。
更好的缓存利用:在C数组上进行迭代时,缓存的使用效率很高,因此速度非常快。pandas的DataFrame是一个“列导向的表”,这意味着每一列实际上就是一个数组。所以你在DataFrame上可以进行的原生操作(比如对某一列的所有元素求和)几乎不会出现缓存失效的情况。
更多的并行处理机会:简单的C数组可以通过SIMD指令进行操作。Numpy的某些部分支持SIMD,这取决于你的CPU和安装方式。虽然并行处理的好处没有静态类型和更好缓存的效果那么明显,但仍然是一个不错的提升。
总结一下:使用Numpy和pandas中的向量操作。它们比Python中的标量操作快,原因很简单,这些操作本来就是C程序员手动编写的(而且数组的写法比嵌入SIMD指令的显式循环要容易得多)。
一般来说,iterrows
只应该在非常特定的情况下使用。以下是不同操作性能的优先顺序:
- 向量化
- 使用自定义的 Cython 例程
- 应用
apply
- 可以在 Cython 中执行的简化操作
- 在 Python 中的迭代
itertuples
iterrows
- 逐行更新一个空的框架(例如,使用 loc 一次更新一行)
使用自定义的 Cython 例程通常太复杂,所以我们暂时不讨论这个。
向量化 总是 是首选和最佳选择。不过,有一小部分情况(通常涉及递归)是无法明显地进行向量化的。此外,在较小的
DataFrame
上,使用其他方法可能会更快。apply
通常 可以通过 Cython 中的迭代器来处理。这是由 pandas 内部处理的,不过具体情况取决于apply
表达式内部的操作。例如,df.apply(lambda x: np.sum(x))
执行得相当快,当然,df.sum(1)
更好。但是像df.apply(lambda x: x['b'] + 1)
这样的操作会在 Python 中执行,因此会慢得多。itertuples
不会把数据装入Series
中。它只是以元组的形式返回数据。iterrows
会 把数据装入Series
中。除非你真的需要这样做,否则建议使用其他方法。逐行更新一个空的框架。我见过这种方法用得太多了。这是最慢的方式。虽然在某些 Python 结构中可能比较常见(而且速度还算快),但
DataFrame
在索引时会进行很多检查,因此逐行更新总是非常慢。更好的方法是创建新的结构并使用concat
。