【问题标题】:Does pandas iterrows have performance issues?pandas iterrows 有性能问题吗?
【发布时间】:2014-09-12 07:17:48
【问题描述】:

我注意到使用 pandas 的 iterrows 时性能很差。

这是别人经历过的事情吗?它是特定于 iterrows 的吗?对于特定大小的数据(我正在处理 2-3 百万行),是否应该避免使用此函数?

This discussion GitHub 上的 This discussion 让我相信这是在数据框中混合 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

为什么像 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]

【问题讨论】:

  • apply 未矢量化。 iterrows 更糟糕,因为它将所有东西都装箱了(这就是与apply 的性能差异)。你应该只在极少数情况下使用iterrows。恕我直言,从来没有。使用iterrows 显示您实际在做什么。
  • 您链接到的问题与将DatetimeIndex 装箱到Timestamps (在python 空间中实现)有关,这在master 中得到了很大改进。
  • 查看此问题以获得更完整的讨论:github.com/pydata/pandas/issues/7194
  • 链接到具体问题(这个问题将保持一般性):stackoverflow.com/questions/24875096/…
  • Please do not recommend the use of iterrows(). 这是 pandas 历史上最糟糕的反模式的公然推动者。

标签: python performance pandas iteration


【解决方案1】:

一般来说,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) 矢量化始终是首选,也是最佳选择。但是,有一小部分案例(通常涉及复发)无法以明显的方式进行矢量化。此外,在较小的DataFrame 上,使用其他方法可能会更快。

3) apply 通常可以由 Cython 空间中的迭代器处理。这由 pandas 内部处理,尽管它取决于 apply 表达式内部发生的情况。例如,df.apply(lambda x: np.sum(x)) 将很快执行,当然,df.sum(1) 更好。但是像 df.apply(lambda x: x['b'] + 1) 这样的东西会在 Python 空间中执行,因此会慢很多。

4) itertuples 不会将数据装箱到 Series。它只是以元组的形式返回数据。

5) iterrows 将数据装箱到Series 中。除非您真的需要,否则请使用其他方法。

6) 一次更新一个空帧。我已经看到这种方法使用得太多了。它是迄今为止最慢的。这可能是常见的地方(对于某些 python 结构来说相当快),但是DataFrame 对索引进行了大量检查,因此一次更新一行总是很慢。更好地创建新结构和concat

【讨论】:

  • 是的,我使用了 6 号(和 5 号)。我有一些学习要做。对于相对初学者来说,这似乎是显而易见的选择。
  • 根据我的经验,3、4 和 5 之间的差异是有限的,具体取决于用例。
  • 我已尝试检查运行时 in this notebook。不知何故itertuplesapply 快:(
  • pd.DataFrame.apply 通常比itertuples 慢。此外,对于 non-vectorisable 计算,值得考虑列表推导 map、名称不佳的 np.vectorizenumba(无特定顺序),例如见this answer
  • @Jeff,出于好奇,你为什么不在这里添加列表推导?虽然它们确实不处理索引对齐或丢失数据(除非您使用带有 try-catch 的函数),但它们适用于 pandas 方法没有矢量化的许多用例(字符串/正则表达式)(真正意义上的)实现。你认为值得一提的是 LC 是 pandas apply 和许多 pandas 字符串函数的更快、更低开销的替代方案吗?
【解决方案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
    

    【讨论】:

    • 非常明确的答案,谢谢。我会尝试合并,但我有疑问,因为我将拥有 50 亿行(250 万 * 2000)。为了保持这个 Q 的通用性,我创建了一个特定的 Q。我很高兴看到一个替代方案来避免这个巨大的桌子,如果你知道的话:这里:stackoverflow.com/questions/24875096/…
    • 这不会创建笛卡尔积 - 它是一个压缩空间并且非常节省内存。你正在做的是一个非常标准的问题。试一下。 (您的链接问题有一个非常相似的解决方案)
    【解决方案4】:

    另一种选择是使用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 进行操作。

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

        【解决方案6】:

        详情在this video

        基准

        【讨论】:

          【解决方案7】:

          不要使用 iterrows!

          ...或iteritems,或itertuples。说真的,不要。尽可能寻找vectorize您的代码。如果你不相信我,ask Jeff

          我承认在 DataFrame 上迭代有合法的用例,但迭代有比iter* 系列函数更好的替代方案,即

          经常有太多 Pandas 初学者提出的问题涉及与 iterrows 相关的代码。由于这些新用户可能不熟悉向量化的概念,他们将解决问题的代码设想为涉及循环或其他迭代例程的东西。也不知道如何迭代,他们通常以this question 结束并学习所有错误的东西。


          支持论据

          The documentation page 在迭代中有一个巨大的红色警告框,上面写着:

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

          如果这不能说服您,请查看矢量化技术与非矢量化技术在添加两列“A + B”时的性能比较,取自我的帖子here

          Benchmarking code, for your referenceiterrows 是迄今为止最差的,还值得指出的是其他迭代方法也好不到哪里去。

          底部的行测量一个用 numpandas 编写的函数,这是一种与 NumPy 大量混合以挤出最大性能的 Pandas 风格。除非您知道自己在做什么,否则应避免编写 numpandas 代码。尽可能坚持使用 API(即,更喜欢 vec 而不是 vec_numpy)。


          结论

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

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

          【讨论】:

            【解决方案8】:

            如果您确实需要对其进行迭代并按名称访问行字段,只需将列名保存到列表并将数据框转换为 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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-04-14
              • 2022-12-11
              • 2017-07-16
              • 2019-04-14
              • 2014-10-17
              • 2019-10-23
              相关资源
              最近更新 更多