现在位置: 首页 > Pandas 教程 > 正文

Pandas groupby-transform() 函数

Pandas 常用函数 Pandas 常用函数


groupby.transform() 是 Pandas 中用于分组后进行数据变换的函数。它与聚合函数不同,聚合会返回每个分组的汇总结果,而变换会返回与原数据同样长度的结果

简单来说,transform() 将分组计算的结果"广播"回原数据的每一行。这在计算每个成员在组内的相对位置、组内标准化、与组平均值的差异等场景中非常有用。


基本语法与参数

transform() 是 GroupBy 对象的成员函数,需要先使用 groupby() 分组后再调用。

语法格式

GroupBy.transform(func, axis=0, *args, engine=None, engine_kwargs=None, **kwargs)

参数说明

参数 类型 说明 默认值
func str、list、dict 或 callable 变换函数。可以是内置函数名(如 'mean', 'sum', 'std')、函数列表、对各列指定不同函数的字典,或自定义函数。 None
axis int 应用变换的轴方向,0 表示按行(分组维度),1 表示按列。 0
args tuple 传递给变换函数的额外位置参数。 ()
engine str 指定计算引擎,可以是 'cython' 或 'numba'。None 表示由 Pandas 自动选择。 None
engine_kwargs dict 传递给底层引擎的额外参数字典。 None

返回值

  • 返回类型SeriesDataFrame
  • 说明:返回与原始数据行数相同的结果。每个原始数据行会得到其所属分组的计算结果。

实例

让我们通过一系列从简单到复杂的例子,彻底掌握 groupby.transform() 的用法。

示例 1:基础用法 - 计算组内平均值

transform() 最基础的用法是计算每个分组的平均值,然后将结果广播回每一行。

实例

import pandas as pd

# 创建学生成绩数据
data = {
    '姓名': ['张三', '李四', '王五', '赵六', '孙七', '周八', '吴九', '郑十'],
    '班级': ['A', 'A', 'A', 'B', 'B', 'B', 'B', 'A'],
    '语文': [85, 92, 78, 88, 95, 82, 90, 87],
    '数学': [90, 85, 92, 78, 88, 91, 85, 89]
}

# 创建 DataFrame
df = pd.DataFrame(data)

print("学生成绩数据:")
print(df)
print()

# 计算每个班级的数学平均分,然后广播到每一行
df['班级数学平均分'] = df.groupby('班级')['数学'].transform('mean')

# 计算每个学生成绩与班级平均分的差值
df['与平均分差值'] = df['数学'] - df['班级数学平均分']

print("添加班级平均分和差值后的数据:")
print(df)
print()

# 同时计算多列的班级平均值
df[['班级语文平均分', '班级数学平均分2']] = df.groupby('班级')[['语文', '数学']].transform('mean')

print("添加多列平均值后的数据:")
print(df)

运行结果:

学生成绩数据:
   姓名  班级  语文  数学
0  张三   A   85   90
1  李四   A   92   85
2  王五   A   78   92
3  赵六   B   88   78
4  孙七   B   95   88
5  周八   B   82   91
6  吴九   B   90   85
7  郑十   A   87   89

添加班级平均分和差值后的数据:
   姓名  班级   语文   数学   班级数学平均分   与平均分差值
0  张三   A   85   90  89.000000   1.000000
1  李四   A   92   85  89.000000  -4.000000
2  王五   A   78   92  89.000000   3.000000
3  赵六   B   88   78  85.666667   -7.666667
4  孙七   B   95   88  85.666667   2.333333
5  周八   B   82   91  85.666667   5.333333
6  吴九   B   90   85  85.666667   -0.666667
7  郑十   A   87   89  89.000000   0.000000

添加多列平均值后的数据:
   姓名  班级   语文   数学  班级语文平均分  班级数学平均分2
0  张三   A   85   90  85.500000         89.0
1  李四   A   92   85  85.500000         89.0
2  王五   A   78   92  85.500000         89.0
3  赵六   B   88   78  88.750000         85.5
4  孙七   B   95   88  88.750000         85.5
5  周八   B   82   91  88.750000         85.5
6  吴九   B   90   85  88.750000         85.5
7  郑十   A   87   89  85.500000         89.0

代码解析:

  1. transform('mean') 计算每个班级的数学平均分。
  2. 由于张三、李四、王五、郑十都在 A 班,所以他们得到的"班级数学平均分"都是 A 班数学成绩的平均值。
  3. 返回值的长度与原始数据行数相同,这是 transform 与 agg 的关键区别。

示例 2:组内标准化(Z-Score)

transform() 常用于计算组内标准化值,这在比较个体在组内的相对位置时非常有用。

实例

import pandas as pd

# 创建员工工资数据
data = {
    '姓名': ['张三', '李四', '王五', '赵六', '孙七', '周八', '吴九', '郑十'],
    '部门': ['销售', '销售', '销售', '技术', '技术', '技术', '行政', '行政'],
    '工资': [5000, 8000, 6000, 9000, 12000, 10000, 4500, 7000]
}
df = pd.DataFrame(data)

print("员工工资数据:")
print(df)
print()

# 计算每个员工的工资在部门内的 Z-Score(标准化得分)
# Z-Score = (原始值 - 组内均值) / 组内标准差
df['部门平均工资'] = df.groupby('部门')['工资'].transform('mean')
df['部门工资标准差'] = df.groupby('部门')['工资'].transform('std')
df['Z-Score'] = (df['工资'] - df['部门平均工资']) / df['部门工资标准差']

print("添加部门统计和Z-Score后的数据:")
print(df)
print()

# 使用自定义函数一次性计算 Z-Score
def z_score(x):
    """计算 Z-Score"""
    return (x - x.mean()) / x.std()

df['Z-Score2'] = df.groupby('部门')['工资'].transform(z_score)

print("使用自定义函数计算的 Z-Score:")
print(df[['姓名', '部门', '工资', 'Z-Score2']])

运行结果:

员工工资数据:
  姓名  部门    工资
0  张三   销售   5000
1  李四   销售   8000
2  王五   销售   6000
3  赵六   技术   9000
4  孙七   技术  12000
5  周八   技术  10000
6  吴九   行政   4500
7  郑十   行政   7000

添加部门统计和Z-Score后的数据:
   姓名   部门    工资    部门平均工资   部门工资标准差    Z-Score
0  张三   销售  5000  6333.333333  1525.755   -0.872871
1  李四   销售  8000  6333.333333  1525.755    1.092091
2  王五   销售  6000  6333.333333  1525.755   -0.218218
3  赵六   技术  9000 10333.333333  1525.755   -0.872871
4  孙七   技术 12000 10333.333333  1525.755    1.092091
5  周八   技术 10000 10333.333333  1525.755   -0.218218
6  吴九   行政   4500  5750.000000  1767.767   -0.707107
7  郑十   行政   7000  5750.000000  1767.767    0.707107

使用自定义函数计算的 Z-Score:
   姓名  部门    工资   Z-Score2
0  张三   销售  5000  -0.872871
1  李四   销售  8000   1.092091
2  王五   销售  6000  -0.218218
3  赵六   技术  9000  -0.872871
4  孙七   技术 12000   1.092091
5  周八   技术 10000  -0.218218
6  吴九   行政  4500  -0.707107
7  郑十   行政  7000   0.707107

代码解析:

  • Z-Score 表示原始值距离均值的标准差数量,正值表示高于均值,负值表示低于均值。
  • 通过 Z-Score,可以消除不同部门工资水平差异的影响,比较员工在其部门内的相对位置。
  • 张三的 Z-Score 为 -0.87,说明他的工资低于销售部门平均水平约 0.87 个标准差。

示例 3:组内排名和百分位

transform() 还可以用于计算组内排名,这在排序和竞争分析中非常有用。

实例

import pandas as pd
import numpy as np

# 创建学生成绩数据
data = {
    '姓名': ['张三', '李四', '王五', '赵六', '孙七', '周八', '吴九', '郑十'],
    '班级': ['A', 'A', 'A', 'B', 'B', 'B', 'B', 'A'],
    '数学': [90, 85, 92, 78, 88, 91, 85, 89]
}
df = pd.DataFrame(data)

print("学生成绩数据:")
print(df)
print()

# 计算每个学生在班级内的排名(从高到低)
df['班级内排名'] = df.groupby('班级')['数学'].transform(lambda x: x.rank(ascending=False))

# 计算每个学生在班级内的百分位(百分比排名)
df['班级内百分位'] = df.groupby('班级')['数学'].transform(lambda x: x.rank(pct=True))

print("添加班级内排名和百分位后的数据:")
print(df)
print()

# 填充缺失值的另一种用法:使用组内均值填充
data_with_na = {
    '姓名': ['张三', '李四', '王五', '赵六', '孙七', '周八'],
    '班级': ['A', 'A', 'A', 'B', 'B', 'B'],
    '成绩': [85, np.nan, 78, 88, np.nan, 82]
}
df_na = pd.DataFrame(data_with_na)

print("包含缺失值的数据:")
print(df_na)
print()

# 使用组内均值填充缺失值
df_na['填充后的成绩'] = df_na.groupby('班级')['成绩'].transform(lambda x: x.fillna(x.mean()))

print("使用组内均值填充后的结果:")
print(df_na)

运行结果:

学生成绩数据:
   姓名  班级  数学
0  张三   A   90
1  李四   A   85
2  王五   A   92
3  赵六   B   78
4  孙七   B   88
5  周八   B   91
6  吴九   B   85
7  郑十   A   89

添加班级内排名和百分位后的数据:
   姓名   班级   数学   班级内排名   班级内百分位
0  张三   A   90   2.0   0.666667
1  李四   A   85   3.0   1.000000
2  王五   A   92   1.0   1.000000
3  赵六   B   78   4.0   0.250000
4  孙七   B   88   2.0   0.500000
5  周八   B   91   1.0   0.750000
6  吴九   B   85   3.0   0.500000
7  郑十   A   89   4.0   0.333333

包含缺失值的数据:
   姓名  班级   成绩
0  张三   A   85.0
1  李四   A    NaN
2  王五   A   78.0
3  赵六   B   88.0
4  孙七   B    NaN
5  周八   B   82.0

使用组内均值填充后的结果:
   姓名  班级   成绩  填充后的成绩
0  张三   A   85.0         85.0
1  李四   A    NaN         81.5
2  王五   A   78.0         78.0
3  赵六   B   88.0         88.0
4  孙七   B    NaN         85.0
5  周八   B   82.0         82.0

代码解析:

  • rank(ascending=False) 计算排名,较高的数值排在前面。
  • rank(pct=True) 计算百分位排名,结果在 0 到 1 之间。
  • transform(lambda x: x.fillna(x.mean())) 是处理缺失值的常见模式,用每个分组的均值填充该组内的缺失值。

示例 4:使用自定义变换函数

transform() 支持自定义函数,可以实现更复杂的组内数据变换逻辑。

实例

import pandas as pd
import numpy as np

# 创建股票价格数据
data = {
    '股票代码': ['AAPL', 'AAPL', 'AAPL', 'AAPL', 'GOOG', 'GOOG', 'GOOG', 'GOOG'],
    '日期': ['2024-01-01', '2024-01-02', '2024-01-03', '2024-01-04'] * 2,
    '收盘价': [100, 105, 102, 110, 80, 85, 82, 90]
}
df = pd.DataFrame(data)

print("股票价格数据:")
print(df)
print()

# 自定义函数:计算每只股票的日收益率
def calc_daily_return(prices):
    """计算日收益率:(今天价格 - 昨天价格) / 昨天价格"""
    return prices.pct_change()

# 计算每只股票的日收益率
df['日收益率'] = df.groupby('股票代码')['收盘价'].transform(calc_daily_return)

print("添加日收益率后的数据:")
print(df)
print()

# 自定义函数:计算组内最大值与最小值的归一化值
def normalize(x):
    """归一化:(x - min) / (max - min)"""
    return (x - x.min()) / (x.max() - x.min())

# 计算每只股票价格的归一化值
df['归一化价格'] = df.groupby('股票代码')['收盘价'].transform(normalize)

print("添加归一化价格后的数据:")
print(df)

运行结果:

股票价格数据:
  股票代码         日期   收盘价
0  AAPL  2024-01-01   100
1  AAPL  2024-01-02   105
2  AAPL  2024-01-03   102
3  AAPL  2024-01-04   110
4  GOOG  2024-01-01    80
5  GOOG  2024-01-02    85
6  GOOG  2024-01-03    82
7  GOOG  2024-01-04    90

添加日收益率后的数据:
   股票代码         日期  收盘价     日收益率
0  AAPL  2024-01-01   100         NaN
1  AAPL  2024-01-02   105    0.050000
2  AAPL  2024-01-03   102   -0.028571
3  AAPL  2024-01-04   110    0.078431
4  GOOG  2024-01-01    80         NaN
5  GOOG  2024-01-02    85    0.062500
6  GOOG  2024-01-03    82   -0.035294
7  GOOG  2024-01-04    90    0.097561

添加归一化价格后的数据:
   股票代码         日期  收盘价     日收益率   归一化价格
0  AAPL  2024-01-01   100         NaN    0.000000
1  AAPL  2024-01-02   105    0.050000    0.500000
2  AAPL  2024-01-03   102   -0.028571    0.200000
3  AAPL  2024-01-04   110    0.078431    1.000000
4  GOOG  2024-01-01    80         NaN    0.000000
5  GOOG  2024-01-02    85    0.062500    0.500000
6  GOOG  2024-01-03    82   -0.035294    0.200000
7  GOOG  2024-01-04    90    0.097561    1.000000

代码解析:

  • 变换函数接收的是一个 Series(每个分组内的数据)。
  • 返回的 Series 长度必须与输入的 Series 长度相同。
  • 第一个值是 NaN 是因为 pct_change() 计算的是相对变化,第一天没有前一天的数据。

关键区别:agg()transform() 的区别在于:agg() 返回每个分组的汇总结果(行数等于分组数),而 transform() 返回与原数据同样长度的结果。简单记忆:agg 压缩数据,transform 保持数据形状。


Pandas 常用函数 Pandas 常用函数