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