学途智助
首页
分类
标签
关于网站
登录
eeettt123
2025-08-24
7
作者编辑
如何将 Pandas 循环提速 71,803 倍
作者:康康GISer 链接:https://zhuanlan.zhihu.com/p/1942317052770628183 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 遍历 Pandas DataFrame 的过程可能非常耗时。本文将介绍几种高效的替代方案。对于使用 Python 和 Pandas 进行数据分析的开发者而言,循环是一种常见的操作需求。然而,即便是处理小型 DataFrame,原生循环的效率也不尽人意,当数据规模增大时,其耗时之长更是难以接受。在一次等待一段代码运行半个多小时的经历之后,笔者开始探索更高效的替代方法。本文记录和分享这些发现。标准循环DataFrame 是 Pandas 的核心数据结构,由行和列构成。标准循环会逐行遍历整个对象,但这种方式无法利用 Pandas 底层的任何优化函数,因此执行效率极低。在我们的示例中,有一个包含 65 列和 1140 行的 DataFrame,记录了 2016-2019 赛季的足球比赛结果。我们的目标是创建一个新列,用以标记某支特定球队的比赛是否为平局。初始代码可能如下所示: def soc_loop(leaguedf,TEAM,): leaguedf['Draws'] = 99999 for row in range(0, len(leaguedf)): if ((leaguedf['HomeTeam'].iloc[row] == TEAM) & (leaguedf['FTR'].iloc[row] == 'D')) | \ ((leaguedf['AwayTeam'].iloc[row] == TEAM) & (leaguedf['FTR'].iloc[row] == 'D')): leaguedf['Draws'].iloc[row] = 'Draw' elif ((leaguedf['HomeTeam'].iloc[row] == TEAM) & (leaguedf['FTR'].iloc[row] != 'D')) | \ ((leaguedf['AwayTeam'].iloc[row] == TEAM) & (leaguedf['FTR'].iloc[row] != 'D')): leaguedf['Draws'].iloc[row] = 'No_Draw' else: leaguedf['Draws'].iloc[row] = 'No_Game'由于数据包含了英超联赛的所有比赛,我们需要检查目标球队(阿森纳)是否参赛,并判断其是主队还是客队。可以看到,这个循环的效率非常低下,执行耗时长达 20.7 秒。接下来,我们探讨如何提升其性能。Pandas 内置函数 iterrows() — 提速 321 倍在第一个示例中,我们遍历了整个 DataFrame。iterrows() 方法则将 DataFrame 的每一行作为一个(索引,Series)对返回。通过这种方式迭代,其性能优于标准循环: def soc_iter(TEAM,home,away,ftr): #team, row['HomeTeam'], row['AwayTeam'], row['FTR'] if ((home == TEAM) & (ftr == 'D')) | ((away == TEAM) & (ftr == 'D')): result = 'Draw' elif ((home == TEAM) & (ftr != 'D')) | ((away == TEAM) & (ftr != 'D')): result = 'No_Draw' else: result = 'No_Game' return result这段代码的运行时间缩短至 68 毫秒,相较于标准循环提速了 321 倍。然而,iterrows() 仍非最优选择。其主要缺陷在于无法保证数据类型(dtype)在迭代过程中的一致性,可能引发潜在的错误。因此,许多专家并不推荐使用它。若要保证数据类型,itertuples() 是一个更好的选择。鉴于本文聚焦于性能,在此不作展开,感兴趣的读者可以查阅官方文档。pandas.DataFrame.itertuples — pandas documentationpandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.itertuples.htmlapply() 方法 — 提速 811 倍严格来说,apply 方法本身并非高速。其性能高度依赖于内部执行的函数。当其内部操作能够被 Cython 优化时(如此处的场景),apply 的执行速度会得到极大提升。我们可以将 apply 与 lambda 函数结合使用,并通过 axis=1 参数指定按行操作:此方法的执行时间进一步缩短至 27 毫秒,性能再次超越了之前的方法。Pandas 向量化 — 提速 9280 倍接下来,我们进入一个全新的主题:向量化(Vectorization)。向量化的核心思想是避免使用 Python 层的显式循环,转而依赖于 Pandas 和 Numpy 底层高度优化的 C 语言代码来执行批量操作[1],从而实现内存使用和计算效率的最大化。我们只需对函数稍作修改: def soc_iter(TEAM,home,away,ftr): df['Draws'] = 'No_Game' df.loc[((home == TEAM) & (ftr == 'D')) | ((away == TEAM) & (ftr == 'D')), 'Draws'] = 'Draw' df.loc[((home == TEAM) & (ftr != 'D')) | ((away == TEAM) & (ftr != 'D')), 'Draws'] = 'No_Draw'现在,我们可以直接将 Pandas Series 作为输入参数来创建新列:在这个场景下,我们甚至完全摆脱了循环。通过调整函数使其能够处理 Series 对象,可以直接利用 Pandas 的批量处理能力,带来巨大的性能飞跃。Numpy 向量化 — 提速 71,803 倍在上一个示例中,我们传入的是 Pandas Series。如果我们通过 .values 属性将其转换为 Numpy 数组,性能将得到进一步的极致提升:Numpy 数组的高性能得益于其“引用局部性”(locality of reference)[2] 的特性,这使得 CPU 缓存能够被高效利用。最终,我们的代码仅需 0.305 毫秒即可完成,相比最初的标准循环,性能提升了惊人的 71,803 倍。结论在 Python、Pandas 和 Numpy 的数据分析生态中,代码性能优化是一个永恒的话题。本文通过一个实例对比了五种不同的 DataFrame 操作方法,并展示了它们在速度上的天壤之别。本文的核心结论可以归纳为以下两条准则:如果你评估后确认必须使用循环,那么 apply 方法通常是最佳选择。在任何可能的情况下,都应优先考虑向量化操作,因为它能带来指数级的性能提升。参考文献:
Python
赞
博客信息
作者
eeettt123
发布日期
2025-08-24
其他信息 : 其他三字母的人名首字母都是其他同学发布的哦