如何在Pandas中迭代DataFrame中的行

4017

我有一个Pandas数据框,df

   c1   c2
0  10  100
1  11  110
2  12  120

我该如何迭代遍历这个数据框的行?对于每一行,我想通过列名访问其元素(单元格中的值)。例如:
for row in df.rows:
    print(row['c1'], row['c2'])

我找到了一个类似的问题,建议使用以下任一方法:

  • 对于日期和行数据,使用df.T.iteritems()进行循环:
    
  • 使用df.iterrows()进行循环:
    

但我不理解 row 对象是什么以及如何使用它。


29
df.iteritems() 迭代的是列而不是行,因此想要迭代行,需要转置(使用“T”操作),将行和列互换位置(对角线反转)。这样,使用 df.T.iteritems() 时,你会有效地遍历原始数据框的每一行。 - Stefan Gruenwald
169
与cs95所说的相反,有很好的理由想要遍历一个数据框,因此新用户不应感到气馁。一个例子是如果您想使用每行的值作为输入来执行一些代码。此外,如果您的数据框相当小(例如少于1000个项目),性能实际上不是问题。 - oulenz
6
在Python中,数据框似乎是默认的表格格式。因此,无论您想读取CSV文件,还是有一个字典列表需要操作其值,或者您想执行简单的连接、分组或窗口操作,都可以使用数据框,即使您的数据相对较小也是如此。 - oulenz
37
我同意@oulenz的观点。据我所知,即使数据集很小,pandas也是读取csv文件的首选。使用API来操作数据更加易于编程。 - F.S.
9
如果您是这个线程的初学者,并且不熟悉pandas库,那么值得退一步评估迭代是否确实是解决问题的方法。在某些情况下,它是有效的。但在大多数情况下,它并不是最佳选择。重要的是通过向他们介绍向量化的概念来帮助初学者了解如何编写“好代码”和“只是能运行的代码”的区别,以及何时使用哪种方法。 - cs95
显示剩余6条评论
34个回答

27

你可以编写自己的迭代器,实现namedtuple

from collections import namedtuple

def myiter(d, cols=None):
    if cols is None:
        v = d.values.tolist()
        cols = d.columns.values.tolist()
    else:
        j = [d.columns.get_loc(c) for c in cols]
        v = d.values[:, j].tolist()

    n = namedtuple('MyTuple', cols)

    for line in iter(v):
        yield n(*line)

这与pd.DataFrame.itertuples是直接可比的。我的目标是以更高效的方式执行相同的任务。


对于给定的数据框,使用我的函数:

list(myiter(df))

[MyTuple(c1=10, c2=100), MyTuple(c1=11, c2=110), MyTuple(c1=12, c2=120)]

或者使用pd.DataFrame.itertuples

list(df.itertuples(index=False))

[Pandas(c1=10, c2=100), Pandas(c1=11, c2=110), Pandas(c1=12, c2=120)]

一项全面的测试
我们测试将所有列都可用并对列进行子集划分。

def iterfullA(d):
    return list(myiter(d))

def iterfullB(d):
    return list(d.itertuples(index=False))

def itersubA(d):
    return list(myiter(d, ['col3', 'col4', 'col5', 'col6', 'col7']))

def itersubB(d):
    return list(d[['col3', 'col4', 'col5', 'col6', 'col7']].itertuples(index=False))

res = pd.DataFrame(
    index=[10, 30, 100, 300, 1000, 3000, 10000, 30000],
    columns='iterfullA iterfullB itersubA itersubB'.split(),
    dtype=float
)

for i in res.index:
    d = pd.DataFrame(np.random.randint(10, size=(i, 10))).add_prefix('col')
    for j in res.columns:
        stmt = '{}(d)'.format(j)
        setp = 'from __main__ import d, {}'.format(j)
        res.at[i, j] = timeit(stmt, setp, number=100)

res.groupby(res.columns.str[4:-1], axis=1).plot(loglog=True);

在这里输入图像描述

在这里输入图像描述


5
不想读代码的人:蓝色线是“intertuples”,橙色线是通过yield块迭代器的列表。“interrows”没有进行比较。 - James L.

24

要循环遍历 dataframe 中的所有行,您可以使用:

for x in range(len(date_example.index)):
    print date_example['Date'].iloc[x]

1
这是链式索引。我不建议这样做。 - cs95
@cs95你会推荐什么? - Pedro Lobito
如果你想让这个工作起来,调用df.columns.get_loc来获取日期列的整数索引位置(在循环之外),然后在内部使用单个iloc索引调用。 - cs95

23
 for ind in df.index:
     print df['c1'][ind], df['c2'][ind]

1
当在大型数据框(例如数百万行)上使用此选项时,其性能如何? - Bazyli Debowski
老实说,我不确定,但我认为与最佳答案相比,经过的时间大致相同,因为两种情况都使用了“for”结构。但在某些情况下,内存可能会有所不同。 - Grag2015
6
这是链式索引,不要使用它! - cs95

17
有时候一个有用的模式是:
# Borrowing @KutalmisB df example
df = pd.DataFrame({'col1': [1, 2], 'col2': [0.1, 0.2]}, index=['a', 'b'])
# The to_dict call results in a list of dicts
# where each row_dict is a dictionary with k:v pairs of columns:value for that row
for row_dict in df.to_dict(orient='records'):
    print(row_dict)

导致结果为:

{'col1':1.0, 'col2':0.1}
{'col1':2.0, 'col2':0.2}

17

更新:cs95已更新他的答案,包括纯numpy矢量化。你可以直接参考他的答案。


cs95的研究结果表明,与其他Pandas方法相比,使用Pandas向量化技术可以更快地计算数据框。

我想补充一点,如果您先将数据框转换为NumPy数组,然后再使用向量化技术,速度甚至比Pandas数据框向量化还要快(包括将其转换回数据框系列的时间)。

如果您将以下函数添加到cs95的基准代码中,则可以很明显地看出这一点:

def np_vectorization(df):
    np_arr = df.to_numpy()
    return pd.Series(np_arr[:,0] + np_arr[:,1], index=df.index)

def just_np_vectorization(df):
    np_arr = df.to_numpy()
    return np_arr[:,0] + np_arr[:,1]

Enter image description here


你是如何绘制这个图的? - wwnde

15

为了循环遍历一个 dataframe 中的所有行并方便地使用每一行的值,可以将 namedtuples 转换成 ndarray。例如:

df = pd.DataFrame({'col1': [1, 2], 'col2': [0.1, 0.2]}, index=['a', 'b'])

迭代行:

for row in df.itertuples(index=False, name='Pandas'):
    print np.asarray(row)

导致结果:

[ 1.   0.1]
[ 2.   0.2]
请注意,如果index=True索引将作为元组的第一个元素添加,这可能对某些应用程序不合适。

15

简而言之:

  • 如果可能的话,请使用向量化
  • 如果某个操作无法向量化 - 使用列表推导式
  • 如果您需要一个表示整行的单个对象 - 请使用 itertuples
  • 如果以上方法速度太慢 - 尝试 swifter.apply
  • 如果仍然太慢 - 尝试使用Cython程序

基准测试

迭代Pandas DataFrame中行的基准测试


Cython会有所帮助,但对于大多数人来说,numpy/numba可能更易于使用。 - JohnE

15

有一种方法可以在获取DataFrame的同时迭代行,而不是Series。我没有看到任何人提到过,您可以将索引作为列表传递以将行返回为DataFrame:

for i in range(len(df)):
    row = df.iloc[[i]]

注意双括号的用法,它返回一个只有一行的 DataFrame。


这对于在排序后获取数据框中第n大的行非常有帮助。谢谢! - Jason Harrison

13

有时候循环确实比向量化代码更好

正如许多答案在这里正确指出的那样,在Pandas中,您应该默认编写向量化代码(带有其隐式循环),而不是自己尝试显式循环。 但问题仍然存在,您是否应该在Pandas中编写循环,如果是的话,在这些情况下最好循环的方法是什么。

我认为至少有一种常规情况可以使用循环:当您需要以某种复杂方式依赖于其他行中的值计算某些函数时。 在这种情况下,循环代码通常比向量化代码更简单、更易读且更少出错。

循环代码甚至可能更快,正如下面所示,因此,在速度非常重要的情况下,循环可能是有意义的。但实际上,这些情况只是您本来就应该开始使用numpy/numba(而不是Pandas)的情况的子集,因为经过优化的numpy/numba几乎总是比Pandas更快。

让我们用一个例子来展示这一点。 假设您想要对列进行累加求和,但每当另一列等于零时都将其重置:

import pandas as pd
import numpy as np

df = pd.DataFrame( { 'x':[1,2,3,4,5,6], 'y':[1,1,1,0,1,1]  } )

#   x  y  desired_result
#0  1  1               1
#1  2  1               3
#2  3  1               6
#3  4  0               4
#4  5  1               9
#5  6  1              15
这是一个很好的例子,您可以使用一行Pandas代码来实现这一点,尽管它并不特别易读,特别是如果您对Pandas不够熟悉的话。
df.groupby( (df.y==0).cumsum() )['x'].cumsum()

对于大多数情况来说,那已经足够快了,尽管你也可以通过避免使用groupby来编写更快的代码,但它可能会更难阅读。

或者,如果我们将其编写为一个循环怎么办?您可以使用NumPy执行以下操作:

import numba as nb

@nb.jit(nopython=True)  # Optional
def custom_sum(x,y):
    x_sum = x.copy()
    for i in range(1,len(df)):
        if y[i] > 0: x_sum[i] = x_sum[i-1] + x[i]
    return x_sum

df['desired_result'] = custom_sum( df.x.to_numpy(), df.y.to_numpy() )

诚然,将DataFrame列转换为NumPy数组需要一些开销,但核心代码只有一行,即使您对Pandas或NumPy一无所知,也可以读懂:

if y[i] > 0: x_sum[i] = x_sum[i-1] + x[i]

这段代码的实际运行速度比向量化代码更快。在100,000行的一些快速测试中,以上方法比groupby方法快约10倍。请注意,其中一个关键是numba(可选)。没有“@nb.jit”行,循环代码实际上比groupby方法慢约10倍。

显然,对于此示例,您可能更喜欢使用一行pandas代码而不是编写带有相关开销的循环。但是,对于更复杂的问题版本,NumPy/numba循环方法的可读性或速度可能更加合理。


13

我建议使用 df.at[row, column] (source) 来迭代所有的 Pandas 单元格。

例如:

for row in range(len(df)):
  print(df.at[row, 'c1'], df.at[row, 'c2'])

输出结果将是:

10 100
11 110
12 120

奖金

您还可以使用df.at[row, column] = newValue修改单元格的值。

for row in range(len(df)):
  df.at[row, 'c1'] = 'data-' + str(df.at[row, 'c1'])
  print(df.at[row, 'c1'], df.at[row, 'c2']) 

输出将是:

data-10 100
data-11 110
data-12 120

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接