深入浅出 Pandas apply():从入门到向量化思维

在 Pandas 的数据处理中,apply() 函数是一个出镜率极高的工具。它像一座桥梁,将我们自定义的逻辑与数据结构无缝连接起来。无论是简单的元素变换,还是复杂的跨列运算,apply() 都能以清晰的语法完成。

然而,很多人在使用 apply() 时,往往只知其然而不知其所以然------为什么有时候需要 axis=1?为什么直接传入 Series 会报错?apply() 和向量化操作到底该如何选择?

今天,我们从头梳理 apply() 的使用方法,并深入探讨它背后的设计思想,帮助你从"会用"进阶到"精通"。


一、Series 上的 apply:逐元素处理的利器

Series 是 Pandas 的一维数据结构,可以看作是带标签的数组。当我们在 Series 上调用 apply() 时,传入的函数会被应用到 Series 的每一个元素上。

1.1 基本用法

定义一个简单的函数,将每个元素乘以 20:

复制代码
import pandas as pd

def multiply_by_20(x):
    return x * 20

s = pd.Series([10, 20, 30])
result = s.apply(multiply_by_20)
print(result)

输出:

复制代码
0    200
1    400
2    600
dtype: int64

这里的 multiply_by_20 依次接收了 10、20、30,并返回计算结果。整个过程类似于 Python 内置的 map() 函数,但 apply() 会保留索引并返回 Series。

如果逻辑简单,我们更常用 lambda 表达式:

复制代码
s.apply(lambda x: x * 20)

1.2 传递额外参数

有时函数需要额外的参数,apply() 允许我们通过关键字参数将其传递进去:

复制代码
def multiply(x, factor):
    return x * factor

s.apply(multiply, factor=3)

输出:

复制代码
0    30
1    60
2    90
dtype: int64

这里的关键是:apply() 会将自己没有匹配上的参数(如 factor=3)在调用函数时作为实参传递。这种设计使得函数复用变得更加方便。


二、DataFrame 上的 apply:按行或按列处理

DataFrame 是二维表格,apply() 在 DataFrame 上的行为与 Series 不同:它不再处理单个元素,而是处理一个完整的 Series(即一行或一列)。这个行为由 axis 参数控制。

2.1 axis=0:按列处理

axis=0 表示沿着行方向移动,即对每一列进行操作。这是 apply() 的默认值。

复制代码
df = pd.DataFrame({"a": [10, 20, 30], "b": [40, 50, 60]})

def col_sum(s):
    return s.sum()

print(df.apply(col_sum))

输出:

复制代码
a    60
b    150
dtype: int64

在这里,函数 col_sum 被依次传入了列 "a" 和列 "b" 的 Series,分别计算它们的和。注意,传入函数的 s 是一个 Series,包含了该列的所有值。

2.2 axis=1:按行处理

当设置 axis=1 时,apply() 会按行处理,函数接收的是当前行的数据(也是一个 Series)。

复制代码
def row_sum(s):
    return s.sum()

print(df.apply(row_sum, axis=1))

输出:

复制代码
0    50
1    70
2    90
dtype: int64

此时,s 代表一行,包含该行所有列的值。我们可以通过列名访问特定列的数据。

2.3 常见误区:为什么有时必须用 axis=1?

一个典型的场景是:我们需要基于当前行的多个列计算一个新值。比如,计算 a 列除以 b 列的结果。

如果使用 axis=0(默认),函数只能看到一列的数据,无法同时获取 a 和 b 两列。因此,必须指定 axis=1,让函数按行处理:

复制代码
def divide(s):
    return s["a"] / s["b"]

print(df.apply(divide, axis=1))

输出:

复制代码
0    0.25
1    0.40
2    0.50
dtype: float64

这个例子很好地说明了 axis 参数的本质:它决定了函数接收的 Series 是列还是行。理解这一点,才能避免在使用 apply() 时犯方向错误。


三、向量化函数:当 apply() 遇到性能瓶颈

虽然 apply() 非常灵活,但它的本质是 Python 级别的循环。当数据量较大时,这种逐行或逐列的处理方式会显得力不从心。Pandas 真正的性能优势来自于向量化运算------利用底层 C 语言实现的数组操作,能够极大地提升速度。

3.1 直接向量化的困境

假设我们需要实现一个安全的除法:如果分母为 0,则返回 NaN;否则返回正常结果。最自然的写法可能是这样的:

复制代码
import numpy as np

def safe_divide(x, y):
    if y == 0:
        return np.nan
    return x / y

df = pd.DataFrame({"a": [10, 20, 30], "b": [40, 0, 60]})

# 直接传入 Series 会报错
safe_divide(df["a"], df["b"])

运行这段代码会抛出 ValueError,因为 if y == 0 语句试图用一个向量(Series)与标量 0 进行比较,这在 Python 中是不允许的。

3.2 使用 np.vectorize() 将函数向量化

numpy 提供了 vectorize() 函数,它可以将一个普通的 Python 函数包装成能够接受数组或 Series 作为输入的向量化函数。包装后的函数会逐元素地应用原始逻辑,并返回一个 NumPy 数组。

方式一:显式调用

复制代码
def safe_divide(x, y):
    if y == 0:
        return np.nan
    return x / y

safe_divide_vec = np.vectorize(safe_divide)
result = safe_divide_vec(df["a"], df["b"])
print(result)

输出:

复制代码
[0.25 nan 0.5 ]

方式二:使用装饰器

如果你希望函数本身就可以直接接收向量输入,可以使用 @np.vectorize 装饰器:

复制代码
@np.vectorize
def safe_divide(x, y):
    if y == 0:
        return np.nan
    return x / y

print(safe_divide(df["a"], df["b"]))

结果与上面完全相同。

3.3 向量化函数的本质与取舍

需要说明的是,np.vectorize() 本质上并没有将函数编译成真正的向量化操作,它只是在 Python 层面进行循环,然后打包成数组输出。它的优势在于:

  • 语法简洁:函数定义与普通函数无异,调用时直接传入 Series 即可。
  • 可读性强:特别适合包含多个分支条件的复杂逻辑。
  • 自动广播:能够处理不同形状的数组输入。

对于追求极致性能的场景,可以考虑使用 numba 或 Cython,但对于绝大多数数据分析任务,np.vectorize() 已经足够高效且易于维护。


四、总结:如何在实际工作中选择

apply() 和向量化函数各有千秋,选择哪一种取决于具体场景:

  1. 当逻辑简单且可以向量化时,优先使用 Pandas 内置的向量化操作(如 df["a"] / df["b"]),这是性能最好的方式。
  2. 当逻辑复杂且难以向量化,但数据量不大时,apply() 是最直接的选择。它代码清晰,易于调试。
  3. 当逻辑复杂且数据量较大时,可以考虑使用 np.vectorize() 包装函数,兼顾可读性和性能。
  4. 当需要跨行或跨列引用多个值时,务必根据需求正确设置 axis 参数。axis=0 处理列,axis=1 处理行,这是避免错误的根本。

最后,我想强调一点:不要盲目追求性能而牺牲代码的可读性。在数据分析的日常工作中,代码的清晰度和可维护性往往比微小的性能提升更重要。apply() 之所以经久不衰,正是因为它让复杂的逻辑变得直观易懂。当你遇到无法直接向量化的场景时,大胆使用 apply();当性能成为瓶颈时,再考虑用 np.vectorize() 或重构为向量化操作。

相关推荐
Fleshy数模1 小时前
基于OpenCV实现人脸与微笑检测:从入门到实战
人工智能·opencv·计算机视觉
我材不敲代码2 小时前
OpenCV 实战——Python 实现图片人脸检测 + 视频人脸微笑检测
人工智能·python·opencv
2501_948114242 小时前
OpenClaw数据采集实战:用星链4SAPI给AI采集装上“稳定引擎”
服务器·人工智能·ai·openclaw
GOWIN革文品牌咨询2 小时前
国际B2B品牌的“价值压缩”怎么做:不是写一句话,而是搭一套定位推导模型
大数据·人工智能
爱学习的程序媛2 小时前
在线客服系统技术全解析:架构、交互与数据格式
人工智能·架构·系统架构·智能客服·在线客服
实在智能RPA2 小时前
Agent上线后有专人运营支持吗?深度解析AI Agent的全生命周期运维保障体系
运维·人工智能·ai
韦东东2 小时前
RAGFlow v0.19图文混排:详细拆解+预处理增强案例
人工智能·大模型·agent·ragflow·图文混排
七夜zippoe2 小时前
模型部署优化:ONNX与TensorRT实战——从训练到推理的完整优化链路
人工智能·python·tensorflow·tensorrt·onnx
AIArchivist2 小时前
AI医院智联中枢:重构医疗生态的超级大脑,从共识到落地的全维度解析
人工智能·重构