RSS
菜单
全部文章快讯开发科技深度热点

第 10 章 数据变换:map / apply / applymap

内容摘要

pandas 的变换三兄弟 **map / apply / applymap** 是数据加工的核心工具:map 逐元素映射、apply 逐行/逐列处理、applymap 全表逐元素处理。本篇文章讲清它们的区别、用法与性能。

pandas 的变换三兄弟 map / apply / applymap 是数据加工的核心工具:map 逐元素映射、apply 逐行/逐列处理、applymap 全表逐元素处理。本篇文章讲清它们的区别、用法与性能。

目录

1. 三兄弟的区别一张表看懂

2. map:Series 逐元素映射

3. applymap:DataFrame 逐元素

4. apply:Series 逐元素 / DataFrame 逐行逐列

5. apply 与 axis 参数

6. transform:与 apply 的对比

7. pipe:管道式处理

8. 性能对比与选择建议

9. 常见坑与注意事项

10. 本章小结与练习


1. 三兄弟的区别一张表看懂

| 方法 | 作用对象 | 处理粒度 | 传入函数参数 | 典型场景 |

|------|----------|----------|--------------|----------|

| Series.map | Series | 每个元素 | 元素值 | 字典映射、元素级清洗 |

| Series.apply | Series | 每个元素 | 元素值 | 逐元素复杂处理 |

| DataFrame.applymap | DataFrame | 每个元素 | 元素值 | 全表格式化 |

| DataFrame.apply | DataFrame | 每行/每列 | 一行/一列数据 | 行/列级聚合、处理 |

先准备数据:

import pandas as pd
import numpy as np

df = pd.DataFrame({
    "姓名": ["张三", "李四", "王五"],
    "部门": ["技术", "销售", "市场"],
    "工资": [12000, 9000, 11000],
})
print(df)

2. map:Series 逐元素映射

map 可以接受字典、函数或Series,把每个元素映射成新值。

2.1 用字典映射(最常用)

# 部门编号映射为中文
dept_map = {"技术": "Tech", "销售": "Sales", "市场": "Mkt"}
df["部门英文"] = df["部门"].map(dept_map)
print(df)
   姓名  部门     工资  部门英文
0  张三  技术  12000   Tech
1  李四  销售   9000  Sales
2  王五  市场  11000    Mkt

经典应用:分类编码 / 打标签

# 工资分档
df["工资等级"] = df["工资"].map(lambda x: "高" if x >= 10000 else "低")

# 性别编码
df["性别"] = ["男", "女", "男"]
df["性别编码"] = df["性别"].map({"男": 1, "女": 0})

2.2 用函数映射

df["工资千元"] = df["工资"].map(lambda x: x / 1000)

# 任何函数都行
import math
df["工资对数"] = df["工资"].map(math.log)

2.3 字典中没有的键

# 未覆盖的键 -> NaN
s = pd.Series(["a", "b", "c"])
print(s.map({"a": 1, "b": 2}))
# 0    1.0
# 1    2.0
# 2    NaN
map 遇到字典中不存在的值返回 NaN,适合做"白名单"映射。

2.4 与 replace 的区别

s = pd.Series(["a", "b", "c"])

# map:未匹配 -> NaN
print(s.map({"a": 1}))

# replace:未匹配保持不变
print(s.replace({"a": 1}))
# 0    1
# 1    b
# 2    c

3. applymap:DataFrame 逐元素

applymap 对 DataFrame 的每个单元格应用函数,返回同形状的 DataFrame。

df_num = pd.DataFrame({"a": [1, 2], "b": [3, 4]})

print(df_num.applymap(lambda x: x * 10))
#     a   b
# 0  10  30
# 1  20  40

# 格式化应用:所有值保留两位小数并加单位
df_fmt = df_num.applymap(lambda x: f"{x:.2f}元")
print(df_fmt)
pandas 2.1+ 推荐改用 df.map(...)(新 API),功能相同。老版本请继续用 applymap。

4. apply:Series 逐元素 / DataFrame 逐行逐列

4.1 Series.apply:逐元素

s = pd.Series([1, 2, 3])

# 等价于 map
print(s.apply(lambda x: x ** 2))
# 0    1
# 1    4
# 2    9
Series.apply 与 Series.map 在传单元素函数时几乎等价,但 map 支持字典、apply 支持复杂参数。

4.2 DataFrame.apply:逐行/逐列

df = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})

# 默认 axis=0:函数接收每一列(Series)
print(df.apply(sum))
# a    6
# b   15

# axis=1:函数接收每一行(Series)
print(df.apply(lambda row: row["a"] + row["b"], axis=1))
# 0    5
# 1    7
# 2    9

# 返回多列结果
def row_metrics(row):
    return pd.Series({
        "总和": row["a"] + row["b"],
        "差值": row["a"] - row["b"],
    })

print(df.apply(row_metrics, axis=1))
#    总和  差值
# 0   5   -3
# 1   7   -3
# 2   9   -3

4.3 apply 的常见场景

# 1. 对每列做非标准统计
df.apply(lambda col: col.max() - col.min())
# a    2
# b    2

# 2. 对每行做复杂业务逻辑
def calc(row):
    """根据多列计算奖金"""
    base = row["a"] * 100
    if row["b"] > 5:
        return base * 1.2
    return base
df["奖金"] = df.apply(calc, axis=1)

# 3. 条件分支(apply 里可以写 if/else)
df["标签"] = df.apply(
    lambda row: "大" if row["a"] + row["b"] > 8 else "小", axis=1
)

4.4 apply 传参

def scale(x, factor):
    return x * factor

# 用 args 传位置参数
print(df["a"].apply(scale, args=(10,)))

# 用 **kwargs 传关键字参数
print(df["a"].apply(lambda x: x + 100))   # 或直接闭包

4.5 result_type 控制返回形状

# 返回 Series 时,result_type="expand" 展开成 DataFrame
print(df.apply(lambda col: pd.Series({"max": col.max(), "min": col.min()}), result_type="expand"))
#       a  b
# max   3  6
# min   1  4

5. apply 与 axis 参数

再强调一次 axis(结合第 3 章):

| axis | 含义 | apply 传入的内容 |

|------|------|------------------|

| axis=0(默认) | 按列 | 每列一个 Series |

| axis=1 | 按行 | 每行一个 Series |

# axis=0:对列操作
df.apply(np.mean)              # 每列均值

# axis=1:对行操作
df.apply(np.mean, axis=1)      # 每行均值
记忆:apply 的 axis 语义与 sum/mean 完全一致:axis=0 竖直方向(跨行)→ 按列;axis=1 水平方向(跨列)→ 按行。

6. transform:与 apply 的对比

transform 与 apply 的重要区别:transform 保持形状不变(一对一变换),且可以同时传多个函数。

df = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})

# apply 可以改变形状(聚合)
print(df.apply(sum))           # 返回一行

# transform 保持形状(逐列变换,结果与原形状相同)
print(df.transform(lambda x: x * 2))
#    a  b
# 0  2  8
# 1  4  10
# 2  6  12

# 多个函数同时应用
print(df.transform(["sum", "mean"]))
#         a    b
# sum   6.0  15.0
# mean  2.0   5.0
transform 常用于 groupby 分组后"每组内做变换并保持原行数"(详见第 13 章)。

7. pipe:管道式处理

当你要把一连串变换串联起来时,pipe 让代码更可读、可复用:

def add_col(df, col_name, values):
    df[col_name] = values
    return df

def double_col(df, col):
    df[col] = df[col] * 2
    return df

# 链式调用(函数必须返回 DataFrame)
df_pipe = (df
    .pipe(add_col, "c", [7, 8, 9])
    .pipe(double_col, "c"))
print(df_pipe)

等价写法对比:

# 传统写法(嵌套,难读)
double_col(add_col(df, "c", [7, 8, 9]), "c")

# pipe 写法(顺序清晰)
df.pipe(add_col, "c", [7, 8, 9]).pipe(double_col, "c")

8. 性能对比与选择建议

| 操作 | 性能 | 建议 |

|------|------|------|

| 向量化运算(df["a"] * 2) | ⭐⭐⭐⭐⭐ | 能向量化绝不 apply |

| map / replace(字典) | ⭐⭐⭐⭐ | 映射首选 |

| apply 逐列 | ⭐⭐⭐ | 需要复杂逻辑时用 |

| apply 逐行(axis=1) | ⭐⭐ | 大表慎用,性能差 |

| Python 循环(for) | ⭐ | 大数据量尽量避免 |

# 性能对比:1 百万行
import time

n = 1_000_000
df = pd.DataFrame({"x": np.random.rand(n)})

# 向量化(最快)
t0 = time.time()
r1 = df["x"] * 2
print(f"向量化: {time.time()-t0:.3f}s")

# map
t0 = time.time()
r2 = df["x"].map(lambda v: v * 2)
print(f"map: {time.time()-t0:.3f}s")

# apply 逐列
t0 = time.time()
r3 = df["x"].apply(lambda v: v * 2)
print(f"apply: {time.time()-t0:.3f}s")

# 循环(最慢,勿用于生产)
# for i in range(n): ...
原则:能向量化(+、*、>, 内置方法)就不要 apply;能用 map 就不要 apply;apply 只用于"向量化写不出来"的复杂逻辑。

9. 常见坑与注意事项

| 坑 | 现象 | 解决办法 |

|----|------|----------|

| map 的字典没覆盖全 | 静默变 NaN | 确认映射完整,或改用 replace |

| apply 忘写 axis=1 | 处理对象错误 | 逐行处理必须 axis=1 |

| apply 返回形状不一致 | 结果混乱 | 返回 Series 时指定列名/result_type |

| 循环替代向量化 | 慢到怀疑人生 | 优先向量化、map、apply |

| apply 修改原表副作用 | 数据被意外改动 | 在函数内用 copy() |

| applymap 处理日期列 | 报错 | 先 select_dtypes 过滤出需要的列 |


10. 本章小结与练习

小结

  • map:Series 逐元素,字典/函数/Series 映射,未命中→NaN;
  • applymap(新版本 df.map):DataFrame 逐元素格式化;
  • apply:Series 逐元素、DataFrame 逐行(axis=1)/逐列(axis=0);
  • transform:保持形状的一对一变换,支持多函数;
  • pipe:链式管道,代码可读可复用;
  • 性能:向量化 > map > apply 逐列 > apply 逐行 > 循环。

练习题

1. 用字典 map 把"春夏秋冬"映射为 1/2/3/4。

2. 用 apply 给每行计算"工资×0.9 后取整"。

3. 用 applymap 把 DataFrame 所有数值格式化为带千分位的字符串。

4. 用 transform 同时计算每列的 sum 和 mean。

5. 写一个 pipe 链:加一列 → 对列做平方 → 过滤大于 10 的行。


下一篇预告:第 11 章 数据合并:concat / merge / join —— 把多张表拼在一起的三种武器。
— 全文完 —回到顶部 ↑
下载推广海报

文章推广海报

《第 10 章 数据变换:map / apply / applymap》完整推广海报
DISCUSSION

文章回复

0 条公开回复
未登录回复需要审核后公开
还没有回复,欢迎参与讨论。