自学教程 学编程从这里开始

Pandas apply / map / applymap

承接《Pandas 时间类型与 dt 访问器》,本篇对比 map、apply、applymap 三者用法、适用范围、性能差异,区分 Series 与 DataFrame 场景,附带向量化优化方案,是业务自定义逻辑的常用工具,保持系列教程统一风格。
前置导入与示例数据

import pandas as pd
import numpy as np

df = pd.DataFrame({
    "name": ["张三", "李四", "王五", "赵六", "孙七"],
    "age": [22, 25, 30, 28, 35],
    "salary": [8000, 12000, 15000, 11000, 18000],
    "city": ["杭州", "上海", "杭州", "北京", "上海"]
})
print(df)

一、核心一句话区分

  • Series.map():Series 专用,值映射、一对一转换;适合字典映射、单值函数
  • Series.apply():Series 专用,比 map 更灵活,支持复杂逻辑、多返回值
  • DataFrame.apply():按行 / 按列批量运算(axis=0 列,axis=1 行),返回 Series/DataFrame
  • DataFrame.applymap():单元格级,遍历每一个元素(Pandas 2.1+ 推荐替换为 df.map())

⚠️ 重点:这几个都是循环封装,性能弱于向量化写法(str、dt、布尔索引),优先向量化,不得已再用它们。

二、Series.map()

最适合字典映射,一对一翻译,非常常用于编码转名称、状态翻译

# 字典映射
city_map = {
    "杭州": "HZ",
    "上海": "SH",
    "北京": "BJ"
}
df["city_code"] = df["city"].map(city_map)

# 传入函数
def get_level(sal):
    if sal >= 15000:
        return "高"
    elif sal >= 10000:
        return "中"
    else:
        return "低"
df["sal_level_map"] = df["salary"].map(get_level)

map 只能接收一个入参(当前元素);无法拿到同行其他字段。

三、Series.apply()

同样作用于 Series 的每个元素,相比 map:

  • 支持更复杂函数
  • 支持返回多个值,扩展成多列
def calc_tax(sal):
    tax = sal * 0.05
    net = sal - tax
    return tax, net

# 返回元组,再展开成多列
df[["tax", "net_salary"]] = df["salary"].apply(calc_tax).apply(pd.Series)

什么时候选 map / apply:简单字典映射优先 map;复杂函数、多返回值用 apply。

四、DataFrame.apply()(高频,按行/列)

axis=0:默认,按列传入 Series
axis=1:按行传入一行的 Series,可以访问同行多个字段!业务最常用

# 按列:求每一列最大值
df[["age","salary"]].apply(max)

# 按行:同时读取本行age、salary、city,多字段联合计算
def row_func(row):
    if row["city"] == "杭州" and row["salary"] > 10000:
        return "重点员工"
    else:
        return "普通员工"

df["tag"] = df.apply(row_func, axis=1)

axis=1 是很多报表逻辑的救星,但大数据量很慢,几十万行慎用。

lambda 简写示例:

df["tag"] = df.apply(lambda r: "重点员工" if (r["city"]=="杭州" and r["salary"]>10000) else "普通员工", axis=1)

五、DataFrame.applymap()

遍历 DataFrame 每一个单元格元素,所有字段统一处理

注意:Pandas 2.1 之后官方推荐使用 df.map(),applymap 逐步废弃

# 全部转为字符串
df_str = df[["name","city"]].applymap(str)
# 数字统一保留2位小数
df[["age","salary"]].applymap(lambda x: round(x,2))

六、参数说明

  • func:自定义函数
  • args:位置参数,给函数额外传参
  • kwargs:关键字参数
def add_val(x, offset):
    return x + offset
df["salary_add"] = df["salary"].apply(add_val, args=(1000,))

七、返回值类型

  1. 函数返回单个值 → apply 输出 Series
  2. 函数返回 Series → apply 输出 DataFrame
  3. 函数返回列表/元组 → 可以展开成多列

八、性能对比(非常重要)

优先级从高到低:

  1. 向量化运算(df["salary"] * 1.05、str、dt、布尔筛选)✅ 首选
  2. map(字典映射,底层优化好)
  3. Series.apply
  4. DataFrame.apply(axis=1) ❌ 大数据尽量避免
  5. applymap ❌ 最慢

示例:不要用 apply 做简单判断

# ❌ 慢写法 apply
df["level"] = df["salary"].apply(lambda x: "高" if x>15000 else "中")
# ✅ 推荐向量化 np.where
df["level"] = np.where(df["salary"]>15000, "高", "中")

多条件向量化:np.select

conds = [
    df["salary"] >= 15000,
    df["salary"] >= 10000
]
choices = ["高", "中"]
df["level"] = np.select(conds, choices, default="低")

九、综合实战模板

def calc_emp_info(row):
    """行级复合逻辑示例"""
    age = row["age"]
    sal = row["salary"]
    if age >= 30 and sal >= 12000:
        return "资深"
    elif age >= 25:
        return "骨干"
    else:
        return "新人"

df["emp_type"] = df.apply(calc_emp_info, axis=1)
print(df)

十、高频坑清单

  1. apply(axis=1) 速度极慢,上万行以上优先改用 np.where / np.select
  2. map 传入函数时,遇到匹配不到的值会返回 NaN
  3. 不要在 apply 里面写循环,循环套循环性能灾难
  4. apply 返回多列时,忘记 .apply(pd.Series) 会得到元组列
  5. applymap 只适合全表统一元素处理,按行多字段判断不能用
  6. 区分:df.apply 是按行/列,不是逐个单元格

十一、选型速查表

方法对象遍历粒度典型场景
Series.mapSeries单个值字典映射、一对一转换
Series.applySeries单个值复杂单值函数、多返回值
DataFrame.applyDataFrame整行 / 整列跨行多字段联合逻辑
DataFrame.applymapDataFrame每个单元格全表统一元素转换(逐步弃用)
标签:

0 条笔记