iterrows是否有性能问题?

使用大pandas时,我注意到性能很差。

这是别人经历的吗? 是否特定于iterrows,并应该避免这个函数的特定大小的数据(我正在2-3万行)?

这个关于GitHub的讨论让我相信这是在数据框中混合dtypes时造成的,但是下面这个简单的例子表明,即使在使用一个dtype(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 

为什么vector化的操作应用得这么快? 我想必须有一些逐行迭代。

我不知道如何在我的情况下不使用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] 

一般来说,只有在非常特殊的情况下才能使用iterrows 。 这是执行各种操作的一般顺序:

 1) vectorization 2) using a custom cython routine 3) apply a) reductions that can be performed in cython b) iteration in python space 4) itertuples 5) iterrows 6) updating an empty frame (eg using loc one-row-at-a-time) 

使用自定义的cython例程通常太复杂了,所以我们暂时略过。

1)vector化始终是第一个也是最好的select。 但是,它们只是一小部分不能以明显方式进行vector化的案例(主要涉及复发)。 进一步在一个小的框架,做其他方法可能会更快。

3)应用涉及通常可以由一个在cython空间的迭代器完成(这是在pandas内部完成的)(这是一个)情况。

这取决于应用expression式内正在发生的事情。 例如df.apply(lambda x: np.sum(x))会很快执行(当然df.sum(1)更好)。 然而,类似于: df.apply(lambda x: x['b'] + 1)将在python空间中执行,因此速度较慢。

4) itertuples不把数据放入一个Series,只是将它作为一个元组返回

5)将数据iterrows成一个Series。 除非你真的需要这个,否则使用另一种方法。

6)一次更新一个空帧。 我已经看到这个方法太多了。 这是迄今为止最慢的。 这可能是常见的地方(对于一些python结构来说是相当快的),但是DataFrame对索引进行了相当数量的检查,所以每次更新一行时总是会很慢。 更好地创build新的结构和concat

Numpy和Pandas中的向量操作要比香草Python中的标量操作快得多 ,原因如下:

分期types查找

Python是一种dynamictypes的语言,因此数组中的每个元素都有运行时开销。 然而,Numpy(从而pandas)在C中执行计算(通常通过Cython)。 数组的types仅在迭代开始时确定; 这个储蓄本身就是最大的胜利之一。

更好的caching

迭代C数组是caching友好的,因此速度非常快。 pandasDataFrame是一个“面向列的表”,这意味着每一列实际上只是一个数组。 因此,您可以在DataFrame上执行的本机操作(如汇总列中的所有元素)将会有less量的caching未命中。

并行性更多的机会

一个简单的C数组可以通过SIMD指令操作。 Numpy的某些部分启用SIMD,具体取决于您的CPU和安装过程。 并行性的好处不会像静态types和更好的caching那么戏剧化,但是它们仍然是一个稳定的胜利。

道德故事:使用Numpy和pandas的vector操作。 它们比Python中的标量操作要快,原因很简单,这些操作正是C程序员手动编写的。 (除了数组的概念比带embedded式SIMD指令的显式循环更容易阅读。)

这是解决问题的方法。 这全部是vector化的。

 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 

另一个select是使用to_records() ,它比itertuplesiterrows

但是对于你的情况,其他types的改进还有很大的空间。

这是我最后的优化版本

 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')) 

基准testing:

 -- 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倍。 策略是:

  1. 使用groupby来避免重复比较值。
  2. 使用to_records来访问原始numpy.records对象。
  3. 编译完所有数据之前,不要在DataFrame上进行操作。