关于python:pandas iterrows有性能问题吗?

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]


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

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空间中的迭代器完成(这在熊猫内部完成)(这是一种情况)。

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

4)itertuples不把数据装箱成一个序列,只把它作为元组返回。

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

6)一次更新一个空帧一行。我见过这种方法用得太多了。这是迄今为止最慢的。这可能是常见的地方(对于某些Python结构来说,速度也相当快),但数据帧会对索引进行大量检查,因此一次更新一行总是非常慢。创建新结构和concat更好。


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


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

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

这是我最后的优化版本

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

  • 使用groupby避免重复比较数值。
  • 使用to_records访问raw numpy.records对象。
  • 在编译完所有数据之前,不要对数据帧进行操作。

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

    为了在对行进行迭代时保留数据类型,最好使用ITertuples(),它返回值的nameduples,并且通常比ITerRows快。