如何在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个回答

5217

DataFrame.iterrows是一个生成器,它同时产生索引和行(作为Series):

import pandas as pd

df = pd.DataFrame({'c1': [10, 11, 12], 'c2': [100, 110, 120]})
df = df.reset_index()  # make sure indexes pair with number of rows

for index, row in df.iterrows():
    print(row['c1'], row['c2'])

10 100
11 110
12 120

来自文档的必要免责声明

遍历pandas对象通常很慢。在许多情况下,不需要手动遍历行,可以通过以下方法之一避免:

  • 寻找矢量化解决方案:许多操作可以使用内置方法或NumPy函数(布尔)索引等进行。
  • 当您有一个无法一次处理完整DataFrame/Series的函数时,最好使用apply()而不是迭代值。请参阅有关函数应用的文档。
  • 如果您需要对值进行迭代操作但性能很重要,请考虑使用cython或numba编写内部循环。有关此方法的示例,请参阅提高性能部分。

如果您想了解更多关于iter*函数的替代方案的深入内容,请查看本主题中的其他答案。


384
注意:由于iterrows返回每一行的Series,因此它无法在行之间保留数据类型。另外,“你不应该修改正在迭代的东西。”根据pandas0.19.1文档 - viddik13
8
@viddik13,非常感谢你的留言。由于这个问题,我遇到了一个情况,数值类型的值如 431341610650 被读取成了 4.31E+11。是否有一种方法可以保留数据类型而不出现这种情况? - Aziz Alto
50
请使用下面解释的itertuples函数来完成操作。也可以参考http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.itertuples.html。 - Axel
171
不要使用iterrows。iteruples速度更快且保留数据类型。详细信息 - James L.
28
根据 文档 ,遍历 pandas 对象通常比较慢。在许多情况下,不需要手动遍历行 [...]。你的回答(在问题上下文中)是正确的,但没有提到这一点,因此不是很好的回答。 - cs95
显示剩余7条评论

2383

如何在Pandas中迭代DataFrame的行

答案:不要这样做*

Pandas中的迭代是一种反模式,只有在耗尽其他选项时才应该使用。您不应该对带有“iter”名称的任何函数使用超过几千行,否则您将不得不习惯于等待很长时间

您想要打印DataFrame吗?请使用DataFrame.to_string()

您想要计算某些东西吗?在这种情况下,请按以下顺序搜索方法(列表修改自此处):

  1. 向量化
  2. Cython例程
  3. 列表推导式(传统的for循环)
  4. DataFrame.apply():i) 可在Cython中执行的归约操作,ii) 在Python空间中进行迭代。
  5. items() iteritems() (自v1.5.0起弃用)
  6. DataFrame.itertuples()
  7. DataFrame.iterrows()

iterrowsitertuples(在回答此问题时都获得了很多票)应该用于非常罕见的情况,例如为连续处理生成行对象/命名元组,这实际上是这些函数唯一有用的事情。

权威性呼吁

迭代页面上有一个巨大的红色警告框,其中写道:

遍历 pandas 对象通常很慢。在许多情况下,不需要手动遍历行 [...].

*实际情况比“不要”的复杂一些。df.iterrows()是这个问题的正确答案,但“向量化您的操作”才是更好的答案。我会承认有些情况下无法避免迭代(例如某些操作结果依赖于计算上一行的值)。但是,需要熟悉该库才能知道何时使用。如果您不确定是否需要迭代解决方案,则可能不需要。PS:要了解我编写此答案的基本原理,请跳到最底部。


比循环更快:向量化, Cython

许多基本操作和计算都被pandas“向量化”(通过NumPy或Cython化的函数)。这包括算术运算、比较、(大多数)缩减、重塑(如透视)、连接和分组操作。请查看基本功能文档,找到适合您问题的向量化方法。

如果没有适合的方法,可以使用自定义Cython扩展编写自己的方法。


下一个最好的选择:列表推导式*

如果1)没有可用的向量化解决方案,2)性能很重要,但不重要到需要将代码进行Cython优化的程度,以及3)您正在尝试对代码执行逐元素转换,则列表推导式应该是您的下一个选择。有足够的证据表明,对于许多常见的Pandas任务,列表推导式足够快(甚至有时更快)。

公式很简单,

# Iterating over one column - `f` is some function that processes your data
result = [f(x) for x in df['col']]
# Iterating over two columns, use `zip`
result = [f(x, y) for x, y in zip(df['col1'], df['col2'])]
# Iterating over multiple columns - same data type
result = [f(row[0], ..., row[n]) for row in df[['col1', ...,'coln']].to_numpy()]
# Iterating over multiple columns - differing data type
result = [f(row[0], ..., row[n]) for row in zip(df['col1'], ..., df['coln'])]

如果您可以将业务逻辑封装到函数中,则可以使用调用它的列表理解。通过原始Python代码的简单性和速度,您可以使任意复杂的事情都能够运作。
注意事项:
列表理解假设您的数据易于处理 - 这意味着您的数据类型是一致的,并且您没有NaN,但这并不总是可以保证的。
1.第一个比较明显,但处理NaN时,请优先使用内置pandas方法(因为它们具有更好的角落情况处理逻辑),或者确保您的业务逻辑包括适当的NaN处理逻辑。
2.当处理混合数据类型时,应迭代zip(df ['A'],df ['B'],...)而不是df [['A','B']] .to_numpy() ,因为后者会隐式地将数据向上转换为最常见的类型。例如,如果A是数字,而B是字符串,则to_numpy()将将整个数组转换为字符串,这可能不是您想要的。幸运的是,将列一起zip起来是最简单的解决方法。
*由于上面“注意”部分中概述的原因,您的结果可能会有所不同。

一个明显的例子

让我们通过一个简单的例子来演示添加两个pandas列A + B的差异。这是一种可向量化的操作,因此很容易对比上述方法的性能。

参考代码基准测试。底部的行测量了一个使用numpandas编写的函数,这是一种将Pandas与NumPy混合使用以获得最大性能的风格。除非您知道自己在做什么,否则应避免编写numpandas代码。尽可能使用API(即优先使用vec而不是vec_numpy)。

然而,我必须提到的是,并不总是如此明确。有时,“哪种操作方法最佳”这个问题的答案是“取决于您的数据”。我的建议是,在选择一种方法之前,请在您的数据上尝试不同的方法。


我的个人观点*

对于iter家族的各种替代方案进行的大多数分析都是从性能的角度来看的。然而,在大多数情况下,您通常会处理一个相当大小的数据集(不超过几千或100K行),性能将排在解决方案的简单性/可读性之后。

以下是我在选择解决问题的方法时的个人偏好。

对于新手:

向量化(如果可能)apply();列表推导式;itertuples()/iteritems()iterrows();Cython

对于更有经验的人:

向量化(如果可能)apply();列表推导式;Cython;itertuples()/iteritems()iterrows()

向量化是任何可以向量化的问题最惯用的方法。始终寻求向量化!如果有疑问,请查阅文档,或在Stack Overflow上查找与您特定任务相关的现有问题。

我在很多帖子中经常抱怨apply有多糟糕,但我承认对于初学者来说更容易理解它正在做什么。此外,在我的这篇文章中解释了相当多的apply用例。

Cython排名较低,因为正确地处理它需要更多的时间和精力。您通常永远不需要编写需要比列表理解更高性能的Pandas代码。

* 如同任何个人意见一样,请充分考虑!


进一步阅读

* Pandas字符串方法是“向量化”的,因为它们在系列上指定但对每个元素进行操作。底层机制仍然是迭代的,因为字符串操作本质上很难向量化。


为什么我写了这个答案

我注意到新用户的一个常见趋势是询问以下形式的问题:“如何迭代我的df以执行X?”。展示调用iterrows()的代码,同时在for循环内部执行某些操作。这就是原因。对于尚未介绍向量化概念的库的新用户,可能会将解决其问题的代码想象为迭代其数据以执行某些操作。不知道如何迭代DataFrame,他们所做的第一件事就是谷歌并最终在这里,即此问题。然后,他们看到被接受的答案告诉他们如何做,他们闭上眼睛并运行此代码,而没有首先质疑是否迭代是正确的做法。

此答案旨在帮助新用户了解迭代不一定是每个问题的解决方案,并且更好、更快和更成语化的解决方案可能存在,并且值得投资时间进行探索。我不想发起迭代与向量化之争,但我希望新用户在开发使用此库解决其问题的解决方案时能够得到信息。

最后……一个TLDR来总结这篇文章。

enter image description here


4
请注意,“iterrows”和“itertuples”存在重要的限制。有关更多详细信息,请参阅此回答和Pandas文档。 - viddik13
146
这是唯一关注使用pandas时应该采用的惯用技巧的答案,因此对于这个问题来说是最好的答案。学会通过正确的代码获得正确答案(而不是通过错误的代码获得正确答案,即低效、不可扩展、过度拟合具体数据等)是学习pandas(以及数据整体)的重要组成部分。 - LinkBerest
26
我认为你对for循环有些不公平,因为在我的测试中它们只比列表推导慢一点点。诀窍是循环遍历zip(df['A'], df['B'])而不是df.iterrows() - Imperishable Night
8
在列表推导式中,“迭代多个列”的示例需要一个警告:DataFrame.values将会把每一列转换为相同的数据类型。DataFrame.to_numpy()也是如此。幸运的是我们可以使用zip来处理任意数量的列。 - David Wasserman
9
@Dean,我经常得到这样的回复,它实在让我很困惑。这完全取决于养成好习惯的问题。“我的数据很少,性能也不重要,所以我使用这种反模式是可以被原谅的”……?当某一天性能确实很重要时,你会感谢自己提前准备了正确的工具。 - cs95
显示剩余31条评论

567
首先考虑一下是否真的需要在DataFrame中迭代行。请参考cs95的回答以获取其他替代方法。
如果你仍然需要迭代行,你可以使用以下方法。请注意,这些方法中没有提到的一些重要注意事项

itertuples()iterrows() 应该更快。

但是请注意,根据文档(目前是 pandas 0.24.2):

  • iterrows:每行的 dtype 可能不匹配

    因为 iterrows 对每一行返回一个 Series,它不会保留行之间的 dtype(对于 DataFrame,列之间的 dtype 会被保留)。为了在迭代行时保留 dtype,最好使用 itertuples(),它返回值的命名元组,并且通常比 iterrows() 快得多。

  • iterrows: 不要修改行

    在迭代过程中,您绝对不应该修改正在迭代的内容。这并不保证在所有情况下都能正常工作。根据数据类型的不同,迭代器返回的是副本而不是视图,对其进行写入操作将不会产生任何效果。

    请改用DataFrame.apply()

    new_df = df.apply(lambda x: x * 2, axis=1)
    
  • itertuples:

    如果列名无效的Python标识符、重复或以下划线开头,它们将被重命名为位置名称。如果列数较多(>255),则返回常规元组。

查看更多详细信息,请参阅pandas文档中的迭代部分

7
对于一个读完这篇文章的人来说,有一个小问题:在效率方面,df.apply()和itertuples相比如何? - Raul Guarini Riva
8
注意:您还可以像这样说for row in df[['c1','c2']].itertuples(index=True, name=None):,以在行迭代器中仅包括某些列。 - Brian Burns
15
可以使用 row.c1 代替 getattr(row, "c1") - viraptor
3
我发现这个问题因为即使我知道有split-apply-combine,我仍然真的需要迭代DataFrame(正如问题所述)。并不是每个人都有提高性能的“numba”和“cython”的奢侈条件(同样的文档说“总是值得先在Python中进行优化”)。我撰写了这个答案,以帮助其他人避免(有时令人沮丧的)问题,因为其他答案没有提到这些注意事项。误导任何人或告诉“那是正确的做法”从来不是我的本意。我已经改进了答案。 - viddik13
1
@Confounded 通过简单的谷歌搜索,您可以使用iloc预处理数据框:df.iloc[::5, :] 将为您提供每五行。有关更多详细信息,请参见此问题 - viddik13
显示剩余5条评论

247

你应该使用 df.iterrows()。虽然逐行迭代不是特别高效,因为需要创建Series对象。


14
将DataFrame转换为numpy数组(通过.values)直接对数组进行操作,这样做比这个方法快吗?我也遇到了同样的问题,但最终我将其转换为numpy数组并使用cython处理。 - vgoklani
12
如果逐行迭代效率低下,并且您有一个非对象的numpy数组,则几乎肯定使用原始的numpy数组会更快,特别是对于具有许多行的数组。除非您绝对必须,否则应避免逐行迭代。 - Phillip Cloud
9
我已经对 df.iterrows()、df.itertuples() 和 zip(df['a'], df['b']) 的时间消耗进行了一些测试,并在另一个问题的答案中发布了结果:https://dev59.com/T2sz5IYBdhLWcg3wiISe#34311080 - Richard Wong

183

虽然 iterrows() 是一个不错的选择,但有时候 itertuples() 可以更快:

df = pd.DataFrame({'a': randn(1000), 'b': randn(1000),'N': randint(100, 1000, (1000)), 'x': 'x'})

%timeit [row.a * 2 for idx, row in df.iterrows()]
# => 10 loops, best of 3: 50.3 ms per loop

%timeit [row[1] * 2 for row in df.itertuples()]
# => 1000 loops, best of 3: 541 µs per loop

10
你两个示例中的时间差异,很大程度上是因为你似乎在使用 .iterrows() 命令时采用了基于标签的索引方式,而在使用 .itertuples() 命令时则采用了基于整数的索引方式。 - Alex
4
在我的机器上,针对一个基于金融数据的数据帧(时间戳和4个浮点数),itertuples比iterrows快19.57倍。只有使用for a,b,c in izip(df["a"],df["b"],df["c"]: 几乎同样快速。 - harbun
9
你能解释一下为什么它更快吗? - Abe Miessler
8
iterrows()方法将数据的每一行包装成一个Series对象,而itertuples()方法则不会。 - miradulo
5
请注意,列的顺序实际上是不确定的,因为df是从一个字典创建的,所以row[1]可能指任意一列。不过事实证明,整数列和浮点数列的时间大致相同。 - Brian Burns
显示剩余4条评论

160
你可以按照以下方式使用df.iloc函数:
for i in range(0, len(df)):
    print(df.iloc[i]['c1'], df.iloc[i]['c2'])

2
我知道应该避免这种方法,而选择使用iterrows或itertuples,但是了解其原因会很有趣。你有什么想法吗? - rocarvaj
22
这是我所知道的唯一有效的技术,如果你想保留数据类型并且还要按列名称引用。 itertuples 会保留数据类型,但会删除任何不喜欢的名称。iterrows 则相反。 - Ken Williams
7
花费数小时试图理解pandas数据结构的个性化特点并实现简单而富有表现力的操作,以此得到易读的代码。 - Sean Anderson
1
虽然 for i in range(df.shape[0]) 可以稍微加快这种方法的速度,但对于我的应用程序来说,它仍然比上面的 iterrows() 方法慢了大约 3.5 倍。 - Kim Miller
2
在大型数据框上,这种方法似乎更好,因为my_iter = df.itertuples()需要双倍的内存,并且复制它需要很长时间。对于iterrows()也是如此。 - Bastiaan

129
你还可以使用df.apply()来迭代行并为一个函数访问多列。 docs: DataFrame.apply()
def valuation_formula(x, y):
    return x * y * 0.5

df['price'] = df.apply(lambda row: valuation_formula(row['x'], row['y']), axis=1)

请注意,这里的axis=1axis='columns'是相同的,它用于将函数应用于每一行而不是每一列。如果未指定,默认行为是将函数应用于每一列。

没关系,我搞定了。将函数调用行改为 **df_new = df['Workclass'].apply(same thing)**。 - SRS
3
将轴默认值设为0是最糟糕的。 - zthomas.nc
14
请注意,apply不会对行进行迭代,而是将函数逐行应用。如果您确实需要迭代和索引,例如在比较不同行的值时(在这种情况下,您只能进行迭代),则上面的代码将无法正常工作。 - gented
@gented...你在这里看到“iteratite”这个词了吗? - cs95
2
这是Pandas的适当答案。 - dhruvm
显示剩余3条评论

71

如何高效迭代

如果你真的需要迭代一个Pandas数据框,你可能想要避免使用iterrows()。有不同的方法,通常的iterrows()远非最佳选择。itertuples()可以快100倍。

简而言之:

  • 一般来说,使用df.itertuples(name=None)。特别是当你有固定数量的列且少于255列时。 见点(3)
  • 否则,使用df.itertuples(),除非你的列具有特殊字符,例如空格或'-'。 见点(2)
  • 即使你的数据框有奇怪的列,也可以使用itertuples(),使用最后一个示例。 见点(4)
  • 只有在前面的解决方案都不可行时才使用iterrows()见点(1)

在Pandas数据框中迭代行的不同方法:

生成一个有一百万行和4列的随机数据框:

    df = pd.DataFrame(np.random.randint(0, 100, size=(1000000, 4)), columns=list('ABCD'))
    print(df)

1) 通常的iterrows()很方便,但是速度非常慢:

start_time = time.clock()
result = 0
for _, row in df.iterrows():
    result += max(row['B'], row['C'])

total_elapsed_time = round(time.clock() - start_time, 2)
print("1. Iterrows done in {} seconds, result = {}".format(total_elapsed_time, result))

2) 默认的itertuples()已经快得多,但它不能处理列名重复或者列名不能简单转换为Python变量名的情况,比如 My Col-Name is very Strange(如果您的列是重复的或者有某些列名不能简单转换为Python变量名,您应该避免使用这种方法)。:

start_time = time.clock()
result = 0
for row in df.itertuples(index=False):
    result += max(row.B, row.C)

total_elapsed_time = round(time.clock() - start_time, 2)
print("2. Named Itertuples done in {} seconds, result = {}".format(total_elapsed_time, result))

3) 默认的itertuples()使用name=None更快,但不太方便,因为您必须为每个列定义一个变量。

start_time = time.clock()
result = 0
for(_, col1, col2, col3, col4) in df.itertuples(name=None):
    result += max(col2, col3)

total_elapsed_time = round(time.clock() - start_time, 2)
print("3. Itertuples done in {} seconds, result = {}".format(total_elapsed_time, result))

4) 最后,命名为itertuples()的方法比前面的方法慢,但是您不必为每列定义一个变量,并且它可以使用列名称,例如My Col-Name is very Strange

start_time = time.clock()
result = 0
for row in df.itertuples(index=False):
    result += max(row[df.columns.get_loc('B')], row[df.columns.get_loc('C')])

total_elapsed_time = round(time.clock() - start_time, 2)
print("4. Polyvalent Itertuples working even with special characters in the column name done in {} seconds, result = {}".format(total_elapsed_time, result))

输出:

         A   B   C   D
0       41  63  42  23
1       54   9  24  65
2       15  34  10   9
3       39  94  82  97
4        4  88  79  54
...     ..  ..  ..  ..
999995  48  27   4  25
999996  16  51  34  28
999997   1  39  61  14
999998  66  51  27  70
999999  51  53  47  99

[1000000 rows x 4 columns]

1. Iterrows done in 104.96 seconds, result = 66151519
2. Named Itertuples done in 1.26 seconds, result = 66151519
3. Itertuples done in 0.94 seconds, result = 66151519
4. Polyvalent Itertuples working even with special characters in the column name done in 2.94 seconds, result = 66151519

本文是关于iterrows和itertuples之间非常有趣的比较


那么,为什么Pandas中会存在这些低效的方法呢?如果使用iterrows和itertuples不应该被使用是“常识”,那么它们为什么会存在?或者说,为什么那些方法没有被Pandas的维护人员后台更新和优化呢? - Monty
@Monty,不是所有的操作都可以向量化。 - Romain Capron

53

我正在寻找如何迭代行列,最后来到了这里:

如下所示:

for i, row in df.iterrows():
    for j, column in row.iteritems():
        print(column)

在可能的情况下,请避免使用iterrows()方法。我在如何高效迭代答案中解释了原因。 - Romain Capron

42

我们有多种方法可以完成同样的任务,许多人分享了他们的答案。

我发现以下两种方法易于操作并且高效:

  1. DataFrame.iterrows()
  2. DataFrame.itertuples()

示例:

 import pandas as pd
 inp = [{'c1':10, 'c2':100}, {'c1':11,'c2':110}, {'c1':12,'c2':120}]
 df = pd.DataFrame(inp)
 print (df)

 # With the iterrows method

 for index, row in df.iterrows():
     print(row["c1"], row["c2"])

 # With the itertuples method

 for row in df.itertuples(index=True, name='Pandas'):
     print(row.c1, row.c2)

注意:itertuples()应该比iterrows()更快。


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