优化Pandas DataFrame apply 函数的性能:利用向量化操作

碧海醫心
发布: 2025-11-05 14:28:01
原创
312人浏览过

优化pandas dataframe apply 函数的性能:利用向量化操作

在处理大型Pandas DataFrame时,`apply` 函数尤其是在结合自定义Python函数使用时,可能成为性能瓶颈。本文将深入探讨 `apply` 函数效率低下的原因,并提供一种更高效的替代方案:利用Pandas和NumPy的向量化(或广播)能力,显著提升数据处理速度,从而避免耗时的逐行操作,实现更快的计算。

理解 apply 函数的性能瓶颈

Pandas的 DataFrame.apply() 方法在处理自定义函数时,通常会逐行或逐列迭代数据。当您将一个Python对象的方法或一个普通的Python函数应用到DataFrame的某个Series上时,Pandas需要执行以下操作:

  1. 数据类型转换: 将底层的NumPy数组值转换为标准的Python对象,以便Python函数可以处理。
  2. 函数调用开销: 对Series中的每个元素独立调用Python函数。每次函数调用都伴随着一定的开销。
  3. 结果转换: 将Python函数返回的结果再次转换回Pandas/NumPy兼容的类型。

这些重复的类型转换和函数调用,对于拥有数百万行的大型DataFrame而言,会积累成巨大的性能开销,导致脚本执行时间过长。Pandas的设计哲学之一是利用底层的C/Fortran优化代码(通过NumPy),以实现对整个数据集的批量操作,而 apply 在这种情况下打破了这一优势。

向量化操作:Pandas的加速秘诀

Pandas和NumPy的核心优势在于其向量化(vectorization)能力。这意味着许多操作(如加、减、乘、除、比较等)可以直接应用于整个Series或DataFrame,而无需显式地循环遍历每个元素。这些向量化操作在底层由高度优化的C或Fortran代码执行,因此比纯Python循环快得多。

当一个自定义函数可以被重写为接受整个Series作为输入,并返回一个Series作为输出时,我们就可以利用这种向量化能力。

示例:加速自定义函数应用

让我们通过一个具体的例子来演示 apply 和向量化操作之间的性能差异。假设我们有一个包含整数的DataFrame,并且有一个自定义类 MyObj,其 move 方法根据一个值和一个偏移量进行计算。

硅基智能
硅基智能

基于Web3.0的元宇宙,去中心化的互联网,高质量、沉浸式元宇宙直播平台,用数字化重新定义直播

硅基智能 62
查看详情 硅基智能
import pandas as pd
import numpy as np
from timeit import timeit

# 创建一个大型DataFrame
df = pd.DataFrame({"col": np.arange(1000000)}) # 增加到100万行以更明显地展示性能差异

class MyObj:

    def __init__(self, position):
        self.pos = position

    def move(self, value, offset):
        """
        一个简单的数值计算方法
        这个方法既可以接受单个数值,也可以接受一个NumPy数组/Pandas Series
        """
        return value * self.pos + offset

# 实例化MyObj
my_obj = MyObj(1)

print("--- 性能对比(1000次迭代)---")

# 方法1: 使用 apply 函数
apply_time = timeit('df["col"].apply(my_obj.move, args=(1,))', 
                    globals=globals(), number=10) # 减少迭代次数,因为apply可能非常慢
print(f"apply 方法耗时: {apply_time:.4f} 秒")

# 方法2: 使用向量化(广播)操作
broadcast_time = timeit('my_obj.move(df["col"], 1)', 
                        globals=globals(), number=1000)
print(f"向量化方法耗时: {broadcast_time:.4f} 秒")

# 验证结果是否一致
result_apply = df["col"].apply(my_obj.move, args=(1,))
result_broadcast = my_obj.move(df["col"], 1)
print(f"结果是否一致: {np.all(result_apply == result_broadcast)}")
登录后复制

示例输出(具体数值可能因机器性能而异):

--- 性能对比(1000次迭代)---
apply 方法耗时: 4.5678 秒
向量化方法耗时: 0.0123 秒
结果是否一致: True
登录后复制

从输出中可以清楚地看到,向量化(广播)方法的执行速度比 apply 方法快了几个数量级。这是因为 my_obj.move 方法内部的数学运算 (* 和 +) 能够直接作用于整个Pandas Series (df["col"]),而无需Python的逐元素循环。

为什么向量化如此高效?

当 my_obj.move(df["col"], 1) 被调用时:

  1. df["col"] 是一个Pandas Series,其底层是一个NumPy数组。
  2. value * self.pos 操作实际上是NumPy数组与标量 self.pos 的乘法,这是一个高度优化的NumPy广播操作。
  3. + offset 也是NumPy数组与标量 offset 的加法,同样是高效的广播操作。

整个过程在底层以C语言速度执行,避免了Python解释器的开销。

注意事项与最佳实践

  1. 优先考虑向量化: 在处理Pandas DataFrame时,始终首先尝试将操作表达为向量化形式。这通常意味着使用Pandas和NumPy提供的内置函数或运算符。
  2. 重构自定义函数: 如果您的自定义函数可以接受整个Series或数组作为输入,并执行元素级操作,那么请重构它以支持这种模式。例如,如果函数只包含基本的数学运算,它很可能已经是向量化友好的。
  3. 复杂逻辑的替代方案:
    • numexpr: 对于复杂的字符串表达式,numexpr 库可以提供比Pandas更快的性能。
    • Numba: 对于无法直接向量化的复杂Python函数,Numba 可以通过JIT(即时编译)将Python代码编译成优化的机器码,从而显著加速执行。
    • Cython: 如果需要极致的性能且愿意编写C扩展,Cython 是一个强大的工具
    • iterrows() / itertuples(): 在极少数情况下,如果操作确实无法向量化,且需要访问行索引或多列数据,itertuples() 通常比 iterrows() 更快,因为它返回命名元组而不是Series对象,减少了开销。但这些迭代器应作为最后的手段。
  4. 避免在 apply 中使用复杂的Python对象方法: 如果方法内部执行了大量非数值或无法广播的Python逻辑,apply 的性能问题会更加突出。

总结

DataFrame.apply() 函数在处理大型数据集时,特别是结合自定义Python函数时,由于其逐元素迭代和类型转换的特性,效率低下。为了显著提升性能,我们应优先利用Pandas和NumPy的向量化(广播)能力,将操作重构为直接作用于整个Series或DataFrame。通过这种方式,我们可以充分发挥这些库底层优化代码的优势,实现更快速、更高效的数据处理。在无法直接向量化的情况下,可以考虑使用 Numba 等工具进行性能优化。

以上就是优化Pandas DataFrame apply 函数的性能:利用向量化操作的详细内容,更多请关注php中文网其它相关文章!

数码产品性能查询
数码产品性能查询

该软件包括了市面上所有手机CPU,手机跑分情况,电脑CPU,电脑产品信息等等,方便需要大家查阅数码产品最新情况,了解产品特性,能够进行对比选择最具性价比的商品。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号