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

例如,我正在使用以下来自雅虎财经的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只逐列迭代。


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

例如,如果close是一个一维数组,你想要逐日变化的百分比,

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

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

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

所以尽量避免Python循环i, row in enumerate(…),和 考虑如何在整个数组(或数据帧)上执行运算,而不是逐行。


你可以通过换位然后调用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,以得到与手工编写的C/ c++差不多快的东西。


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

for index, row in df.iterrows():

    # do some logic here

或者,如果你想要更快,可以使用itertuples()

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


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

还有iterkv,它遍历(column, series)元组。


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

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

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

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


就像之前提到的,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英特尔酷睿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

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

如果使用itertuple,请注意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

你有三个选择:

按索引(最简单):

>>> 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


当然,遍历数据帧的最快方法是通过df访问底层numpy ndarray。值(如您所做的那样)或通过分别访问每一列df.column_name.values。因为你也想访问索引,你可以使用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]

不是神谕的吗?当然。但很快。

如果你想从循环中挤出更多的果汁,你会想看看cython。Cython将让你获得巨大的加速(想想10 -100倍)。检查cython的内存视图以获得最大性能。


我相信循环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代码一样快。

其他细节:

Not shown are various options like iterrows, itertuples, etc. which are orders of magnitude slower and really should never be used. The timings here are fairly typical: numpy is faster than pandas and vectorized is faster than loops, but adding numba to numpy will often speed numpy up dramatically. Everything except the pandas option requires converting the DataFrame column to a numpy array. That conversion is included in the timings. The time to define/compile the numpy/numba functions was not included in the timings, but would generally be a negligible component of the timing for any large 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(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