Does pandas iterrows have performance issues?
我注意到在使用大熊猫图片时性能非常差。
这是别人经历过的吗?它是特定于ITerRows的吗?对于特定大小的数据(我处理的是200-300万行),应该避免使用此函数吗?
关于Github的讨论使我相信它是在数据帧中混合数据类型时引起的,但是下面的简单示例表明,即使使用一个数据类型(float64),它也是存在的。在我的机器上这需要36秒:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 |
为什么像这样的矢量化操作应用得这么快?我想一定也有一些逐行迭代在进行。
在我的案例中,我不知道如何不使用ITerRows(这将留给将来的问题)。因此,如果您一直能够避免这种迭代,我将不胜感激。我正在根据不同数据帧中的数据进行计算。谢谢您!
---编辑:下面添加了我要运行的简化版本---
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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] |
一般来说,
1 2 3 4 5 6 7 8 | 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 (e.g. using loc one-row-at-a-time) |
使用一个自定义的赛马拉松程序通常太复杂了,所以我们暂时跳过它。
1)矢量化始终是首选。但是,有一小部分病例不能以明显的方式进行向量化(主要涉及复发)。此外,在一个小框架上,执行其他方法可能更快。
3)应用涉及通常可以由Cython空间中的迭代器完成(这在熊猫内部完成)(这是一种情况)。
这取决于应用表达式内部的情况。例如,
4)
5)
6)一次更新一个空帧一行。我见过这种方法用得太多了。这是迄今为止最慢的。这可能是常见的地方(对于某些Python结构来说,速度也相当快),但数据帧会对索引进行大量检查,因此一次更新一行总是非常慢。创建新结构和
numpy和pandas中的向量操作比普通python中的标量操作快得多,原因如下:
摊余类型查找:Python是一种动态类型语言,因此数组中的每个元素都有运行时开销。然而,numpy(因此pandas)在c中执行计算(通常通过cython)。数组的类型仅在迭代开始时确定;仅此一项节省就是最大的胜利之一。
更好的缓存:在C数组上迭代是缓存友好的,因此速度非常快。熊猫数据帧是一个"面向列的表",这意味着每一列实际上只是一个数组。因此,您可以在数据帧上执行的本机操作(如汇总列中的所有元素)将很少有缓存未命中。
更多并行的机会:一个简单的C数组可以通过simd指令操作。numpy的某些部分启用simd,这取决于您的CPU和安装过程。并行性的好处不会像静态类型和更好的缓存那样引人注目,但它们仍然是一个坚实的胜利。
故事的寓意:使用numpy和pandas中的向量运算。它们比Python中的标量操作快,原因很简单,因为这些操作正是C程序员手工编写的。(除了阵列概念比使用嵌入式SIMD指令的显式循环更容易读取。)
这是解决问题的方法。这都是矢量化的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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 |
另一种选择是使用
但对于您的情况,还有很多其他类型的改进空间。
这是我最后的优化版本
1 2 3 4 5 6 7 8 9 10 11 12 | 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')) |
基准测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | -- 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 |
完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | 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(' -- 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(' -- 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(' -- 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(' -- 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(' -- 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倍。战略是:
是的,pandas itertuples()比iterrow()更快。您可以参考文档:https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.dataframe.iterrow.html
为了在对行进行迭代时保留数据类型,最好使用ITertuples(),它返回值的nameduples,并且通常比ITerRows快。