使用pandas循环数据帧的最有效方法是什么?

时间:2011-10-20 14:46:15

标签: python pandas performance dataframe for-loop

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

例如,我使用的是从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..

这是最有效的方式吗?鉴于对熊猫速度的关注,我认为必须有一些特殊的函数来迭代遍历值,同时也检索索引(可能通过生成器来节省内存)?遗憾的是,df.iteritems只是逐列迭代。

11 个答案:

答案 0 :(得分:339)

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

for index, row in df.iterrows():

    # do some logic here

或者,如果您希望更快地使用itertuples()

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

答案 1 :(得分:146)

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(...) 考虑如何通过整个数组(或数据帧)的操作来执行计算,而不是逐行。

答案 2 :(得分:81)

与前面提到的一样,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:最快,但无法访问行的索引

答案 3 :(得分:72)

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

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,只需要很少的工作就可以获得与手一样快的东西-coded C / C ++。

答案 4 :(得分:26)

您有三种选择:

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

来源:neural-networks.io

答案 5 :(得分:25)

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

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

答案 6 :(得分:20)

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

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

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

答案 7 :(得分:7)

正如@joris指出的那样,iterrowsitertuples慢得多,itertuplesiterrows快约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

答案 8 :(得分:6)

当然,迭代数据框的最快方法是通过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将让你获得巨大的加速(想想10x-100x)。为了获得最佳性能,请检查memory views for cython

答案 9 :(得分:5)

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

答案 10 :(得分:1)

我相信循环遍历 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 函数的时间不包括在计时中,但通常是任何大型数据帧计时的一个可以忽略不计的组成部分。