在Python做数据处理中会经常用到map 、apply、 agg、 transformer ,今天总结一下集中函数的用法。

map

map映射规则

map 只能用于 Series(单列),作用是对列中的「每个元素」做 “一对一” 映射(比如替换值、简单计算),逻辑简单、性能快。

# Series.map(映射规则):规则可以是字典、函数、Series
df['列名'].map(映射规则)

首先生成示例数据

import pandas as pd
import numpy as np

year = 2024  # 固定年份2024
start_date = f"{year}-01-01"
end_date = f"{year}-12-31"
total_users_min = 1000
total_users_max = 5000
overproduction_ratio_max = 0.8  # 调高上限(确保超产率可能>0.6)
overproduction_flag_type = "str"  # 保持输出1/0
output_type = "excel"


regions = ["北京", "上海", "广州", "深圳", "杭州", "成都", "武汉", "西安", "南京", "重庆"]
date_column = pd.date_range(start=start_date, end=end_date, freq="D")
index = pd.MultiIndex.from_product(
    [regions, date_column],
    names=["地区", "日期"]
)
base_df = pd.DataFrame(index=index).reset_index()

np.random.seed(42)
total_users_column = np.random.randint(
    low=total_users_min,
    high=total_users_max + 1,
    size=len(base_df)
).astype(int)

overproduction_ratios = np.random.uniform(0, overproduction_ratio_max, size=len(base_df))
overproduction_users_column = np.round(total_users_column * overproduction_ratios).astype(int)
overproduction_users_column = np.minimum(overproduction_users_column, total_users_column)  # 不超过总用户数
overproduction_users_column = np.maximum(overproduction_users_column, 0)  # 避免负数

total_users_column = pd.to_numeric(total_users_column, errors="coerce")
overproduction_users_column = pd.to_numeric(overproduction_users_column, errors="coerce")
total_users_column = np.where(total_users_column == 0, 1, total_users_column)  # 避免除以0

overproduction_rate_column = np.round(
    overproduction_users_column / total_users_column,
    decimals=4
)

if overproduction_flag_type == "bool":
    is_overproduction_column = overproduction_rate_column > 0.6  # 基于超产率判断
elif overproduction_flag_type == "str":
    is_overproduction_column = np.where(
        overproduction_rate_column > 0.6,  # 超产率>0.6 → 1(超产)
        1,
        0  # 否则→0(未超产)
    )
result_df = base_df.assign(
    总用户数=total_users_column.astype(int),
    超产用户数=overproduction_users_column.astype(int),
    超产率=overproduction_rate_column,
    是否超产=is_overproduction_column
)
result_df = result_df[["日期", "地区", "总用户数", "超产用户数", "超产率", "是否超产"]] 
result_df

在这里插入图片描述

# 字典映射:1→超产,0→未超产
result_df['是否超产_文本'] = result_df['是否超产'].map({1: '超产', 0: '未超产'})
result_df

在这里插入图片描述

map简单计算

# 函数映射:每个元素×100 + 加百分号
result_df['超产率_百分比'] = result_df['超产率'].map(lambda x: f"{x*100:.1f}%")
result_df

在这里插入图片描述

map的关键特点
1.仅支持 Series(不能直接用在 DataFrame 上);
2.映射规则简单(字典、无复杂逻辑的函数);
3.性能最优(比Series.apply 快,因为无额外函数调用开销)。

apply

Series.apply(元素级复杂映射)

# 对「超产率」列分类:高/中/低超产(多条件判断)
def classify_overproduction(rate):
    if rate > 0.7:
        return '高超产'
    elif rate > 0.4:
        return '中超产'
    else:
        return '低超产'

# 用 apply 调用自定义复杂函数
result_df['超产等级'] = result_df['超产率'].apply(classify_overproduction)

result_df

在这里插入图片描述

DataFrame.apply(行 / 列级处理)

案例1

# 按行处理(axis=1):计算「超产用户数占总用户数的差值(千分位)」
result_df['超产用户数千分差'] = result_df.apply(
    lambda row: (row['超产用户数'] / row['总用户数']) * 1000,  # row 代表一行数据
    axis=1
)
result_df

在这里插入图片描述

分组后.apply(分组级复杂逻辑)

案例2:apply 后面跟自定义的函数

import pandas as pd
import numpy as np

#生成假数据
names = ["设备A", "设备B", "设备C", "传感器1", "传感器2"]
total_rows = 2000
value_min = 3.0
value_max = 135.0
value_decimals = 2
name_column = [names[i % len(names)] for i in range(total_rows)]
np.random.seed(42) 
value_column = np.round(
    np.random.uniform(value_min, value_max, size=total_rows),
    decimals=value_decimals)
result_df = pd.DataFrame({
    "名称": name_column,
    "值": value_column
})
def jisuan_zscore(group):
    # 1. 取出当前分组的「值」列(核心计算对象)
    x = group["值"]
    
    # 2. 生成「非缺失值掩码」(处理缺失值的关键步骤)
    mask = ~x.isna()  # x.isna():判断每个位置是否是缺失值(NaN),返回布尔数组;~取反→True=非缺失值,False=缺失值
    
    # 3. 初始化Z-score数组:长度和当前分组一致,默认填充NaN(缺失值)
    z_scores = np.full(len(x), np.nan)  # 为什么先填NaN?→ 只对非缺失值计算,缺失值位置保持NaN,不污染结果
    
    # 4. 确保非缺失值数量≥2(避免计算标准差时出错)
    if mask.sum() >= 2:  # mask.sum():统计非缺失值的个数(True=1,False=0)
        # 4.1 计算该组非缺失值的「均值」
        mean = x[mask].mean()
        # 4.2 计算该组非缺失值的「总体标准差」(ddof=0是关键参数)
        std = x[mask].std(ddof=0)  # ddof=0:总体标准差(分母为n);ddof=1:样本标准差(分母为n-1)
        # 4.3 计算Z-score,并赋值到对应非缺失值位置
        z_scores[mask] = (x[mask] - mean) / std  # 核心公式:(原始值-均值)/标准差
    
    # 5. 用计算好的Z-score替换当前分组的「值」列
    group["值"] = z_scores
    
    # 6. 返回处理后的分组DataFrame
    return group

#group_keys=False:可选参数,作用是「不将分组键(名称)添加到结果的索引中」,保持原始数据的索引结构(如果设为 True,结果索引会多一层 “名称” 分组键,通常不需要)
RESULT = result_df.groupby("名称", group_keys=False).apply(jisuan_zscore) # 按「名称」分组,对每组的「值」列进行 Z-score(标准化)计算,并替换原列值
RESULT

以上代码的核心解释

  1. 缺失值处理逻辑
    代码用 mask = ~x.isna() 筛选非缺失值,仅对非缺失值计算 Z-score;缺失值位置保持 np.nan,避免缺失值影响均值 / 标准差计算,也避免结果中出现无效值。
  2. 为什么要判断 mask.sum() >= 2?
    若某分组的非缺失值≤1:计算总体标准差时,分母为 n(1 或 0),会导致标准差 = 0(n=1 时,所有值等于均值,标准差为 0),此时除以 0 会报错;
    该判断是「防错机制」,确保只有足够数据量时才计算 Z-score,否则该分组的「值」列全部保留为 NaN。
  3. 总体标准差(ddof=0)的意义
    x[mask].std(ddof=0) 中 ddof=0 表示「自由度为 0」,对应总体标准差(分母是 n,n 为非缺失值个数);
    若改为 ddof=1,则是样本标准差(分母是 n-1),适用于 “用样本推断总体” 的场景;
    代码注释提到 “与 zscore 一致”,是指 numpy.nanmean/numpy.nanstd(默认 ddof=0)和 scipy.stats.zscore(默认 ddof=0)的默认行为,保证计算逻辑统一。
  4. 分组逻辑的核心
    groupby(“名称”) 确保「同一名称的所有数据在同一组内计算 Z-score」,避免不同名称的数据混合计算(比如 “设备 A” 和 “设备 B” 的原始值量级可能不同,分开标准化才合理)。这里是引用

在这里插入图片描述
groupby 中 group_keys=True 的 用处。

RESULT = result_df.groupby("名称", group_keys=True).apply(jisuan_zscore) # 按「名称」分组,对每组的「值」列进行 Z-score(标准化)计算,并替换原列值
RESULT

在这里插入图片描述

apply 的关键特点
1.万能灵活,能覆盖 map、agg、transform 的部分场景;
2.性能:Series.apply ≈map(简单逻辑),DataFrame.apply/ 分组.apply 较慢(大数据量谨慎用);
3.函数参数:Series.apply是「单个元素」,DataFrame.apply 是「一行 / 一列」,分组.apply 是「子 DataFrame」。

agg

agg 是「聚合(Aggregation)」的简写,专门用于统计汇总(多行→1 个 scalar 值),支持多列、多函数同时计算,性能最优(pandas 内部优化,无 Python 循环)。

单列单函数

A0 = result_df['超产用户数'].agg('sum')
A0

在这里插入图片描述

多列多函数(字典指定)

## 方法一
A1 = result_df.groupby('地区').agg({
    '总用户数': ['sum', 'mean'],
    '是否超产': 'count'
})
A1

在这里插入图片描述

## 方法二
A2 = result_df.groupby('地区').agg(
    全年总用户数=('总用户数', 'sum'),  # 总用户数求和
    全年超产用户数均值=('超产用户数', 'mean'),  # 超产用户数均值
    超产天数=('是否超产', 'sum'),  # 超产天数(1的个数)
    最高超产率=('超产率', 'max')  # 最高超产率
).reset_index()
A2

在这里插入图片描述

agg的关键特点
1.仅支持「聚合逻辑」(输入多行,输出 1 个统计值); 不支持变形、过滤,但若需汇总,优先用 agg(比 apply 简洁 10 倍);
2.支持重命名结果列(如 全年总用户数=(‘总用户数’, ‘sum’))。

transform

transform 专门用于生成新列,核心约束:输出长度必须和输入长度一致(即保持原数据行数),常用于给明细数据添加「分组级统计指标」(如分组均值、标准化值)。

# 分组后对列做变形,返回同长度 Series,用于新增列
df['新列'] = df.groupby('分组列')['列名'].transform(函数)

案例1

# 按地区分组,计算每个地区的总用户数均值,广播到该地区的所有行
result_df['地区总用户数均值'] = result_df.groupby('地区')['总用户数'].transform('mean')
result_df

在这里插入图片描述

transform的关键特点
1.输出长度 ≡ 输入长度(核心约束,违反则报错); 用于「给原数据加新列」,且新列是分组级统计结果;
2.性能优于分组.apply(向量化操作),比手动计算后合并更简洁。

总结

整体性能排序为:map ≈ agg ≈ transform > apply(分组/行级),其中前三者属于「高性能梯队」(向量化、无 Python 循环、内部优化),而 apply 因场景不同性能波动极大,尤其是行级 / 分组级 apply 属于「低性能梯队」。
在这里插入图片描述

# 反例慢:逐行计算超产用户数占比(慢)
df['超产占比'] = df.apply(lambda row: row['超产用户数'] / row['总用户数'], axis=1)

# 正例快-向量化运算(毫秒级)
df['超产占比'] = df['超产用户数'] / df['总用户数']
# 反例慢:分组计算总用户数求和(可用 agg)
df.groupby('地区').apply(lambda x: x['总用户数'].sum())
# 正例快-
df.groupby('地区')['总用户数'].agg('sum')

更多推荐