【问题标题】:How to iterate over rows in a DataFrame in Pandas如何遍历 Pandas DataFrame 中的行
【发布时间】:2022-10-25 02:50:04
【问题描述】:

我有一个熊猫数据框,df

   c1   c2
0  10  100
1  11  110
2  12  120

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

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

我找到了一个 similar question,它建议使用以下任一方法:

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

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

【问题讨论】:

  • df.iteritems() 遍历列而不是行。因此,要使其遍历行,您必须转置(“T”),这意味着您将行和列相互更改(反映在对角线上)。因此,当您使用 df.T.iteritems() 时,您可以有效地在其行上迭代原始数据框
  • 与 cs95 所说的相反,想要遍历数据框是有充分理由的,所以新用户不应该感到气馁。一个示例是,如果您想使用每一行的值作为输入来执行一些代码。此外,如果您的数据框相当小(例如少于 1000 个项目),那么性能并不是真正的问题。
  • @cs95 在我看来,数据帧是 Python 中的首选表格格式。因此,无论何时你想读入一个 csv,或者你有一个要操作其值的字典列表,或者你想执行简单的连接、分组或窗口操作,你都可以使用数据框,即使你的数据相对较小。
  • @cs95 不,但这是对“完全使用 DataFrame”的回应。我的观点是,这就是为什么一个人可能将自己的数据放在数据框中的原因。如果你想,例如为每一行数据运行一个脚本,你必须遍历该数据框。
  • 我支持@oulenz。据我所知,pandas 是读取 csv 文件的首选,即使数据集很小。使用 API 操作数据更容易编程

标签: python pandas dataframe


【解决方案1】:

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

【讨论】:

  • 注意:“因为 iterrows 为每一行返回一个系列,所以它才不是保留跨行的数据类型。”另外,“你永远不应该修改你正在迭代的东西。”根据pandas 0.19.1 docs
  • @viddik13 这是一个很好的说明,谢谢。因此,我遇到了这样一种情况,其中像 431341610650 这样的数值读作 4.31E+11。有没有办法保留数据类型?
  • @AzizAlto 使用itertuples,如下所述。另见pandas.pydata.org/pandas-docs/stable/generated/…
  • 不要使用 iterrows。 Itertuples 更快并且保留数据类型。 More info
  • 来自the documentation:“遍历 pandas 对象通常很慢。在许多情况下,不需要手动遍历行[...]”。您的答案是正确的(在问题的上下文中)但没有在任何地方提及这一点,因此它不是一个很好的答案。
【解决方案2】:

如何遍历 Pandas DataFrame 中的行

答案:不要*

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

你想打印一个 DataFrame 吗?使用DataFrame.to_string()

你想计算一些东西吗?在这种情况下,按以下顺序搜索方法(列表修改自here):

  1. 矢量化
  2. Cython套路
  3. 列表理解(vanilla for loop)
  4. DataFrame.apply():i)可以在 Cython 中执行的归约,ii)Python 空间中的迭代
  5. DataFrame.itertuples()iteritems()
  6. DataFrame.iterrows()

    iterrowsitertuples(在回答这个问题时都获得了很多选票)应该在非常罕见的情况下使用,例如为顺序处理生成行对象/名称元组,这实际上是这些函数唯一有用的地方。

    诉诸权威

    The documentation page 在迭代中有一个巨大的红色警告框,上面写着:

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

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


    比循环更快:VectorizationCython

    pandas(通过 NumPy 或通过 Cythonized 函数)“矢量化”了大量基本操作和计算。这包括算术、比较、(大多数)归约、重塑(例如旋转)、连接和分组操作。查看Essential Basic Functionality 上的文档,找到适合您问题的矢量化方法。

    如果不存在,请随意使用自定义 Cython extensions 编写您自己的。


    下一个最好的事情:List Comprehensions*

    如果 1) 没有可用的矢量化解决方案,2) 性能很重要,但还不够重要,无法解决代码的 cythonizing 问题,并且 3) 您正在尝试执行元素转换在你的代码上。 good amount of evidence 表明列表理解对于许多常见的 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() 会将整个数组转换为字符串,这可能不是您想要的。幸运的是,zipping 你的列在一起是最直接的解决方法。

      *您的里程数可能因以下列出的原因而有所不同注意事项上面的部分。


      一个明显的例子

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

      Benchmarking code, for your reference。底部的一行测量了一个用 numpandas 编写的函数,numpandas 是一种与 NumPy 大量混合以发挥最大性能的 Pandas 风格。除非您知道自己在做什么,否则应避免编写 numpandas 代码。尽可能坚持 API(即,更喜欢 vec 而不是 vec_numpy)。

      然而,我应该提一下,它并不总是如此干脆。有时“什么是最佳操作方法”的答案是“这取决于您的数据”。我的建议是在选择一种方法之前先针对您的数据测试不同的方法。


      我的个人意见*

      对 iter 系列的各种替代方案进行的大多数分析都是通过性能的镜头进行的。但是,在大多数情况下,您通常会处理一个合理大小的数据集(不超过几千行或 10 万行),性能将仅次于解决方案的简单性/可读性。

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

      对于新手:

      矢量化(若有可能); apply();列表理解; itertuples()/iteritems()iterrows(); Cython

      对于更有经验的人:

      矢量化(若有可能); apply();列表理解;赛通; itertuples()/iteritems()iterrows()

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

      在我的很多帖子中,我确实倾向于继续谈论 apply 有多糟糕,但我承认对于初学者来说更容易理解它在做什么。此外,在 this post of mine 中解释了 apply 的许多用例。

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

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


      延伸阅读

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


      为什么我写这个答案

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

      这个答案的目的是帮助新用户理解迭代不一定是所有问题的解决方案,可能存在更好、更快和更惯用的解决方案,值得花时间去探索它们。我并不是要开始一场迭代与矢量化的战争,但我希望新用户在使用这个库开发问题解决方案时得到通知。

【讨论】:

  • 请注意,iterrowsitertuples 有重要的注意事项。有关详细信息,请参阅this answerpandas docs
  • 这是唯一专注于应该与熊猫一起使用的惯用技术的答案,使其成为该问题的最佳答案。学习获得正确的 回答 正确的 代码(而不是正确的 回答 错误的 代码- 即效率低下,无法扩展,太适合特定数据)是学习熊猫(和一般数据)的重要组成部分。
  • 不过,我认为你对 for 循环不公平,因为在我的测试中它们只比列表理解慢一点。诀窍是循环 zip(df['A'], df['B']) 而不是 df.iterrows()
  • 在 List Comprehensions 下,“迭代多列”示例需要注意:DataFrame.values 会将每一列转换为通用数据类型。 DataFrame.to_numpy() 也这样做。幸运的是,我们可以对任意数量的列使用zip
  • @Dean 我经常收到这种回复,老实说,这让我很困惑。这一切都是为了养成良好的习惯。 “我的数据很小,性能无关紧要,所以可以原谅我使用这种反模式”..?当有一天性能真的很重要时,您会感谢自己提前准备了正确的工具。
【解决方案3】:

首先考虑是否真的需要迭代在 DataFrame 中的行上。有关替代方案,请参阅this answer

如果您仍然需要遍历行,您可以使用下面的方法。注意一些重要警告其他任何答案中均未提及。

itertuples() 应该比 iterrows()

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

  • iterrows:dtype 行与行之间可能不匹配

因为 iterrows 为每一行返回一个 Series,所以它不保留跨行的数据类型(对于数据帧,数据类型跨列保留)。要在遍历行时保留数据类型,最好使用 itertuples() ,它返回值的命名元组,通常比 iterrows() 快得多

  • iterrows:不要修改行

你应该从不修改你正在迭代的东西。这不能保证在所有情况下都有效。根据数据类型,迭代器返回副本而不是视图,写入它不会有任何效果。

使用 DataFrame.apply() 代替:

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

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

有关详细信息,请参阅pandas docs on iteration

【讨论】:

  • 完成后这么长时间阅读此线程的人只是一个小问题: df.apply() 在效率方面与 itertuples 相比如何?
  • 注意:您也可以说类似for row in df[['c1','c2']].itertuples(index=True, name=None): 的内容,以便在行迭代器中只包含某些列。
  • 您可以只使用 row.c1 而不是 getattr(row, "c1")
  • 我大约 90% 确定,如果您使用 getattr(row, "c1") 而不是 row.c1,您将失去 itertuples 的任何性能优势,并且如果您实际上需要通过字符串访问该属性,则应该改用 iterrows。
  • 我偶然发现了这个问题,因为虽然我知道有拆分应用组合,但我仍然真的需要迭代在 DataFrame 上(如问题所述)。不是每个人都有机会通过numbacython 进行改进(同样的文档说“总是值得首先在 Python 中进行优化”)。我写这个答案是为了帮助其他人避免(有时令人沮丧)问题,因为其他答案都没有提到这些警告。误导任何人或说“这是正确的做法”从来都不是我的本意。我已经改进了答案。
【解决方案4】:

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

【讨论】:

  • 这比将 DataFrame 转换为 numpy 数组(通过 .values)并直接对数组进行操作更快吗?我有同样的问题,但最终转换为 numpy 数组然后使用 cython。
  • @vgoklani 如果逐行迭代效率低下并且您有一个非对象 numpy 数组,那么几乎可以肯定使用原始 numpy 数组会更快,尤其是对于具有许多行的数组。你应该避免遍历行,除非你绝对必须
  • 我对 df.iterrows()、df.itertuples() 和 zip(df['a'], df['b']) 的时间消耗做了一些测试,并将结果发布在另一个答案中问题:stackoverflow.com/a/34311080/2142098
【解决方案5】:

虽然 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

【讨论】:

  • 您的两个示例中的大部分时间差异似乎是由于您似乎对 .iterrows() 命令使用基于标签的索引,对 .itertuples() 命令使用基于整数的索引。
  • 对于基于财务数据的数据帧(时间戳和 4 倍浮点数),itertuples 在我的机器上比 iterrows 快 19.57 倍。只有for a,b,c in izip(df["a"],df["b"],df["c"]: 几乎同样快。
  • 你能解释为什么它更快吗?
  • @AbeMiessler iterrows()将每一行数据装箱到一个系列中,而itertuples()不会。
  • 请注意,列的顺序实际上是不确定的,因为 df 是从字典创建的,所以 row[1] 可以引用任何列。事实证明,整数列与浮点列的时间大致相同。
【解决方案6】:

您可以使用 df.iloc 函数,如下所示:

for i in range(0, len(df)):
    print(df.iloc[i]['c1'], df.iloc[i]['c2'])

【讨论】:

  • 我知道应该避免使用 iterrows 或 itertuples,但知道为什么会很有趣。有什么想法吗?
  • 如果您想保留数据类型并按名称引用列,这是我所知道的唯一有效技术。 itertuples 保留数据类型,但去掉任何它不喜欢的名称。 iterrows 做相反的事情。
  • 花了几个小时试图通过 pandas 数据结构的特性来做一些简单而富有表现力的事情。这导致可读代码。
  • 虽然 for i in range(df.shape[0]) 可能会稍微加快这种方法的速度,但它仍然比我的应用程序上面的 iterrows() 方法慢 3.5 倍。
  • 在大型 Datafrmes 上,这似乎更好,因为 my_iter = df.itertuples() 需要双倍的内存和大量时间来复制它。 iterrows() 也一样。
【解决方案7】:

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

【讨论】:

  • df['price'] 是指数据框中的列名吗?我正在尝试创建一个字典,其中包含来自 csv 文件中多个列的唯一值。我使用您的逻辑创建了一个具有唯一键和值的字典,并收到一条错误消息TypeError: ("'Series' 对象是可变的,因此它们不能被散列",u'发生在索引 0')
  • 代码:df['Workclass'] = df.apply(lambda row: dic_update(row), axis=1)行结束编号 = 0行结束def dic_update(row):如果行不在 dic 中:dic[row] = id id = id + 1
  • 将轴默认为 0 是最糟糕的
  • 请注意,apply 不会“迭代”行,而是按行应用函数。如果你真的,上面的代码将不起作用需要迭代和索引,例如当比较不同行的值时(在这种情况下你只能迭代)。
  • 这是熊猫的合适答案
【解决方案8】:

如何高效迭代

如果你真的必须迭代 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()已经快很多了,但是它不适用于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

This article is a very interesting comparison between iterrows and itertuples

【讨论】:

  • 那么,为什么这些低效的方法首先在 Pandas 中可用 - 如果不应该使用 iterrows 和 itertuples 是“常识” - 那么为什么它们在那里,或者更确切地说,为什么这些方法没有更新并在熊猫维护者的背景?
  • @Monty,并非总是可以向量化所有操作。
【解决方案9】:

我在寻找如何迭代行 并在这里结束:

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

【讨论】:

【解决方案10】:

我们有多种选择来做同样的事情,很多人已经分享了他们的答案。

我发现以下两种方法简单有效:

  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() 更快

【讨论】:

    【解决方案11】:

    您可以编写自己的实现 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);
    

    【讨论】:

    • 对于不想阅读代码的人:蓝线是intertuples,橙线是通过 yield 块的迭代器列表。 interrows没有比较。
    【解决方案12】:

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

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

    【讨论】:

    • 这是链式索引。我不建议这样做。
    • @cs95 你会推荐什么?
    • 如果你想让它工作,调用 df.columns.get_loc 来获取日期列的整数索引位置(在循环之外),然后在内部使用单个 iloc 索引调用。
    【解决方案13】:
     for ind in df.index:
         print df['c1'][ind], df['c2'][ind]
    

    【讨论】:

    • 在大型数据帧(例如数百万行)上使用时,此选项的性能如何?
    • 老实说,我不太清楚,我认为与最佳答案相比,经过的时间将大致相同,因为两种情况都使用“for”-construction。但在某些情况下内存可能会有所不同。
    • 这是链式索引。不要使用这个!
    【解决方案14】:

    更新: cs95 已更新 his answer 以包含纯 numpy 向量化。你可以简单地参考他的回答。


    cs95 shows 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]
    

    【讨论】:

    【解决方案15】:

    有时有用的模式是:

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

    【讨论】:

      【解决方案16】:

      循环 dataframe 和中的所有行利用每行的值方便地namedtuples可以转换为ndarrays。例如:

      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索引被添加为元组的第一个元素,这对于某些应用程序可能是不可取的。

      【讨论】:

        【解决方案17】:

        简而言之

        • 尽可能使用矢量化
        • 如果无法向量化操作 - 使用列表理解
        • 如果您需要单个对象代表整行 - 使用 itertuples
        • 如果上面的速度太慢 - 试试更快捷的申请
        • 如果仍然太慢 - 尝试 Cython 例程

        基准

        【讨论】:

          【解决方案18】:

          有一种方法可以在返回 DataFrame 的同时遍历行,而不是 Series。我没有看到有人提到您可以将索引作为要作为 DataFrame 返回的行的列表传递:

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

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

          【讨论】:

          • 这对于在排序后获得数据框中的第 n 大行非常有帮助。谢谢!
          【解决方案19】:

          对于查看和修改值,我会使用iterrows()。在 for 循环中并通过使用元组解包(参见示例:i, row),我使用 row 仅查看值,并在我想修改值时使用 iloc 方法。如之前的答案所述,您不应在此处修改正在迭代的内容。

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

          这里循环中的 row 是该行的副本,而不是它的视图。因此,你不应该写类似row['A'] = 'New_Value' 的东西,它不会修改 DataFrame。但是,您可以使用 iloc 并指定 DataFrame 来完成这项工作。

          【讨论】:

            【解决方案20】:

            有很多方法可以迭代 Pandas 数据框中的行。一种非常简单直观的方法是:

            df = pd.DataFrame({'A':[1, 2, 3], 'B':[4, 5, 6], 'C':[7, 8, 9]})
            print(df)
            for i in range(df.shape[0]):
                # For printing the second column
                print(df.iloc[i, 1])
            
                # For printing more than one columns
                print(df.iloc[i, [0, 2]])
            

            【讨论】:

              【解决方案21】:

              最简单的方法,使用apply函数

              def print_row(row):
                 print row['c1'], row['c2']
              
              df.apply(lambda row: print_row(row), axis=1)
              

              【讨论】:

                【解决方案22】:

                正如这里的许多答案正确而明确地指出的那样,您通常不应该尝试在 Pandas 中循环,而应该编写矢量化代码。但问题仍然存在,如果你应该曾经在 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 行的快速测试中,上面的代码比上面的代码快 10 倍左右通过...分组方法。请注意,速度的一个关键是 numba,这是可选的。没有“@nb.jit”行,循环代码实际上比通过...分组方法。

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

                【讨论】:

                  【解决方案23】:

                  您还可以执行 NumPy 索引以获得更大的速度提升。它并不是真正的迭代,但对于某些应用程序来说比迭代要好得多。

                  subset = row['c1'][0:5]
                  all = row['c1'][:]
                  

                  您可能还想将其转换为数组。这些索引/选择应该已经像 NumPy 数组一样工作了,但我遇到了问题,需要转换

                  np.asarray(all)
                  imgs[:] = cv2.resize(imgs[:], (224,224) ) # Resize every image in an hdf5 file
                  

                  【讨论】:

                    【解决方案24】:

                    df.iterrows() 返回 tuple(a, b),其中 aindexbrow

                    【讨论】:

                      【解决方案25】:

                      可能是最优雅的解决方案(但肯定不是最有效的):

                      for row in df.values:
                          c2 = row[1]
                          print(row)
                          # ...
                      
                      for c1, c2 in df.values:
                          # ...
                      

                      注意:

                      • the documentation 明确建议改用.to_numpy()
                      • 生成的 NumPy 数组将具有适合所有列的数据类型,在最坏的情况下object
                      • good reasons首先不要使用循环

                      不过,我认为这个选项应该包括在这里,作为一个(应该认为)微不足道的问题的直接解决方案。

                      【讨论】:

                        【解决方案26】:

                        此示例使用 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])
                        

                        【讨论】:

                          【解决方案27】:

                          免责声明:虽然这里有很多推荐的答案不是使用迭代(循环)方法(我基本同意),我仍然认为它是适用于以下情况的合理方法:

                          使用来自 API 的数据扩展数据框

                          假设您有一个包含不完整用户数据的大型数据框。现在您必须使用附加列来扩展此数据,例如,用户的 agegender

                          这两个值都必须从后端 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')
                          

                          【讨论】:

                            【解决方案28】:

                            一些库(例如我使用的 Java 互操作库)需要一次连续传递值,例如,如果是流式数据。为了复制流式传输的性质,我一个一个地“流式传输”我的数据帧值,我在下面写了下面的内容,它有时会派上用场。

                            class DataFrameReader:
                              def __init__(self, df):
                                self._df = df
                                self._row = None
                                self._columns = df.columns.tolist()
                                self.reset()
                                self.row_index = 0
                            
                              def __getattr__(self, key):
                                return self.__getitem__(key)
                            
                              def read(self) -> bool:
                                self._row = next(self._iterator, None)
                                self.row_index += 1
                                return self._row is not None
                            
                              def columns(self):
                                return self._columns
                            
                              def reset(self) -> None:
                                self._iterator = self._df.itertuples()
                            
                              def get_index(self):
                                return self._row[0]
                            
                              def index(self):
                                return self._row[0]
                            
                              def to_dict(self, columns: List[str] = None):
                                return self.row(columns=columns)
                            
                              def tolist(self, cols) -> List[object]:
                                return [self.__getitem__(c) for c in cols]
                            
                              def row(self, columns: List[str] = None) -> Dict[str, object]:
                                cols = set(self._columns if columns is None else columns)
                                return {c : self.__getitem__(c) for c in self._columns if c in cols}
                            
                              def __getitem__(self, key) -> object:
                                # the df index of the row is at index 0
                                try:
                                    if type(key) is list:
                                        ix = [self._columns.index(key) + 1 for k in key]
                                    else:
                                        ix = self._columns.index(key) + 1
                                    return self._row[ix]
                                except BaseException as e:
                                    return None
                            
                              def __next__(self) -> 'DataFrameReader':
                                if self.read():
                                    return self
                                else:
                                    raise StopIteration
                            
                              def __iter__(self) -> 'DataFrameReader':
                                return self
                            

                            哪个可以使用:

                            for row in DataFrameReader(df):
                              print(row.my_column_name)
                              print(row.to_dict())
                              print(row['my_column_name'])
                              print(row.tolist())
                            

                            并为正在迭代的行保留值/名称映射。显然,这比使用上面指出的 apply 和 Cython 慢很多,但在某些情况下是必要的。

                            【讨论】:

                              【解决方案29】:

                              正如 the accepted answer 所述,在行上应用函数的最快方法是使用向量化函数,即所谓的 NumPy ufuncs(通用函数)。

                              但是,当您要应用的函数尚未在 NumPy 中实现时,您应该怎么做?

                              那么,使用 numba 中的 vectorize 装饰器,您可以像这样直接在 Python 中轻松创建 ufunc:

                              from numba import vectorize, float64
                              
                              @vectorize([float64(float64)])
                              def f(x):
                                  #x is your line, do something with it, and return a float
                              

                              此功能的文档在这里:Creating NumPy universal functions

                              【讨论】:

                                【解决方案30】:

                                除了这篇文章中的出色答案外,我还将提出建议分而治之方法,我不是写这个答案来废除其他伟大的答案,而是用另一种对我有效的方法来实现它们。它有两个步骤 splittingmerging pandas 数据框:

                                分而治之的优点:

                                • 您不需要使用矢量化或任何其他方法将数据帧的类型转换为另一种类型
                                • 您不需要对您的代码进行 Cythonize,这通常会花费您额外的时间
                                • 在我的案例中,iterrows()itertuples() 在整个数据帧上都具有相同的性能
                                • 根据您选择的切片index,您将能够以指数方式加快迭代速度。 index 越高,迭代过程越快。

                                分而治之的缺点:

                                • 您不应该依赖于相同数据帧和不同数据帧的迭代过程.意思是如果你想从其他人那里读或写,这可能很难做到。

                                ===================分而治之的方法=================

                                第 1 步:拆分/切片

                                在这一步中,我们将在整个数据帧上划分迭代。假设您要将 CSV 文件读入 pandas df,然后对其进行迭代。在可能的情况下,我有 5,000,000 条记录,我将把它分成 100,000 条记录。

                                笔记:我需要重申,正如本页其他解决方案中解释的其他运行时分析一样,“记录数”在 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')
                                

                                参考:

                                Efficient way of iteration over datafreame

                                Concatenate CSV files into one Pandas Dataframe

                                【讨论】:

                                  猜你喜欢
                                  • 2012-05-30
                                  • 2019-07-19
                                  • 2018-12-28
                                  相关资源
                                  最近更新 更多