Pandas groupby-transform() 函数
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 |
返回值
- 返回类型:
Series或DataFrame - 说明:返回与原始数据行数相同的结果。每个原始数据行会得到其所属分组的计算结果。
实例
让我们通过一系列从简单到复杂的例子,彻底掌握 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)
# 创建学生成绩数据
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
代码解析:
transform('mean')计算每个班级的数学平均分。- 由于张三、李四、王五、郑十都在 A 班,所以他们得到的"班级数学平均分"都是 A 班数学成绩的平均值。
- 返回值的长度与原始数据行数相同,这是 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']])
# 创建员工工资数据
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)
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)
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 常用函数