使用pandas遍历数据框的最高效方法是什么?
我想对数据框里的金融数据进行一些复杂的操作,而且是按顺序来做。
比如,我用的是从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
只会一列一列地遍历。
13 个回答
138
正如之前提到的,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核 32GB 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
169
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(...)
,要考虑如何对整个数组(或数据框)进行操作,而不是一行一行地处理。
425
最新版本的pandas现在有一个内置的功能,可以用来逐行遍历数据。
for index, row in df.iterrows():
# do some logic here
如果你想要更快一点,可以使用 itertuples()
这个方法。
不过,unutbu建议使用numpy的函数来避免逐行遍历,这样能写出最快的代码。