如何在 Pandas 中遍历 DataFrame 中的行?
回答:不要*!
Pandas 中的迭代是一种反模式,只有在用尽所有其他选项时才应该这样做。您不应该使用名称中包含“iter”的任何函数超过几千行,否则您将不得不习惯大量等待。
你想打印一个 DataFrame 吗?使用DataFrame.to_string()。
你想计算一些东西吗?在这种情况下,按此顺序搜索方法(列表修改自here):
- 矢量化
-
Cython套路
- 列表理解(vanilla
for 循环)
-
DataFrame.apply():i) 可以在 Cython 中执行的缩减,ii) Python 空间中的迭代
-
DataFrame.itertuples() 和 iteritems()
DataFrame.iterrows()
iterrows 和 itertuples(在这个问题的答案中都获得了很多投票)应该在非常罕见的情况下使用,例如为顺序处理生成行对象/名称元组,这确实是这些函数唯一有用的地方为。
向当局申诉
The documentation page 在迭代中有一个巨大的红色警告框,上面写着:
遍历 pandas 对象通常很慢。在许多情况下,不需要手动迭代行 [...]。
* 它实际上比“不要”要复杂一些。 df.iterrows() 是这个问题的正确答案,但“矢量化你的操作”是更好的答案。我承认在某些情况下无法避免迭代(例如,某些操作的结果取决于为前一行计算的值)。但是,需要对库有一定的了解才能知道何时。如果您不确定是否需要迭代解决方案,您可能不需要。 PS:要了解更多关于我写这个答案的理由,请跳到最底部。
大量的基本操作和计算由 pandas“矢量化”(通过 NumPy 或通过 Cythonized 函数)。这包括算术、比较、(大多数)归约、重塑(例如旋转)、连接和 groupby 操作。查看Essential Basic Functionality 上的文档,为您的问题找到合适的矢量化方法。
如果不存在,请随意使用自定义Cython extensions 编写自己的。
如果 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,但这并不总是得到保证。
- 第一个更明显,但是在处理 NaN 时,如果存在内置的 pandas 方法(因为它们具有更好的极端情况处理逻辑),则更喜欢它们,或者确保您的业务逻辑包含适当的 NaN 处理逻辑。
- 在处理混合数据类型时,您应该迭代
zip(df['A'], df['B'], ...) 而不是 df[['A', 'B']].to_numpy(),因为后者隐式地将数据向上转换为最常见的类型。例如,如果 A 是数字,B 是字符串,to_numpy() 会将整个数组转换为字符串,这可能不是您想要的。幸运的是,zipping 你的列是最直接的解决方法。
*您的里程可能会因上述注意事项部分中所述的原因而有所不同。
一个明显的例子
让我们通过添加两个 pandas 列 A + B 的简单示例来演示区别。这是一个可向量化的操作,因此很容易对比上述方法的性能。
Benchmarking code, for your reference。底部的行测量了一个用 numpandas 编写的函数,这是一种与 NumPy 大量混合以挤出最大性能的 Pandas 风格。除非您知道自己在做什么,否则应避免编写 numpandas 代码。尽可能坚持使用 API(即,更喜欢 vec 而不是 vec_numpy)。
不过,我应该提一下,它并不总是这么干脆利落的。有时,“什么是最佳操作方法”的答案是“这取决于您的数据”。我的建议是在确定一种方法之前先对您的数据测试不同的方法。
我的个人意见*
对 iter 系列的各种替代品进行的大多数分析都是从性能角度进行的。但是,在大多数情况下,您通常会处理大小合理的数据集(不超过几千或 100K 行),而性能将仅次于解决方案的简单性/可读性。
这是我在选择解决问题的方法时的个人偏好。
对于新手:
矢量化(如果可能); apply();列出理解; itertuples()/iteritems(); iterrows();赛通
对于更有经验的人:
矢量化(如果可能); apply();列出理解;赛通; itertuples()/iteritems(); iterrows()
对于任何可以向量化的问题,向量化是最惯用的方法。始终寻求矢量化!如有疑问,请查阅文档,或在 Stack Overflow 上查看有关您特定任务的现有问题。
我确实倾向于在我的很多帖子中继续谈论apply 的糟糕程度,但我承认初学者更容易理解它正在做什么。此外,apply 有很多用例,在this post of mine 中有说明。
Cython 在列表中排名较低,因为它需要更多的时间和精力才能正确完成。通常,您永远不需要使用 pandas 编写需要这种性能水平的代码,即使是列表解析也无法满足。
* 与任何个人意见一样,请多加注意!
进一步阅读
* Pandas 字符串方法是“矢量化的”,因为它们是在系列上指定的,但对每个元素都进行操作。底层机制仍然是迭代的,因为字符串操作本质上很难向量化。
我为什么写这个答案
我注意到新用户的一个常见趋势是提出“如何迭代我的 df 以执行 X?”形式的问题。显示在 for 循环内执行某些操作时调用 iterrows() 的代码。这就是为什么。一个没有被引入向量化概念的库的新用户可能会将解决他们问题的代码设想为迭代他们的数据来做某事。不知道如何迭代 DataFrame,他们做的第一件事就是用谷歌搜索它,然后在这个问题上结束。然后,他们看到接受的答案告诉他们如何去做,然后他们闭上眼睛运行这段代码,而不会首先质疑迭代是否是正确的做法。
此答案的目的是帮助新用户了解迭代不一定是所有问题的解决方案,并且可能存在更好、更快和更惯用的解决方案,值得花时间去探索它们。我并不是想发起一场迭代与矢量化的战争,但我希望新用户在使用这个库开发解决他们的问题的解决方案时得到通知。