【问题标题】:What is the most efficient way to loop through dataframes with pandas?用熊猫循环遍历数据帧的最有效方法是什么?
【发布时间】:2011-12-11 20:53:10
【问题描述】:

我想以顺序方式对数据框中的财务数据执行我自己的复杂操作。

例如,我正在使用从Yahoo Finance 获取的以下 MSFT CSV 文件:

Date,Open,High,Low,Close,Volume,Adj Close
2011-10-19,27.37,27.47,27.01,27.13,42880000,27.13
2011-10-18,26.94,27.40,26.80,27.31,52487900,27.31
2011-10-17,27.11,27.42,26.85,26.98,39433400,26.98
2011-10-14,27.31,27.50,27.02,27.27,50947700,27.27

....

然后我执行以下操作:

#!/usr/bin/env python
from pandas import *

df = read_csv('table.csv')

for i, row in enumerate(df.values):
    date = df.index[i]
    open, high, low, close, adjclose = row
    #now perform analysis on open/close based on date, etc..

这是最有效的方法吗?鉴于 pandas 对速度的关注,我认为必须有一些特殊的函数来迭代值,以一种也检索索引的方式(可能通过生成器来提高内存效率)? df.iteritems 不幸的是只逐列迭代。

【问题讨论】:

  • 您是否尝试过编写函数并将其传递给df.apply()?
  • 如果你想要内存效率,你应该考虑使用向量化操作(使用矩阵和向量)。但是我不了解pandas,所以我不能告诉你,那里是否可以进行这样的操作。
  • 引用unutbu,NumPy 似乎支持向量化操作 (The key to speed with NumPy arrays is to perform your operations on the whole array at once)。
  • 这个问题是针对顺序迭代的,这在金融中很常见,因为向量化通常是不可能的。 Nick Crawford 接受的答案回答了这个问题,并警告尽可能使用矢量化。

标签: python pandas performance dataframe for-loop


【解决方案1】:

Pandas 基于 NumPy 数组。 使用 NumPy 数组加快速度的关键是一次对整个数组执行操作,而不是逐行或逐项。

例如,如果close 是一维数组,并且您想要每天的百分比变化,

pct_change = close[1:]/close[:-1]

这会将整个百分比变化数组计算为一个语句,而不是

pct_change = []
for row in close:
    pct_change.append(...)

所以尽量避免 Python 循环 for i, row in enumerate(...) 完全,并且 考虑如何通过对整个数组(或数据帧)作为一个整体而不是逐行的操作来执行计算。

【讨论】:

  • 我同意这是最好的方法,这也是我通常做的简单操作。但是,在这种情况下,这是不可能的,因为结果操作会变得非常复杂。具体来说,我正在尝试回测交易策略。例如。如果价格在 30 天内处于新低,那么我们可能想要购买股票并在满足特定条件时退出,这需要就地模拟。这个简单的例子仍然可以通过矢量化来完成,但是,交易策略越复杂,使用矢量化的可能性就越小。
  • 您必须更详细地解释您尝试执行的确切计算。它有助于首先以任何方式编写代码,然后对其进行分析和优化。
  • 顺便说一下,对于某些计算(尤其是那些不能表示为对整个数组的操作),使用 Python 列表的代码可能比使用 numpy 数组的等效代码更快。
  • 我同意矢量化在可能的情况下是正确的解决方案——但有时迭代算法是唯一的方法。
  • 迟到的评论,但我发现尝试对列进行完整计算有时很难编写和调试。考虑中间计算列,更容易调试和理解计算。发现即使是最复杂的逻辑也可以这样实现,同时还能避免循环。
【解决方案2】:

您可以通过转置然后调用 ititems 来循环遍历行:

for date, row in df.T.iteritems():
   # do some logic here

我不确定这种情况下的效率。为了在迭代算法中获得最佳性能,您可能想探索在Cython 中编写它,因此您可以执行以下操作:

def my_algo(ndarray[object] dates, ndarray[float64_t] open,
            ndarray[float64_t] low, ndarray[float64_t] high,
            ndarray[float64_t] close, ndarray[float64_t] volume):
    cdef:
        Py_ssize_t i, n
        float64_t foo
    n = len(dates)

    for i from 0 <= i < n:
        foo = close[i] - open[i] # will be extremely fast

我建议先用纯 Python 编写算法,确保它可以工作,看看它有多快——如果它不够快,可以像这样用最少的工作将东西转换成 Cython,以获得与手一样快的东西- 编码的 C/C++。

【讨论】:

  • 我也推荐 Cython;我正在解决一个类似的问题来构建我的回测引擎,我得到了 1000 倍的加速。然后我将它与多处理库相结合,这是一个非常好的组合。
  • 根据@NickCrawford 的回答,此答案需要更新以包含新的df.iterrows()。
  • df.T.iteritems() 是一个很好的解决方案,而不是使用 df.iterrows() 如果您想遍历特定列 +1
  • 给出错误:def my_algo(ndarray[object] dates, ndarray[float64_t] opn, ^ SyntaxError: invalid syntax
【解决方案3】:

最新版本的 pandas 现在包含一个用于迭代行的内置函数。

for index, row in df.iterrows():

    # do some logic here

或者,如果您想要更快地使用itertuples()

但是,unutbu 建议使用 numpy 函数来避免迭代行将产生最快的代码。

【讨论】:

  • 请注意,iterrows 非常慢(它将每一行转换为一系列,可能会弄乱您的数据类型)。当你需要一个迭代器时,最好使用itertuples
  • BTW itertuples 返回命名元组 (docs.python.org/3/library/…),因此您可以使用 row.high 或 getattr(row,'high') 按名称访问每一列
  • 请注意,根据当前docs:“您应该永远不要修改您正在迭代的内容。这不能保证在所有情况下都有效。取决于数据类型,迭代器返回一个副本而不是一个视图,写入它不会有任何效果。”
  • @joris。我完全同意你的看法,itertuples 比 iterrows 胖大约 100 倍。
  • itertuples(name=None) 甚至更快,因为它将产生普通元组而不是命名元组。看到这篇有趣的文章:medium.com/swlh/…
【解决方案4】:

在注意到Nick Crawford's 的答案后,我检查了iterrows,但发现它产生了 (index, Series) 元组。不确定哪个最适合您,但我最终使用 itertuples 方法解决我的问题,它产生 (index, row_value1...) 元组。

还有iterkv,它遍历(列、系列)元组。

【讨论】:

  • 您可以执行类似 dict(row) 的操作,以使用可搜索的列从行中创建一个集合
  • 我还发现 itertuples 在我的用例中要快得多(10 倍),因为没有创建 Series 对象。
  • 仅供参考:iterkv 自 0.13.1 起已弃用
  • iterrows(): Iterate over the rows of a DataFrame as (index, Series) pairs.... itertuples(): Iterate over the rows of a DataFrame as tuples of the values. This is a lot faster as iterrows(), and is in most cases preferable to use to iterate over the values of a DataFrame.
【解决方案5】:

作为一个小补充,如果您有一个应用到单个列的复杂函数,您也可以执行应用:

http://pandas.pydata.org/pandas-docs/dev/generated/pandas.DataFrame.apply.html

df[b] = df[a].apply(lambda col: do stuff with col here)

【讨论】:

  • 可能 x 对于列名和行变量来说是一个令人困惑的名称,尽管我同意 apply 是最简单的方法:)
  • 只是添加,apply也可以应用于多列:df['c'] = df[['a','b']].apply(lambda x: do stuff with x[0] and x[1] here, axis=1)
  • 可以应用在代码中其他地方定义的函数吗?这样我们就可以引入一个更复杂的函数
  • 是的,lambda 函数可以使用任何类型的用户定义函数。请注意:如果您有一个大型数据框,您可能希望恢复为 cython(Python 在调用函数时有一些开销)
  • 我重命名了x -> col。更好的名字
【解决方案6】:

如果行的子集共享允许您这样做的特征,另一个建议是将 groupby 与矢量化计算结合起来。

【讨论】:

    【解决方案7】:

    如前所述,pandas 对象在一次处理整个数组时效率最高。然而,对于像我这样真正需要遍历 pandas DataFrame 来执行某些操作的人来说,我发现至少三种方法可以做到这一点。我做了一个简短的测试,看看这三个中哪一个最耗时。

    t = pd.DataFrame({'a': range(0, 10000), 'b': range(10000, 20000)})
    B = []
    C = []
    A = time.time()
    for i,r in t.iterrows():
        C.append((r['a'], r['b']))
    B.append(time.time()-A)
    
    C = []
    A = time.time()
    for ir in t.itertuples():
        C.append((ir[1], ir[2]))    
    B.append(time.time()-A)
    
    C = []
    A = time.time()
    for r in zip(t['a'], t['b']):
        C.append((r[0], r[1]))
    B.append(time.time()-A)
    
    print B
    

    结果:

    [0.5639059543609619, 0.017839908599853516, 0.005645036697387695]
    

    这可能不是衡量时间消耗的最佳方法,但对我来说很快。

    恕我直言,这里有一些优点和缺点:

    • .iterrows():在单独的变量中返回索引和行项,但速度明显慢
    • .itertuples(): 比.iterrows() 快,但返回索引和行项,ir[0] 是索引
    • zip:最快,但无法访问该行的索引

    编辑 2020/11/10

    值得一提的是,这里有一些其他替代品的更新基准(MacBookPro 2.4 GHz Intel Core i9 8 核 32 Go 2667 MHz DDR4)

    import sys
    import tqdm
    import time
    import pandas as pd
    
    B = []
    t = pd.DataFrame({'a': range(0, 10000), 'b': range(10000, 20000)})
    for _ in tqdm.tqdm(range(10)):
        C = []
        A = time.time()
        for i,r in t.iterrows():
            C.append((r['a'], r['b']))
        B.append({"method": "iterrows", "time": time.time()-A})
    
        C = []
        A = time.time()
        for ir in t.itertuples():
            C.append((ir[1], ir[2]))
        B.append({"method": "itertuples", "time": time.time()-A})
    
        C = []
        A = time.time()
        for r in zip(t['a'], t['b']):
            C.append((r[0], r[1]))
        B.append({"method": "zip", "time": time.time()-A})
    
        C = []
        A = time.time()
        for r in zip(*t.to_dict("list").values()):
            C.append((r[0], r[1]))
        B.append({"method": "zip + to_dict('list')", "time": time.time()-A})
    
        C = []
        A = time.time()
        for r in t.to_dict("records"):
            C.append((r["a"], r["b"]))
        B.append({"method": "to_dict('records')", "time": time.time()-A})
    
        A = time.time()
        t.agg(tuple, axis=1).tolist()
        B.append({"method": "agg", "time": time.time()-A})
    
        A = time.time()
        t.apply(tuple, axis=1).tolist()
        B.append({"method": "apply", "time": time.time()-A})
    
    print(f'Python {sys.version} on {sys.platform}')
    print(f"Pandas version {pd.__version__}")
    print(
        pd.DataFrame(B).groupby("method").agg(["mean", "std"]).xs("time", axis=1).sort_values("mean")
    )
    
    ## Output
    
    Python 3.7.9 (default, Oct 13 2020, 10:58:24) 
    [Clang 12.0.0 (clang-1200.0.32.2)] on darwin
    Pandas version 1.1.4
                               mean       std
    method                                   
    zip + to_dict('list')  0.002353  0.000168
    zip                    0.003381  0.000250
    itertuples             0.007659  0.000728
    to_dict('records')     0.025838  0.001458
    agg                    0.066391  0.007044
    apply                  0.067753  0.006997
    iterrows               0.647215  0.019600
    

    【讨论】:

    • NB in Python 3 zip() 返回一个迭代器,所以使用list(zip())
    • 你不能用t.index循环遍历索引吗?
    • 这很棒;谢谢理查德。它仍然与 Python 3.7+ 相关。从使用 iterrows 的 286 秒到使用 zip 的 3.62 秒。谢谢
    • 我用 pandas.__version__ == 1.1.4、Python 3.7.9 和全新的 MacBookPro 2.4 GHz Intel Core i9 8 核 32 Go 2667 MHz DDR4 和对于iterrows(),结果甚至更差:[0.6970570087432861, 0.008062124252319336, 0.0036787986755371094]
    • @ClementWalter,很好!
    【解决方案8】:

    正如@joris 指出的那样,iterrows 比itertuples 慢得多,itertuples 比iterrows 胖大约 100 倍,我在 DataFrame 中用 5027505 条记录测试了这两种方法的速度,结果是iterrows 为 1200it/s,itertuples 为 120000it/s。

    如果使用itertuples,注意for循环中的每个元素都是一个namedtuple,所以要获取每一列的值,可以参考下面的示例代码

    >>> df = pd.DataFrame({'col1': [1, 2], 'col2': [0.1, 0.2]},
                          index=['a', 'b'])
    >>> df
       col1  col2
    a     1   0.1
    b     2   0.2
    >>> for row in df.itertuples():
    ...     print(row.col1, row.col2)
    ...
    1, 0.1
    2, 0.2
    

    【讨论】:

      【解决方案9】:

      您有三个选择:

      index(最简单):

      >>> for index in df.index:
      ...     print ("df[" + str(index) + "]['B']=" + str(df['B'][index]))
      

      iterrows(最常用):

      >>> for index, row in df.iterrows():
      ...     print ("df[" + str(index) + "]['B']=" + str(row['B']))
      

      使用itertuples(最快):

      >>> for row in df.itertuples():
      ...     print ("df[" + str(row.Index) + "]['B']=" + str(row.B))
      

      三个选项显示如下:

      df[0]['B']=125
      df[1]['B']=415
      df[2]['B']=23
      df[3]['B']=456
      df[4]['B']=189
      df[5]['B']=456
      df[6]['B']=12
      

      来源:alphons.io

      【讨论】:

        【解决方案10】:

        当然,迭代数据帧的最快方法是通过df.values(如您所做的那样)或单独访问每个列df.column_name.values 访问底层的numpy ndarray。由于您也想访问索引,因此您可以使用df.index.values。

        index = df.index.values
        column_of_interest1 = df.column_name1.values
        ...
        column_of_interestk = df.column_namek.values
        
        for i in range(df.shape[0]):
           index_value = index[i]
           ...
           column_value_k = column_of_interest_k[i]
        

        不是pythonic?当然。但很快。

        如果您想从循环中挤出更多的汁液,您需要查看cython。 Cython 会让你获得巨大的加速(想想 10 倍到 100 倍)。为了获得最佳性能检查memory views for cython。

        【讨论】:

          【解决方案11】:

          我相信循环遍历 DataFrame 最简单有效的方法是使用 numpy 和 numba。在这种情况下,在许多情况下,循环可能与矢量化操作一样快。如果 numba 不是一个选项,则普通 numpy 可能是下一个最佳选择。正如多次指出的那样,您的默认值应该是矢量化,但这个答案仅考虑有效循环,无论出于何种原因决定循环。

          对于测试用例,让我们使用@DSM 的计算百分比变化答案中的示例。这是一个非常简单的情况,实际上您不会编写循环来计算它,但因此它为时序矢量化方法与循环提供了合理的基线。

          让我们用一个小的 DataFrame 设置这 4 种方法,我们将在下面的一个更大的数据集上对它们进行计时。

          import pandas as pd
          import numpy as np
          import numba as nb
          
          df = pd.DataFrame( { 'close':[100,105,95,105] } )
          
          pandas_vectorized = df.close.pct_change()[1:]
          
          x = df.close.to_numpy()
          numpy_vectorized = ( x[1:] - x[:-1] ) / x[:-1]
                  
          def test_numpy(x):
              pct_chng = np.zeros(len(x))
              for i in range(1,len(x)):
                  pct_chng[i] = ( x[i] - x[i-1] ) / x[i-1]
              return pct_chng
          
          numpy_loop = test_numpy(df.close.to_numpy())[1:]
          
          @nb.jit(nopython=True)
          def test_numba(x):
              pct_chng = np.zeros(len(x))
              for i in range(1,len(x)):
                  pct_chng[i] = ( x[i] - x[i-1] ) / x[i-1]
              return pct_chng
              
          numba_loop = test_numba(df.close.to_numpy())[1:]
          

          这里是一个有 100,000 行的 DataFrame 上的时序(使用 Jupyter 的 %timeit 函数执行的时序,为了便于阅读,折叠成一个汇总表):

          pandas/vectorized   1,130 micro-seconds
          numpy/vectorized      382 micro-seconds
          numpy/looped       72,800 micro-seconds
          numba/looped          455 micro-seconds
          

          总结:对于像这个这样的简单情况,您可以使用(矢量化)pandas 以简化和可读性,并使用(矢量化)numpy 以提高速度。如果您确实需要使用循环,请在 numpy.如果 numba 可用,请将其与 numpy 结合使用以提高速度。在这种情况下,numpy + numba 几乎与向量化的 numpy 代码一样快。

          其他细节:

          • 未显示各种选项,例如 iterrows、itertuples 等,它们的速度要慢几个数量级,并且真的不应该使用。
          • 这里的时序相当典型:numpy 比 pandas 快,vectorized 比循环快,但是将 numba 添加到 numpy 通常会显着加快 numpy。
          • 除了 pandas 选项之外的所有内容都需要将 DataFrame 列转换为 numpy 数组。该转换包含在计时中。
          • 定义/编译 numpy/numba 函数的时间不包括在计时中,但对于任何大型数据帧来说,通常是可忽略不计的计时组成部分。

          【讨论】:

            【解决方案12】:

            看看最后一个

            t = pd.DataFrame({'a': range(0, 10000), 'b': range(10000, 20000)})
            B = []
            C = []
            A = time.time()
            for i,r in t.iterrows():
                C.append((r['a'], r['b']))
            B.append(round(time.time()-A,5))
            
            C = []
            A = time.time()
            for ir in t.itertuples():
                C.append((ir[1], ir[2]))    
            B.append(round(time.time()-A,5))
            
            C = []
            A = time.time()
            for r in zip(t['a'], t['b']):
                C.append((r[0], r[1]))
            B.append(round(time.time()-A,5))
            
            C = []
            A = time.time()
            for r in range(len(t)):
                C.append((t.loc[r, 'a'], t.loc[r, 'b']))
            B.append(round(time.time()-A,5))
            
            C = []
            A = time.time()
            [C.append((x,y)) for x,y in zip(t['a'], t['b'])]
            B.append(round(time.time()-A,5))
            B
            
            0.46424
            0.00505
            0.00245
            0.09879
            0.00209
            

            【讨论】:

              猜你喜欢
              • 2021-12-22
              • 1970-01-01
              • 2020-11-20
              • 1970-01-01
              • 2017-06-08
              • 2017-01-15
              • 1970-01-01
              相关资源
              最近更新 更多