使用pandas遍历数据框的最高效方法是什么?

405 投票
13 回答
664041 浏览
提问于 2025-04-17 04:42

我想对数据框里的金融数据进行一些复杂的操作,而且是按顺序来做。

比如,我用的是从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的函数来避免逐行遍历,这样能写出最快的代码。

撰写回答