使用 Pandas 循环遍历数据框的最有效方法是什么?

2024-12-25 08:51:00
admin
原创
129
摘要:问题描述:我想按顺序对数据框中的财务数据执行我自己的复杂操作。例如,我正在使用来自Yahoo Finance的以下 MSFT CSV 文件:Date,Open,High,Low,Close,Volume,Adj Close 2011-10-19,27.37,27.47,27.01,27.13,42880000...

问题描述:

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

例如,我正在使用来自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不幸的是,只能逐列迭代。


解决方案 1:

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

for index, row in df.iterrows():

    # do some logic here

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

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

解决方案 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(...),并考虑如何对整个数组(或数据框)进行整体操作而不是逐行执行计算。

解决方案 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

结果:

[0.5639059543609619, 0.017839908599853516, 0.005645036697387695]

这可能不是衡量时间消耗的最佳方法,但对我来说很快。

在我看来,这里有一些优点和缺点:

  • .iterrows():在单独的变量中返回索引和行项,但速度明显较慢

  • .itertuples():比 .iterrows() 更快,但会与行项一起返回索引,ir[0] 是索引

  • 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

解决方案 4:

您可以通过转置然后调用 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 ++ 一样快的速度。

解决方案 5:

您有三个选择:

按索引(最简单):

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

解决方案 6:

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

还有iterkv,它遍历(列,系列)元组。

解决方案 7:

作为一个小小的补充,如果您有一个应用于单个列的复杂函数,您也可以执行应用:

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

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

解决方案 8:

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

如果使用itertuples,请注意 for 循环中的每个元素都是一个命名元组,因此要获取每列的值,可以参考以下示例代码

>>> 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(如您所做的那样) 或通过分别访问每列来访问底层 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]

不是 Pythonic?当然。但是速度很快。

如果你想从循环中榨取更多的能量,那么你需要研究一下cython。 Cython 会让你获得巨大的加速(比如 10 倍到 100 倍)。 要获得最佳性能,请检查cython 的内存视图。

解决方案 10:

另一个建议是,如果行的子集具有共同的特征,则将 groupby 与矢量化计算相结合。

解决方案 11:

看看最后一个

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

解决方案 12:

我认为循环 DataFrames 的最简单、最有效的方法是使用 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 快,而矢量化比循环快,但是将 numba 添加到 numpy 通常会显著加快 numpy 的速度。

  • 除了 pandas 选项之外的所有内容都需要将 DataFrame 列转换为 numpy 数组。该转换包含在计时中。

  • 定义/编译 numpy/numba 函数的时间不包含在计时中,但对于任何大型数据框来说,它通常是计时中可以忽略不计的部分。

解决方案 13:

这是最有效的方法吗?鉴于熊猫注重速度,我认为一定有一些特殊的函数来迭代值...

绝对有一种最有效的方法:矢量化。之后是列表理解,然后是itertuples()。远离iterrows()。这非常糟糕,df["A"][i]甚至比使用常规类型索引的原始 for 循环还要慢得多。

我在这里详细介绍了 13 种方法,对它们全部进行了速度测试,并展示了所有代码:如何迭代 Pandas DataFrame[使用和不使用] 迭代。

我花了几周时间写这个答案。结果如下:

在此处输入图片描述

关键要点:

  1. 如果您的目标是编写易于阅读、编写和维护的代码,同时仍然非常快,请使用列表推导。 它仅比纯矢量化慢约 10 倍。

  2. 如果您的目标是代码尽可能快,请使用纯矢量化if。但是,当您有复杂的方程式(例如,公式中为每一行计算的语句)时,读写会更困难。

iterrows()诸如此类的函数非常慢,比纯矢量化慢约 600 倍

为了证明我快速测试的所有 13 种技术即使在复杂的公式中也是可行的,我选择了这个非平凡公式来计算所有技术,其中A,,,和是列,下标是行(例如:是向上 2 行,是上一行,是当前行,是下一行,等等)BC`Dii-2i-1i`i+1

在此处输入图片描述

有关更多详细信息以及所有 13 种技术的代码,请参阅我的主要答案:如何DataFrame在不进行迭代的情况下遍历 Pandas 。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1499  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1344  
  IPD(Integrated Product Development)流程是一套先进的产品开发管理体系,旨在通过整合跨部门资源,实现产品的高效开发与交付。在IPD流程中,确保项目按时交付是至关重要的,它直接关系到企业的市场竞争力和客户满意度。以下将从多个关键方面探讨如何在IPD流程阶段确保项目按时交付。精准的项目规划项...
IPD流程分为几个阶段   10  
  IPD(Integrated Product Development)流程是一套先进的产品开发管理体系,旨在缩短产品上市时间、提高产品质量、降低成本并增强企业的市场竞争力。深入理解IPD流程阶段的关键要素,对于企业成功实施IPD,实现产品开发的高效运作至关重要。IPD流程的概念与重要性IPD流程强调将产品开发视为一个整...
IPD测试流程   10  
  IPD(Integrated Product Development)产品开发流程是一套先进的、旨在提高产品开发效率与质量的管理体系。在这个体系中,评审环节起着至关重要的作用,它们如同关卡,确保产品在各个阶段都朝着正确的方向前进,符合市场需求和企业战略。其中有四个评审环节尤为关键,它们分别在不同阶段对产品进行全面审视,...
研发IPD流程   15  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用