我想以顺序方式对数据框中的财务数据执行我自己的复杂操作。
例如,我正在使用从 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()
?
unutbu
,NumPy 似乎支持向量化操作 (The key to speed with NumPy arrays is to perform your operations on the whole array at once
)。
最新版本的 pandas 现在包括一个用于迭代行的内置函数。
for index, row in df.iterrows():
# do some logic here
或者,如果您希望它更快,请使用 itertuples()
但是,unutbu 建议使用 numpy 函数来避免迭代行将产生最快的代码。
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(...)
,并考虑如何通过对整个数组(或数据帧)作为一个整体而不是逐行的操作来执行计算。
就像之前提到的,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() 快,但返回 index 和 row items,ir[0] 是 index
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
zip()
返回一个迭代器,所以使用 list(zip())
t.index
循环遍历索引吗?
iterrows()
来说甚至更差: [0.6970570087432861, 0.008062124252319336, 0.0036787986755371094]
您可以通过转置然后调用 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++。
df.iterrows()
。
df.T.iteritems()
是一个很好的解决方案,而不是使用 df.iterrows()
def my_algo(ndarray[object] dates, ndarray[float64_t] opn, ^ SyntaxError: invalid syntax
你有三个选择:
通过 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
我在注意到 Nick Crawford's 答案后检查了 iterrows
,但发现它产生 (index, Series) 元组。不确定哪个最适合您,但我最终使用 itertuples
方法来解决我的问题,它产生 (index, row_value1...) 元组。
还有 iterkv
,它遍历 (column, series) 元组。
iterkv
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.
作为一个小补充,如果您有一个应用于单个列的复杂函数,您也可以执行应用:
http://pandas.pydata.org/pandas-docs/dev/generated/pandas.DataFrame.apply.html
df[b] = df[a].apply(lambda col: do stuff with col here)
apply
也可以应用于多个列:df['c'] = df[['a','b']].apply(lambda x: do stuff with x[0] and x[1] here, axis=1)
x
-> col
。更好的名字
正如 @joris 所指出的,iterrows
比 itertuples
慢得多,而 itertuples
比 iterrows
快大约 100 倍,我在具有 500 万条记录的 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
当然,迭代数据帧的最快方法是通过 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]
不是蟒蛇?当然。但是快。
如果您想从循环中挤出更多的汁液,您需要查看 cython。 Cython 会让你获得巨大的加速(想想 10 倍到 100 倍)。为获得最佳性能检查 memory views for cython。
如果行的子集共享允许您这样做的特征,另一个建议是将 groupby 与矢量化计算结合起来。
看最后一个
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
我相信循环遍历 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 函数的时间不包括在计时中,但对于任何大型数据帧,通常是计时的一个可忽略不计的组成部分。
不定期副业成功案例分享
iterrows
非常慢(它将每一行转换为一个系列,可能会弄乱您的数据类型)。当您需要迭代器时,最好使用itertuples
itertuples
比iterrows
胖了大约 100 倍。