如何加速DataFrame中自定义函数的apply运算?
加速Pandas百万行DataFrame列计算的方案
针对你用df["col"].apply(my_obj.move, args=(1,))处理百万行数据速度极慢的问题,以下是几个实用的加速方案:
1. 优先用向量化运算替代逐元素apply
apply本质是逐元素调用Python函数,百万级数据会产生巨大的函数调用开销。如果my_obj.move的逻辑是纯数值运算,直接改用Pandas/Numpy的向量化操作,速度能提升几十到上百倍。
操作步骤:
- 把
move方法的逻辑改造成支持数组输入的形式(直接基于Numpy数组运算) - 直接对整列数据进行运算
示例:
假设原来的move方法是:
class MyClass: def move(self, x, c): # 示例运算:x乘以2加常量c return x * 2 + c
改造后,新增一个支持向量化的方法:
class MyClass: def move(self, x, c): return x * 2 + c def move_vectorized(self, arr, c): # 直接对Numpy数组进行运算,无需循环 return arr * 2 + c
然后替换原代码:
# 先把列转成Numpy数组(可选,Pandas Series本身支持向量化) col_arr = df["col"].to_numpy() df["new_col"] = my_obj.move_vectorized(col_arr, 1)
如果逻辑简单,甚至可以直接写一行:
df["new_col"] = df["col"] * 2 + 1 # 等价于上面的move逻辑
2. 用Numba编译加速数值运算
如果move的逻辑复杂,无法直接用简单向量化实现,可以用Numba将函数编译为机器码,消除Python循环开销。
操作步骤:
- 把
move的逻辑提取为纯Python函数(Numba对类方法支持有限,优先用纯函数) - 用
numba.jit装饰器编译函数 - 直接对Numpy数组调用编译后的函数
示例:
from numba import jit # 提取move的逻辑为纯函数,用numba编译 @jit(nopython=True) # nopython模式下速度最快 def move_numba(x, c): # 这里写原move方法的数值运算逻辑,比如复杂的条件判断或循环 if x > 100: return x * 1.5 + c else: return x * 0.8 + c # 对整列的Numpy数组调用编译后的函数 df["new_col"] = move_numba(df["col"].to_numpy(), 1)
3. 并行处理(适合无法向量化的复杂逻辑)
如果上述两种方法都无法适配你的move逻辑,可以用并行apply来分摊计算时间。推荐用swifter库(自动选择最优执行方式),或者手动用多进程。
用swifter的示例:
import swifter # swifter会自动判断是用向量化还是并行apply,无需手动调整 df["new_col"] = df["col"].swifter.apply(my_obj.move, args=(1,))
手动多进程示例(无需额外库):
from multiprocessing import Pool import numpy as np # 把数据拆分成多个块 num_chunks = 4 # 根据CPU核心数调整 col_chunks = np.array_split(df["col"].to_numpy(), num_chunks) # 定义并行处理的函数 def process_chunk(chunk): return [my_obj.move(x, 1) for x in chunk] # 启动进程池处理 with Pool(num_chunks) as pool: results = pool.map(process_chunk, col_chunks) # 合并结果并赋值 df["new_col"] = np.concatenate(results)
为什么原代码慢?
apply是逐元素调用Python函数,每个元素的处理都要经历Python函数调用的开销(比如参数传递、栈帧创建等)。百万级数据下,这些微小的开销会被无限放大,导致运行时间急剧增加。而向量化/Numba/并行处理都是从消除或分摊这部分开销入手,从而大幅提升速度。
内容的提问来源于stack exchange,提问作者roulette01
相关产品推荐
相关产品推荐

