熊猫迭代有性能问题吗?

我注意到在使用来自大熊猫的迭代时,性能非常差。

它是特定于迭代行的吗? 对于特定大小的数据(我使用的是2-3百万行) ,是否应该避免使用这个函数?

GitHub 上的这个讨论 让我相信它是在数据框架中混合 dtype 时引起的,然而下面的简单示例显示,即使使用一个 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

为什么向量化操作的应用速度要快得多?我想那里一定也有一些逐行的迭代。

在我的案例中,我不知道如何不使用迭代器(这个问题我将留待以后讨论)。因此,如果您一直能够避免这个迭代,我将非常感激您的建议。我是根据不同数据框中的数据进行计算的。

我想运行的东西的简化版本:

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]
102727 次浏览

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

  1. 矢量化
  2. 使用自定义 Cython 例程
  3. 申请
    • 可在 Cython 进行的削减
    • Python 空间中的迭代
  4. 迭代
  5. 迭代
  6. 更新一个空帧(例如,使用 loc 每次一行)

使用自定义 Cython 例程通常过于复杂,因此现在让我们跳过它。

  1. 矢量化是 一直都是一直都是的首选和最佳选择。然而,有一小部分情况(通常涉及到重复发生)不能以明显的方式进行向量化。此外,在较小的 DataFrame上,使用其他方法可能更快。

  2. 在 Cython 空间中使用迭代器可以处理 apply 通常。这是由熊猫内部处理的,不过这取决于 apply表达式内部发生了什么。例如,df.apply(lambda x: np.sum(x))将非常快速地执行,当然,df.sum(1)甚至更好。然而,类似于 df.apply(lambda x: x['b'] + 1)的代码将在 Python 空间中执行,因此速度要慢得多。

  3. itertuples并不将数据装箱成 Series,它只是以元组的形式返回数据。

  4. 将数据装箱成一个 Series。除非你真的需要这个,使用另一种方法。

  5. 每次更新单行空帧。我已经看到这种方法使用的方式太多了。它是目前为止最慢的。这可能是常见的情况(对于某些 Python 结构来说,速度相当快) ,但是 DataFrame对索引进行了相当多的检查,因此每次更新一行时总是非常慢。创建新的结构和 concat要好得多。

Numpy 的向量业务和熊猫业务之所以比香草 Python 中的标量业务更胜一筹,有以下几个原因:

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

  • 更好的缓存 : 在 C 数组上迭代是缓存友好的,因此非常快。熊猫数据框架是一个“面向列的表”,这意味着每个列实际上只是一个数组。因此,您可以在 DataFrame 上执行的本机操作(比如对列中的所有元素求和)几乎不会丢失缓存。

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

故事的寓意: 在 Numpy 和大熊猫中使用病媒操作。它们比 Python 中的标量操作更快,原因很简单,因为这些操作正是 C 程序员手工编写的。(除了数组概念比嵌入 SIMD 指令的显式循环更容易阅读之外。)

这就是解决你问题的方法,这些都是矢量化的。

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

另一种选择是使用 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.record 对象。
  3. 在编译完所有数据之前,不要对 DataFrame 进行操作。

是的,Panda 迭代()比 iterrows ()更快。 您可以参考文档: < em > < a href = “ https://panas.pydata.org/anda-docs/stat/reference/api/Pandas.DataFrame.iterrows.html”rel = “ nofollow noReferrer”> anda. DataFrame.iterrows

为了在对行进行迭代时保留 dtype,最好使用 itertuple () ,它返回值的名称元组,并且通常比 iterrows 更快。

详情请参阅 这个视频

基准 enter image description here

不要使用迭代器!

... 或 iteritems,或 itertuples。说真的,不要。只要有可能,寻求对你的代码进行向量化。如果你不相信我,问问杰夫

我承认在 DataFrame 上对于 不断重复有合法的用例,但是对于迭代来说有比 iter*家族函数更好的选择,即

  • Cython /< a href = “ https://Pandas.pydata.org/anda-docs/stat/user _ guide/enhancingPerf.html # using-numba”rel = “ noReferrer”> numba
  • 列表理解
  • (在少数情况下) apply

经常有太多熊猫初学者会问与 iterrows有关的代码相关的问题。因为这些新用户可能不熟悉向量化的概念,所以他们将解决问题的代码想象成包含循环或其他迭代例程的代码。也不知道如何迭代,他们通常在 这个问题结束,学习所有错误的东西。


支持论点

关于迭代的文档页面 有一个巨大的红色警告框,上面写着:

在熊猫对象中迭代通常比较慢, 不需要在行上手动迭代[ ... ]。

如果这还不能说服你,那就看看向量化技术和非向量化技术之间的性能比较,这两种技术用于添加两列“ A + B”,摘自我的文章 给你

基准测试代码,供您参考。到目前为止,iterrows是最差的迭代方法,值得指出的是,其他迭代方法也好不到哪里去。

底部的一行测量了一个用数字熊猫编写的函数,这是一种熊猫风格,它与 NumPy 紧密结合以获得最大的性能。应该避免编写数熊猫代码,除非您知道自己在做什么。尽可能坚持使用 API (比如,更喜欢 vec而不是 vec_numpy)。


结语

总是寻求矢量化。有时,基于您的问题或数据的性质,这并不总是可能的,所以寻求比 iterrows更好的迭代例程。除了在处理极少数量的行时方便之外,几乎没有合法的用例可用于此,否则就要准备好在代码可能运行几个小时的情况下进行大量的等待。

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

如果您真的需要迭代它并通过名称访问行字段,只需将列名保存到列表中并将数据框转换为 笨蛋数组:

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