for
循环真的很“糟糕”吗?如果不是,在什么情况下它们会比使用更传统的“矢量化”方法更好?1
我熟悉“矢量化”的概念,以及pandas如何使用矢量化技术来加速计算。矢量化函数在整个系列或DataFrame上广播操作,以实现比传统的数据迭代更大的加速。
然而,我很惊讶地看到很多代码(包括来自Stack Overflow的答案)提供了解决问题的方法,这些问题涉及使用for
循环和列表解析来循环数据。文档和API都说循环是“坏”的,并且应该“永远不要”遍历数组、序列或 Dataframe 。那么,为什么我有时会看到用户建议基于循环的解决方案?
虽然这个问题听起来有点宽泛,但事实是,在一些非常特殊的情况下,for
循环通常比传统的数据迭代更好。这篇文章旨在为后代捕捉这一点。
3条答案
按热度按时间6l7fqoea1#
TLDR;不,
for
循环并不完全“坏”,至少并不总是如此。更准确地说,某些向量化操作比迭代慢**,而不是说迭代比某些向量化操作快。知道何时以及为什么是从代码中获得最大性能的关键。简而言之,在这些情况下,值得考虑矢量化pandas函数的替代方案:1.当您的数据很小时(...取决于您正在做什么),
1.当处理
object
/混合数据类型时1.使用
str
/regex访问器函数时让我们分别研究这些情况。
小数据迭代矢量化
Pandas在其API设计中遵循"Convention Over Configuration"方法。这意味着相同的API已被安装以满足广泛的数据和用例。
当pandas函数被调用时,以下事情(以及其他事情)必须由函数内部处理,以确保工作正常
1.索引/轴对齐
1.处理混合数据包
1.处理缺失数据
几乎每个函数都必须在不同程度上处理这些问题,这会带来开销。数值函数的开销较小(例如
Series.add
),而字符串函数的开销较大(例如Series.str.replace
)。另一方面,
for
循环比你想象的要快。更好的是列表解析(通过for
循环创建列表)甚至更快,因为它们是列表创建的优化迭代机制。列表解析遵循以下模式
其中
seq
是pandas系列或DataFrame列。或者,当在多个列上操作时,其中
seq1
和seq2
是列。数值比较
考虑一个简单的布尔索引操作。列表解析方法已经针对
Series.ne
(!=
)和query
进行了计时。以下是函数:为了简单起见,我使用
perfplot
包来运行本文中的所有timeit测试。上述操作的时间安排如下:对于中等大小的N,列表解析的性能优于
query
,甚至优于向量化的不等于比较。不幸的是,列表解析是线性扩展的,所以它不能为更大的N提供太多的性能增益。注意
值得一提的是,列表解析的大部分好处来自于不必担心索引对齐,但这意味着如果你的代码依赖于索引对齐,这将中断。在某些情况下,对底层NumPy数组的向量化操作可以被认为是“两全其美”,允许向量化 * 而不需要 * pandas函数的所有不必要的开销。这意味着您可以将上面的操作重写为
它的性能优于pandas和列表解析等价物:
NumPy矢量化超出了本文的范围,但如果性能很重要,它绝对值得考虑。
价值计数
再举一个例子--这一次,使用了另一个比for循环更快的vanilla python结构--
collections.Counter
。一个常见的要求是计算值计数并将结果作为字典返回。这是用value_counts
、np.unique
和Counter
完成的:结果更明显,
Counter
在更大范围的小N(~3500)上胜过两种矢量化方法。注意
更多琐事(礼貌@user2357112)。
Counter
是用C加速器实现的,所以虽然它仍然必须使用python对象而不是底层的C数据库,但它仍然比for
循环快。巨蟒之力!当然,从这里得到的是,性能取决于您的数据和用例。这些例子的目的是说服你不要排除这些解决方案作为合法的选择。如果这些仍然不能给予你所需要的性能,那么还有cython和numba。让我们把这个测试加入到这个组合中。
Numba提供了将循环的python代码JIT编译为非常强大的矢量化代码。了解如何使numba工作涉及到学习曲线。
混合/
object
dtypes操作基于字符串的比较
重新回顾第一节中的过滤示例,如果要比较的列是字符串,会怎么样?考虑上面相同的3个函数,但是将输入DataFrame转换为字符串。
那么,什么改变了?这里需要注意的是,**string操作本质上很难向量化。**Pandas将字符串视为对象,所有对对象的操作都回到了一个缓慢、循环的实现中。
现在,因为这个循环的实现被上面提到的所有开销所包围,所以这些解决方案之间存在恒定的幅度差异,即使它们的规模相同。
当涉及到对可变/复杂对象的操作时,没有比较。列表理解优于所有涉及字典和列表的操作。
按键显示字典值
下面是从一列字典中提取值的两个操作的计时:
map
和列表理解。设置在附录中的“代码段”标题下。位置列表索引
从列列表中提取第0个元素的3个操作的计时(处理异常),
map
,str.get
accessor method和列表解析:注意
如果索引很重要,你会想做:
在重建系列的时候。
列表扁平化
最后一个例子是扁平列表。这是另一个常见的问题,并证明了python在这里是多么强大。
itertools.chain.from_iterable
和嵌套列表解析都是纯粹的python结构,并且比stack
解决方案更好地扩展。这些时间是一个强有力的迹象,表明pandas不具备与混合数据类型一起工作的能力,并且您可能应该避免使用它来这样做。只要有可能,数据应该以标量值(int/floats/string)的形式出现在单独的列中。
最后,这些解决方案的适用性在很大程度上取决于您的数据。因此,最好的做法是在决定使用什么之前,在数据上测试这些操作。请注意,我没有对这些解决方案中的
apply
进行计时,因为这会扭曲图形(是的,它就是那么慢)。正则表达式操作和
.str
访问方法Pandas可以对字符串列应用正则表达式操作,如
str.contains
、str.extract
和str.extractall
,以及其他“向量化”字符串操作(如str.split
、str.find
、str.translate
等)。这些函数比列表解析慢,并且比其他函数更方便。预编译正则表达式模式并使用
re.compile
对数据进行重新编译通常要快得多(另请参阅Is it worth using Python's re.compile?)。与str.contains
等效的list comp看起来像这样:或者
如果需要处理NaN,可以执行以下操作
与
str.extract
(不带组)等效的列表组件如下所示:如果你需要处理无匹配和NaN,你可以使用一个自定义函数(更快!):
matcher
功能非常可扩展。它可以根据需要返回每个捕获组的列表。只需提取查询matcher对象的group
或groups
属性。对于
str.extractall
,将p.search
更改为p.findall
。字符串提取
考虑一个简单的过滤操作。这个想法是提取4位数字,如果它前面是一个大写字母。
更多示例
充分披露-我是作者(部分或全部)这些职位列出如下。
总结
正如上面的例子所示,迭代在处理小行的 Dataframe 、混合 Dataframe 和正则表达式时非常有用。
您获得的加速取决于您的数据和问题,因此您的里程可能会有所不同。最好的办法是仔细地运行测试,看看付出的努力是否值得。
“矢量化”函数的优点在于其简单性和可读性,所以如果性能不是关键的,您肯定会更喜欢它们。
另一方面,某些字符串操作处理有利于使用NumPy的约束。下面是两个例子,仔细的NumPy向量化优于Python:
此外,有时仅通过
.values
在底层数组上操作,而不是在Series或DataFrames上操作,可以为大多数常见场景提供足够健康的加速(请参见上面数值比较部分的注意)。例如,df[df.A.values != df.B.values]
将显示出比df[df.A != df.B]
更快的性能提升。使用.values
可能不是在所有情况下都合适,但它是一个有用的技巧。如上所述,由您来决定这些解决方案是否值得实施。
附录:代码片段
<!- ->
<!- ->
<!- ->
<!- ->
<!- ->
<!- ->
<!- _>
20jt8wwn2#
总之
iterrows
非常慢。开销在~ 1 k行上并不显著,但在10 k+行上很明显。itertuples
比iterrows
或apply
快得多。itertuples
快得多基准x1c 0d1x
ycl3bljg3#
pandas中的for循环真的很糟糕吗?
是的,是的,它们是(至少对于大量行的大型计算)。看看这个:
我测试的13种技术中最慢的 4 都使用
for
循环。它们比矢量化方法慢300 x ~ 1400 x!太疯狂了在这里看到我的完整答案:How to iterate over Pandas
DataFrame
s without iterating。我什么时候该在乎?
1.如果你迭代了很多行。
1.如果你厌倦了等待你的计算完成。
1.如果你想让Python使用更少的电力。
同样,当计算超过200万行时,看看上面图中的极端差异。
那么,为什么我有时会看到用户建议使用基于循环的解决方案?
我怀疑他们可能只是不知道。制作上面的情节花了我几百个小时的学习。如果你看到了
for
循环,请引导人们使用my answer,他们可以参考我提供的替代技术。其中一些是相当复杂的,如布尔索引的纯向量化方法。而且,即使是列表理解也不是天生的直觉。他们只是需要有人来证明:1.糟糕的技术有多慢。
1.如何做好技术,有真实的代码和真实的例子。
我应该关心
for
循环是坏的吗?也许不会记住 functional,现有的代码总是比 perfect,不存在的代码好。对于小型数据集,运行时节省可能并不重要(例如:0.01秒对0.2秒)。
我发现如果我等待代码运行的时间超过1秒,我想知道为什么。
而对于200万行,30~135+秒是绝对无法承受的,特别是考虑到简单的列表理解解决方案是~1秒,而纯向量化方法是0.1秒。
参见
1.我的完整回答:How to iterate over Pandas
DataFrame
s without iterating