我有一个熊猫数据帧,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对象是什么,以及如何使用它。


当前回答

对于查看和修改值,我将使用iterrows()。在for循环中,通过使用元组解包(参见示例:i,row),我使用行仅查看值,并在需要修改值时使用i和loc方法。正如前面的回答所述,这里您不应该修改正在迭代的内容。

for i, row in df.iterrows():
    df_column_A = df.loc[i, 'A']
    if df_column_A == 'Old_Value':
        df_column_A = 'New_value'  

在这里,循环中的行是该行的副本,而不是它的视图。因此,您不应该编写类似于行['a']='New_Value'的内容,它不会修改DataFrame。但是,您可以使用i和loc并指定DataFrame来完成这项工作。

其他回答

免责声明:尽管这里有很多答案建议不要使用迭代(循环)方法(我基本同意),但我仍然认为这是一种适用于以下情况的合理方法:

使用API中的数据扩展数据帧

假设您有一个包含不完整用户数据的大型数据帧。现在,您必须使用其他列来扩展此数据,例如,用户的年龄和性别。

这两个值都必须从后端API获取。我假设API不提供“批处理”端点(一次接受多个用户ID)。否则,您应该只调用一次API。

网络请求的成本(等待时间)远远超过了数据帧的迭代。我们讨论的是数百毫秒的网络往返时间,相比之下,使用迭代的替代方法可以忽略不计的小增益。

每行一个昂贵的网络请求

所以在这种情况下,我绝对倾向于使用迭代方法。尽管网络请求很昂贵,但可以保证对数据帧中的每一行只触发一次。以下是使用DataFrame.iterrows的示例:

实例

for index, row in users_df.iterrows():
  user_id = row['user_id']

  # Trigger expensive network request once for each row
  response_dict = backend_api.get(f'/api/user-data/{user_id}')

  # Extend dataframe with multiple data from response
  users_df.at[index, 'age'] = response_dict.get('age')
  users_df.at[index, 'gender'] = response_dict.get('gender')

如何在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上迭代,他们做的第一件事就是谷歌搜索它,并在这里结束这个问题。然后,他们看到被接受的答案,告诉他们如何去做,然后闭上眼睛运行这段代码,而不必首先质疑迭代是否正确。

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

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

正如这里的许多答案正确指出的那样,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循环方法的可读性或速度可能是有意义的。

除了这篇文章中的好答案,我将提出“分而治之”的方法,我写这个答案并不是为了废除其他好答案,而是为了用另一种对我有效的方法来实现它们。它有两个步骤,即拆分和合并熊猫数据帧:

分裂与征服的证明:

您不需要使用矢量化或任何其他方法将数据帧的类型转换为另一种类型您不需要将代码循环化,这通常需要额外的时间在我的例子中,iterrows()和itertples()在整个数据帧上都具有相同的性能根据您对切片索引的选择,您将能够以指数方式加快迭代。索引越高,迭代过程越快。

分裂与征服的缺点:

您不应该依赖于同一数据帧和不同切片的迭代过程。这意味着,如果你想从其他切片读取或写入,那么可能很难做到这一点。

===============分而治之=================

步骤1:分割/切片

在这一步中,我们将在整个数据帧上划分迭代。假设你要将一个CSV文件读入panda df,然后对其进行迭代。在这种情况下,我有5000000条记录,我要将其拆分为100000条记录。

注意:我需要重申,正如本页其他解决方案中解释的其他运行时分析一样,在df上搜索时,“记录数”与“运行时”成指数比例。基于我的数据基准,以下是结果:

Number of records | Iteration rate [per second]
========================================
100,000           | 500
500,000           | 200
1,000,000         | 50
5,000,000         | 20

第2步:合并

这将是一个简单的步骤,只需将所有写入的CSV文件合并到一个数据帧中,然后将其写入一个更大的CSV文件。

以下是示例代码:

# Step 1 (Splitting/Slicing)
import pandas as pd
df_all = pd.read_csv('C:/KtV.csv')
df_index = 100000
df_len = len(df)
for i in range(df_len // df_index + 1):
    lower_bound = i * df_index
    higher_bound = min(lower_bound + df_index, df_len)
    # Splitting/slicing df (make sure to copy() otherwise it will be a view
    df = df_all[lower_bound:higher_bound].copy()
    '''
    Write your iteration over the sliced df here
    using iterrows() or intertuples() or ...
    '''
    # Writing into CSV files
    df.to_csv('C:/KtV_prep_' + str(i) + '.csv')



# Step 2 (Merging)
filename = 'C:/KtV_prep_'
df = (pd.read_csv(f) for f in [filename + str(i) + '.csv' for i in range(ktv_len // ktv_index + 1)])
df_prep_all = pd.concat(df)
df_prep_all.to_csv('C:/KtV_prep_all.csv')

参考:

数据流迭代的有效方法

将CSV文件连接到一个Pandas数据帧中

本例使用iloc隔离数据帧中的每个数字。

import pandas as pd

 a = [1, 2, 3, 4]
 b = [5, 6, 7, 8]

 mjr = pd.DataFrame({'a':a, 'b':b})

 size = mjr.shape

 for i in range(size[0]):
     for j in range(size[1]):
         print(mjr.iloc[i, j])