我想按顺序对数据帧中的财务数据执行自己的复杂操作。例如,我正在使用以下来自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不幸的是,只能逐列进行迭代。

评论

您是否尝试编写函数并将其传递给df.apply()?

引用unutbu,NumPy似乎支持向量化操作(加快NumPy数组速度的关键是立即对整个数组执行操作)。

这里的许多答案都是危险的,因为它们很乐意建议使用迭代方法,这些方法缓慢,笨拙,并且消耗的内存比他们应有的多。在可能的情况下,应该对操作进行向量化。此答案更详细地研究了向量化和性能注意事项。

这个问题是顺序迭代所特有的,这在金融中非常普遍,因为矢量化通常是不可能的。尼克·克劳福德(Nick Crawford)接受的答案不仅回答了这一问题,而且还警告在可能的情况下使用向量化。

@ cs95仅供参考,我刚刚重新打开(您已标记为重复)b / c我觉得这与另一个问题相似,但不完全相同,因为它具有特定的效率方面(效率不是唯一的考虑因素) ,也是因为我想添加答案!我个人认为应该链接但不要关闭它,但是由于我现在已经添加了一个答案,因此如果您强烈希望关闭它,我也不会反对。 (我对此和其他答案都同意,有很多过时的答案不再有用。)

#1 楼

熊猫的最新版本现在包括用于遍历行的内置函数。

for index, row in df.iterrows():

    # do some logic here


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

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

评论


请注意,迭代非常慢(它将每一行转换为一系列,可能会干扰您的数据类型)。当您需要迭代器时,最好使用itertuples

– joris
15年7月29日在15:46

BTW itertuples返回命名元组(docs.python.org/3/library/…),因此您可以使用row.high或getattr(row,'high')按名称访问每一列

–seanv507
16年4月17日在18:51

请注意,根据当前文档,“您永远不要修改要迭代的内容。不能保证在所有情况下都能正常工作。根据数据类型,迭代器将返回副本而不是视图,并且对其进行写入没有效果。”

–viddik13
16 Dec 7'在18:50

@joris。我完全同意您的观点,迭代比迭代快约100倍。

– GoingMyWay
17年7月7日在9:24

fjsj:既然问题是关于“循环”和“效率”的,我提供了一个回答,回答了问题的两个部分。除非绝对必要,否则任何人都不应在数据帧上循环。

–尼克·克劳福德(Nick Crawford)
19年1月9日在1:08

#2 楼

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天内达到新的低点,则我们可能想购买股票,并在满足特定条件时退出,这需要就地模拟。这个简单的例子仍然可以通过向量化完成,但是,交易策略越复杂,使用向量化的可能性就越小。

–木偶
2011-10-20 15:16



您必须更详细地说明您要执行的确切计算。它可以帮助您首先以任何方式编写代码,然后对其进行概要分析和优化。

–unutbu
2011-10-20 15:19



顺便说一下,对于某些计算(尤其是那些不能表示为对整个数组的操作的计算),使用Python列表的代码可能比使用numpy数组的等效代码更快。

–unutbu
2011-10-20 15:35



我同意向量化是在可能的情况下的正确解决方案,尽管有时迭代算法是唯一的方法。

–韦斯·麦金尼
2011年10月21日在16:15

最近的评论,但我发现尝试对列进行完整计算有时很难编写和调试。考虑中间计算列,使调试和理解计算更加容易。我们发现即使最复杂的逻辑也可以像这样实现,同时仍然避免循环。

–乔普
2014年9月22日上午11:27

#3 楼

就像之前提到的那样,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

结果:消耗,但对我来说是快速的。
恕我直言,这里有一些优点和缺点: ():比.iterrows()快,但是返回索引和行项目,ir [0]是索引。
zip:最快,但无法访问该行的索引。

EDIT 2020/11/10
值得一提的是,这里提供了一些其他替代产品的更新基准(使用MacBookPro 2,4 GHz Intel Core i9 8核32 Go 2667 MHz DDR4的性能)
=“ lang-py prettyprint-override”> 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

评论


Python 3中的NB zip()返回一个迭代器,因此请使用list(zip())

–路易丝·马多克斯(Louis Maddox)
16-10-12在13:33



您不能使用t.index遍历索引吗?

–elPastor
16/12/22在2:54



这很棒;谢谢理查德。它仍然与Python 3.7+相关。从286秒(带拉链)到3.62(带拉链)。谢谢

–pacta_sunt_servanda
19年5月16日在12:48

我已经使用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
20 Nov 10在17:02



@ClementWalter,太好了!

–黄耀明(Richard Wong)
20 Nov 12'在6:43

#4 楼

您可以通过转置然后调用迭代项来遍历各行:

为了在迭代算法中获得最佳性能,您可能想探索用Cython编写代码,因此可以执行以下操作:

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


我建议编写首先,请使用纯Python编写该算法,并确保它能够运行并观察其运行速度-如果运行速度不够快,只需花费最少的工作即可将其转换为Cython,从而获得与手工编码的C / C ++差不多的速度。

评论


我也推荐Cython。我在构建回测引擎时也遇到了类似的问题,并且提速了1000倍。然后,我将其与多处理库相结合,这是一个非常不错的组合。

–vgoklani
2012年10月7日在12:31

根据@NickCrawford的答案,此答案需要更新以包括新的df.iterrows()。

–LondonRob
2014年6月6日下午16:14

如果要迭代特定列+1,则df.T.iteritems()是一个很好的解决方案,而不是使用df.iterrows()

– ALH
15-10-25在10:37

给出错误:def my_algo(ndarray [object] dates,ndarray [float64_t] opn,^ SyntaxError:无效语法

–astro123
19年4月1日,下午3:54

#5 楼

您有以下三种选择:

按索引(最简单):

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


具有迭代次数(最常用):

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


使用itupuples(最快):

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


来源:Neuro-networks.io

#6 楼

在注意到尼克·克劳福德的答案后,我检查了iterrows,但发现它产生了(索引,系列)元组。不确定哪种方法最适合您,但是我最终对问题使用了itertuples方法,该方法产生了(index,row_value1 ...)元组。 ,系列)元组。

评论


您可以执行dict(row)之类的操作,以使行可以搜索到列

–卡斯特
13-10-16在22:36

我还发现在我的用例中itertuples快得多(10倍),因为没有创建Series对象。

–卡米尔·辛迪
2014年6月11日12:51

仅供参考:从0.13.1开始不推荐使用iterkv

– JS。
2015年9月9日23:06

iterrows():将DataFrame的行作为(索引,系列)对进行迭代...。itertuples():将DataFrame的行作为值的元组进行迭代。作为iterrows(),这要快得多,并且在大多数情况下,最好使用它来遍历DataFrame的值。

–红豌豆
2015年11月5日下午5:21

#7 楼

只是一个很小的补充,如果您具有应用于单个列的复杂功能,则也可以执行应用:
generate / pandas.DataFrame.apply.html

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


评论


x可能是列名和行变量的一个令人困惑的名称,尽管我同意apply是最简单的方法:)

–安迪·海登(Andy Hayden)
13-10-17在6:09



只需添加,apply也可以应用于多个列:df ['c'] = df [['a','b']]。apply(lambda x:用x [0]和x [1]进行填充在这里,axis = 1)

–奇妙
14年8月16日在13:18

可以应用代码中其他地方定义的功能吗?这样我们就可以引入更复杂的功能

–user308827
2014年11月9日15:28

是的,lambda函数可以使用任何类型的用户定义函数。请注意:如果您有一个大型数据框,则可能需要恢复为cython(在调用函数时,Python会有一些开销)

–卡斯特
2014年11月18日15:53

我将其重命名为x-> col。更好的名字

–smci
2015年2月5日在4:16



#8 楼

正如@joris指出的那样,iterrowsitertuples慢得多,而itertuplesiterrows慢100倍左右,我在使用5027505的DataFrame中测试了这两种方法的速度,结果是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 楼

当然,迭代数据帧的最快方法是通过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倍)。为了获得最佳性能,请检查cython的内存视图。

#10 楼

另一个建议是,如果行的子集共享特征,则可以将groupby与矢量化计算结合使用。

#11 楼

我相信遍历DataFrames的最简单,最有效的方法是使用numpy和numba。在这种情况下,在许多情况下,循环的速度可以与向量化操作差不多。如果不是numba,则简单的numpy可能是次佳选择。正如很多次提到的那样,您的默认值应该是向量化,但是由于任何原因,只要给出决定循环,此答案仅考虑有效循环。
对于测试用例,让我们使用@DSM的计算示例百分比变化。这是一种非常简单的情况,实际上,您不会编写循环来计算它,但是这样可以为时序矢量化方法与循环提供合理的基线。 ,我们将它们计时在下面的更大数据集上。
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,itetuples等,它们的数量级要慢一些,
这里的时间非常典型:numpy比pandas快,vectorized比循环快,但是将numba添加到numpy通常会大大加快numpy的速度。
除pandas选项之外的所有内容需要将DataFrame列转换为numpy数组。该转换包含在计时中。
定时中不包括定义/编译numpy / numba函数的时间,但是对于任何大型数据帧,时间通常可以忽略不计。