关于python:使用pandas循环数据帧的最有效方法是什么?

What is the most efficient way to loop through dataframes with pandas?

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

例如,我使用的是从雅虎财经(Yahoo Finance)获取的以下msft csv文件:

1
2
3
4
5
6
7
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

....

然后我执行以下操作:

1
2
3
4
5
6
7
8
9
#!/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只能逐列迭代。


Panda的最新版本现在包括一个内置函数,用于遍历行。

1
2
3
for index, row in df.iterrows():

    # do some logic here

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

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


熊猫是基于numpy数组的。使用numpy数组加速的关键是一次对整个数组执行操作,而不是逐行或逐项执行。

例如,如果close是一个一维数组,并且您希望日复一日的百分比变化,

1
pct_change = close[1:]/close[:-1]

它将整个百分比变化数组计算为一条语句,而不是

1
2
3
pct_change = []
for row in close:
    pct_change.append(...)

因此,尝试完全避免python循环for i, row in enumerate(...),以及考虑如何对整个数组(或数据帧)整体执行操作,而不是逐行执行计算。


和前面提到的一样,pandas对象在一次处理整个数组时效率最高。然而,对于那些真正需要循环访问熊猫数据框架来执行类似我这样的操作的人,我找到了至少三种方法来执行。我做了一个简短的测试,看看三个测试中哪一个最省时。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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

结果:

1
[0.5639059543609619, 0.017839908599853516, 0.005645036697387695]

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

以下是一些优点和缺点:

  • .iterrows():在单独的变量中返回索引和行项,但速度明显较慢
  • .ITertuples():比.ITerRows()快,但返回索引和行项目,ir[0]是索引
  • zip:最快,但无法访问行的索引


您可以通过转置并调用ITeritems来循环这些行:

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

在这种情况下,我不确定效率。为了在迭代算法中获得可能的最佳性能,您可能希望探索用Cython编写它,这样您就可以执行如下操作:

1
2
3
4
5
6
7
8
9
10
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++一样快的东西。


我注意到尼克·克劳福德的回答后,查看了iterrows,但发现它生成(index,series)元组。不确定哪种方法最适合您,但我最终使用了itertuples方法来解决我的问题,它生成(index,row_value1…)元组。

还有iterkv,它迭代(列、系列)元组。


您有三种选择:

按索引(最简单):

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

使用iTerrows(最常用):

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

使用ITertuples(最快):

1
2
>>> for row in df.itertuples():
...     print ("df[" + str(row.Index) +"]['B']=" + str(row.B))

三个选项显示如下:

1
2
3
4
5
6
7
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

资料来源:神经网络.io


正如一个小的添加,如果您有一个复杂的函数要应用于单个列,那么您也可以执行应用:

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

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


正如@joris所指出的,iterrowsitertuples慢得多,itertuplesiterrows胖约100倍,我在一个有5027505条记录的数据帧中测试了这两种方法的速度,结果是iterrows的,是1200it/s,itertuples是120000it/s。

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

1
2
3
4
5
6
7
8
9
10
11
>>> 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进行访问。

1
2
3
4
5
6
7
8
9
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]

不是Python?当然。但是很快。

如果你想挤出更多的果汁,你会想看看赛通。赛通将让你获得巨大的加速(想想10倍-100倍)。要获得最佳性能,请检查Cython的内存视图。


另一个建议是,如果行的子集共享特性(这允许您这样做),则将groupby与矢量化计算结合起来。