pandas iterrows 有性能问题吗?
- 2024-11-25 08:50:00
- admin 原创
- 197
问题描述:
我注意到使用 pandas 的 iterrows 时性能非常差。
它是特定于 iterrows 的吗?对于特定大小的数据(我正在处理 200 万到 300 万行)是否应该避免使用该函数?
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
为什么像 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]
解决方案 1:
一般来说,iterrows
只应在非常特殊的情况下使用。这是执行各种操作的一般优先顺序:
矢量化
使用自定义 Cython 例程
申请
可以在 Cython 中执行的减少
Python 空间中的迭代
迭代元组
迭代
更新空框架(例如,一次使用 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
。
解决方案 2:
Numpy 和 pandas 中的矢量运算比原始 Python 中的标量运算快得多,原因如下:
摊销类型查找:Python 是一种动态类型语言,因此数组中的每个元素都有运行时开销。但是,Numpy(以及 pandas)使用 C(通常通过 Cython)执行计算。数组的类型仅在迭代开始时确定;仅此一项节省就是最大的好处之一。
更好的缓存:对 C 数组进行迭代有利于缓存,因此速度非常快。pandas DataFrame 是一个“面向列的表”,这意味着每一列实际上只是一个数组。因此,您可以在 DataFrame 上执行的本机操作(例如对列中的所有元素求和)将很少发生缓存未命中。
更多并行机会:可以通过 SIMD 指令操作简单的 C 数组。Numpy 的某些部分启用 SIMD,具体取决于您的 CPU 和安装过程。并行性的好处不会像静态类型和更好的缓存那样引人注目,但它们仍然是一个坚实的优势。
这个故事的寓意是:使用 Numpy 和 pandas 中的向量运算。它们比 Python 中的标量运算更快,原因很简单,这些运算正是 C 程序员手写的内容。(除了数组概念比嵌入 SIMD 指令的显式循环更容易阅读。)
解决方案 3:
这是解决你的问题的方法。这都是矢量化的。
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
解决方案 4:
不要使用 iterrows!
...或者iteritems
,或者itertuples
。说真的,不要。尽可能地将代码矢量化。如果你不相信我,可以问 Jeff。
我承认,对DataFrame 进行迭代iter*
确实有合法的用例,但有比系列函数更好的迭代替代方案,即
cython / numba
列表推导,以及
(极少数情况)
apply
。
很多 Pandas 初学者经常会问一些与 相关的代码问题iterrows
。由于这些新用户可能不熟悉矢量化的概念,他们会将解决问题的代码想象成涉及循环或其他迭代例程的东西。由于也不知道如何迭代,他们通常会陷入这个问题并学到所有错误的东西。
支持论点
迭代的文档页面上有一个巨大的红色警告框,上面写着:
遍历 pandas 对象通常比较慢。在许多情况下,不需要手动遍历行 [...]。
如果这还不能说服你,请看一下我在 这里的帖子中对添加两列“A + B”的矢量化和非矢量化技术的性能比较。
基准测试代码,供您参考。iterrows
是迄今为止最糟糕的,并且还值得指出的是,其他迭代方法也好不到哪里去。
底部的一行测量了一个用 numpandas 编写的函数,numpandas 是 Pandas 的一种风格,它与 NumPy 大量混合,以发挥最大的性能。除非您知道自己在做什么,否则应避免编写 numpandas 代码。尽可能坚持使用 API(即,优先vec
于vec_numpy
)。
综上所述
始终寻求矢量化。有时,根据问题或数据的性质,这并不总是可行的,因此请寻求比 更好的迭代例程iterrows
。除了处理极少量行时的方便之外,几乎没有合法的用例,否则请准备好等待很长时间,因为您的代码可能会运行数小时。
查看下面的链接来确定解决代码的最佳方法/矢量化例程。
10 分钟了解 Pandas及其基本功能- 有用的链接,向您介绍 Pandas 及其矢量化*/cythonized 函数库。
增强性能- 增强标准 Pandas 操作的文档入门指南
解决方案 5:
另一种选择是使用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('
-- 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 倍。策略是:
用于
groupby
避免重复比较值。用于
to_records
访问原始 numpy.records 对象。在编译完所有数据之前,请勿对 DataFrame 进行操作。
解决方案 6:
如果您确实需要对其进行迭代并按名称访问行字段,只需将列名保存到列表中并将数据框转换为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
解决方案 7:
是的,Pandas itertuples()比 iterrows() 更快。您可以参考文档:pandas.DataFrame.iterrows
为了在迭代行时保留 dtypes,最好使用 itertuples(),它返回值的命名元组,并且通常比 iterrows 更快。
解决方案 8:
在建议不要使用的答案中iterrows
,请尝试以下操作:将 DataFrame 转储到(临时)CSV 文件,然后重新加载以执行iterrows
。在我的实验中,对于 600-700 万行数据,它将速度提高了 100 倍以上。
# The three lines below are the reason why the `iterrows` becomes slow...
all_df = ...
for path in glob.glob("..."):
all_df = pd.concat(all_df, pd.read_csv(path))
# This is my solution, just dump it to a file
all_df.to_csv("tmp.csv")
all_df = pd.read_csv("tmp.csv")
# because I like tqdm, so I have to use iterrows..
for index, row in tqdm(df.iterrows(), total=all_df.shape[0]):
# do something
...
# set something
all_df.at[index, ..] = ...
p/s:不确定是否有更好的解决方案..