pandas iterrows有性能问题吗?

时间:2014-07-21 17:19:17

标签: python performance pandas iteration

我注意到在使用pandas的iterrows时性能非常差。

这是别人经历的事吗?它是否特定于iterrows,并且对于特定大小的数据(我使用2-3百万行)应该避免此功能吗?

GitHub上的

This discussion让我相信它是在数据帧中混合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

为什么矢量化操作应用得如此之快?我想也必须有一些逐行迭代。

我无法弄清楚如何在我的情况下不使用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]

8 个答案:

答案 0 :(得分:132)

通常,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 (e.g. using loc one-row-at-a-time)

使用自定义cython例程通常过于复杂,所以现在让我们跳过它。

1)矢量化始终是第一个也是最好的选择。然而,有一小部分案例无法以明显的方式进行矢量化(主要涉及复发)。此外,在一个小框架上,做其他方法可能会更快。

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

这取决于apply表达式内部的内容。例如df.apply(lambda x: np.sum(x))会很快执行(当然df.sum(1)更好)。但是:df.apply(lambda x: x['b'] + 1)之类的内容将在python空间中执行,因此速度较慢。

4)itertuples不将数据打包成系列,只将其作为元组返回

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

6)一次更新空帧a-single-row。我见过这种方法过于使用了WAY。它是迄今为止最慢的。它可能是常见的(并且对于某些python结构来说相当快),但是DataFrame对索引进行了相当多的检查,因此一次更新行总是非常慢。更好地创建新结构和concat

答案 1 :(得分:13)

Numpy和pandas中的向量操作比vanilla Python中的标量操作快得多有以下几个原因:

  • 分摊式查找:Python是一种动态类型语言,因此数组中的每个元素都有运行时开销。但是,Numpy(以及大熊猫)在C中执行计算(通常通过Cython)。数组的类型仅在迭代开始时确定;仅此节省就是最大的胜利之一。

  • 更好的缓存:迭代C数组是缓存友好的,因此速度非常快。 pandas DataFrame是一个面向列的表",这意味着每列实际上只是一个数组。因此,您可以对DataFrame执行的本机操作(如汇总列中的所有元素)将很少有缓存未命中。

  • 更多并行机会:可以通过SIMD指令操作简单的C数组。 Numpy的某些部分启用SIMD,具体取决于您的CPU和安装过程。平行主义的好处不会像静态打字和更好的缓存一样引人注目,但它们仍然是一个坚实的胜利。

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

答案 2 :(得分:6)

这是解决问题的方法。这都是矢量化的。

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

答案 3 :(得分:6)

另一种选择是使用to_records(),这比itertuplesiterrows都要快。

但是对于您的情况,还有很多其他类型的改进空间。

这是我的最终优化版

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

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

答案 4 :(得分:0)

是的,Pandas itertuples()比iterrows()更快。 您可以参考文档:https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.iterrows.html

“要在遍历行时保留dtype,最好使用itertuples()返回值的命名元组,并且通常比迭代更快。”

答案 5 :(得分:0)

this video

中的详细信息

基准 enter image description here

答案 6 :(得分:0)

请勿使用迭代器!

...或iteritemsitertuples。说真的,不要。尽可能寻求vectorize您的代码。如果您不相信我,请ask Jeff

我将承认,在DataFrame上存在用于 iteration 的合法用例,但是比iter*系列函数(即

)有更好的迭代替代方案。

通常没有太多的初学者来问熊猫问题,这些问题所涉及的代码与iterrows有关。由于这些新用户可能不熟悉矢量化的概念,因此他们将解决问题的代码设想为涉及循环或其他迭代例程的代码。他们都不知道如何进行迭代,通常会在this question处学习所有错误的东西。


支持参数

The documentation page的迭代过程中有一个巨大的红色警告框,显示:

遍历熊猫对象通常很慢。在很多情况下 不需要手动在行上进行迭代[...]。

如果那不能使您信服,请看一下矢量化技术与非矢量化技术之间的性能比较,该技术用于添加两列“ A + B”,摘自我的帖子here

Benchmarking code, for your referenceiterrows到目前为止是最糟糕的,并且还需要指出其他迭代方法也没有很多。

底部的一行测量的功能是用numpandas编写的,这是一种Pa​​ndas风格,与NumPy大量混合使用,以最大限度地发挥性能。除非您知道自己在做什么,否则应避免编写numpandas代码。坚持使用API​​(例如,首选vec胜过vec_numpy)。


总结

始终寻求向量化。有时,根据您的问题或数据的性质,这并不总是可能的,因此寻求比iterrows更好的迭代例程。除了在处理极少数行时的便利性之外,几乎没有合法的用例,否则,可能需要花大量时间等待代码等待数小时的等待。

查看以下链接,以确定解决代码的最佳方法/向量化例程。

答案 7 :(得分:0)

如果你真的需要迭代它并按名称访问行字段,只需将列名保存到列表并将数据帧转换为 numpy 数组:

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})
columns = list(dfa.columns)
dfa = dfa.values
start = time.time()
i=0
for row in dfa:
    blablabla = row[columns.index('s1')]
    i+=1
end = time.time()
print (end - start)

0.9485495090484619