我有一个熊猫数据帧,df:

   c1   c2
0  10  100
1  11  110
2  12  120

如何迭代此数据帧的行?对于每一行,我希望能够通过列的名称访问其元素(单元格中的值)。例如:

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

我发现了一个类似的问题,建议使用以下任一项:

for date, row in df.T.iteritems():
for row in df.iterrows():

但我不知道row对象是什么,以及如何使用它。


当前回答

如何在Pandas中迭代DataFrame中的行

回答:不要*!

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

是否要打印DataFrame?使用DataFrame.to_string()。

你想计算什么吗?在这种情况下,按以下顺序搜索方法(从此处修改列表):

矢量化Cython例程列出理解(循环的香草)DataFrame.apply():i)可以在Cython中执行的缩减,ii)Python空间中的迭代DataFrame.itertuples()和itertimes()DataFrame.iterrows()

iterrows和itertules(都在回答这个问题时获得了很多投票)应该在非常罕见的情况下使用,例如为顺序处理生成行对象/nametuples,这实际上是这些函数唯一有用的功能。

向当局提出上诉

迭代的文档页面有一个巨大的红色警告框,上面写着:

遍历熊猫对象通常很慢。在许多情况下,不需要手动迭代行[…]。

*实际上,这比“不”要复杂一点。iterrows()是这个问题的正确答案,但“矢量化操作”是更好的答案。我承认,在某些情况下,迭代是无法避免的(例如,某些操作的结果取决于为前一行计算的值)。然而,需要对图书馆有一定的了解才能知道什么时候。如果您不确定是否需要迭代解决方案,则可能不需要。PS:要了解更多关于我写这个答案的理由,请跳到最底部。


比循环更快:矢量化,Cython

大量的基本操作和计算都由panda“矢量化”(通过NumPy或通过Cythonized函数)。这包括算术、比较、(大多数)归约、重塑(如旋转)、联接和分组操作。查看有关基本功能的文档,找到适合您问题的矢量化方法。

如果没有,请使用自定义Cython扩展编写自己的代码。


下一件最好的事:列出理解*

如果1)没有可用的矢量化解决方案,2)性能很重要,但还不够重要,不足以解决代码代码化的麻烦,3)您正在尝试对代码执行元素化转换,那么列表理解应该是您的下一个选择。有大量证据表明,对于许多常见的熊猫任务,列表理解足够快(有时甚至更快)。

公式简单,

# 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,但这不能总是得到保证。

第一种方法更为明显,但在处理NaN时,如果存在内置panda方法(因为它们具有更好的角案例处理逻辑),请选择内置panda,或者确保您的业务逻辑包含适当的NaN处理逻辑。当处理混合数据类型时,应该迭代zip(df['A'],df['B'],…)而不是df[['A','B'].to_num(),因为后者隐式地将数据上转换为最常见的类型。例如,如果A是数字,B是字符串,to_numpy()将整个数组强制转换为字符串,这可能不是您想要的。幸运的是,将您的专栏压缩在一起是解决此问题的最直接的方法。

*您的里程数可能会因上述“注意事项”部分中概述的原因而有所不同。


一个明显的例子

让我们用一个添加两个panda列a+B的简单示例来演示差异。这是一个可矢量化的操作,因此很容易对比上面讨论的方法的性能。

基准测试代码,供您参考。底部的一行衡量的是用numpanda编写的函数,这是一种Pandas风格,它与NumPy大量混合,以挤出最大的性能。除非你知道自己在做什么,否则应该避免编写numpanda代码。尽可能使用API(即,更喜欢vec而不是vec_numpy)。

然而,我应该提一提,这并不总是那么简单。有时候,“手术的最佳方法是什么”的答案是“这取决于你的数据”。我的建议是在确定数据之前,先测试不同的方法。


我的个人意见*

对iter系列的各种替代品进行的大多数分析都是从性能角度进行的。然而,在大多数情况下,您通常会处理一个大小合理的数据集(只有几千或100K行),性能将仅次于解决方案的简单性/可读性。

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

对于新手:

矢量化(如果可能);apply();列出理解;itertples()/iteritems();迭代();赛龙

对于更有经验的人:

矢量化(如果可能);apply();列出理解;Cython;itertples()/iteritems();迭代()

对于任何可以矢量化的问题,矢量化都是最常用的方法。始终寻求矢量化!如果有疑问,请查阅文档,或查看Stack Overflow以了解有关特定任务的现有问题。

我确实倾向于在我的很多帖子中继续谈论申请有多糟糕,但我承认初学者更容易理解申请的内容。此外,在我的这篇文章中解释了许多应用的用例。

Cython在榜单上排名靠后,因为它需要更多的时间和精力才能正确完成。通常情况下,您永远不需要使用panda编写代码,因为它需要这样的性能水平,即使是列表理解也无法满足。

*与任何个人意见一样,请带着大量的盐!


进一步阅读

熊猫10分钟,以及基本功能-有用的链接,可向您介绍熊猫及其矢量化*/细胞化函数库。提高性能-从提高标准熊猫操作的文档中获得的入门知识熊猫的循环真的很糟糕吗?我什么时候该关心?-我对列表的理解及其对各种操作的适用性(主要是涉及非数字数据的操作)的详细记录我什么时候应该(不)在代码中使用pandas apply()?-apply很慢(但没有iter*家族那么慢。但是,在某些情况下,可以(或应该)将apply视为一种严肃的选择,特别是在某些GroupBy操作中)。

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


为什么我写这个答案

我从新用户那里注意到的一个常见趋势是,以“我如何迭代我的df来做X?”的形式提出问题。显示在for循环内执行某些操作时调用iterrows()的代码。原因如下。一个没有被引入矢量化概念的库新用户可能会将解决他们问题的代码设想为迭代他们的数据来做一些事情。不知道如何在DataFrame上迭代,他们做的第一件事就是谷歌搜索它,并在这里结束这个问题。然后,他们看到被接受的答案,告诉他们如何去做,然后闭上眼睛运行这段代码,而不必首先质疑迭代是否正确。

这个答案的目的是帮助新用户理解迭代不一定是每个问题的解决方案,可以存在更好、更快、更地道的解决方案。我并不是想挑起一场迭代与矢量化的战争,但我希望新用户在使用这个库开发解决问题的解决方案时得到通知。

其他回答

有时,有用的模式是:

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

您还可以使用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)

有一种方法可以在返回DataFrame而不是Series时迭代抛出行。我没有看到任何人提到可以将索引作为列表传递给要作为DataFrame返回的行:

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

注意双括号的用法。这将返回具有单行的DataFrame。

DataFrame.iterrows是一个生成索引和行(作为一个系列)的生成器:

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中的默认计划应该是编写矢量化代码(带有隐式循环),而不是自己尝试显式循环。但问题仍然是你是否应该在Pandas中编写循环,如果是的话,在这些情况下最好的循环方式是什么。

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

循环代码甚至可能更快,正如您将在下面看到的那样,所以在速度至关重要的情况下,循环可能是有意义的。但实际上,这些只是一些情况的子集,您可能应该首先使用numpy/numa(而不是Pandas),因为优化的numpy/noma几乎总是比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]

这段代码实际上比矢量化代码更快。在一些具有100000行的快速测试中,上述方法比groupby方法快大约10倍。注意,速度的一个关键是numba,这是可选的。如果没有“@nb.jit”行,循环代码实际上比groupby方法慢大约10倍。

显然,这个示例非常简单,您可能更喜欢一行panda,而不是编写一个带有相关开销的循环。然而,对于这个问题,有更复杂的版本,NumPy/numa循环方法的可读性或速度可能是有意义的。