df.groupby() 是 pandas 最强大的功能之一,遵循分 裂-应用-组合(split-apply-combine)范式。
groupby 基础
创建 groupby 对象
import pandas as pd
import numpy as np
df = pd.dataframe({
'部门': ['销售', '销售', '技术', '技术', '人事', '人事'],
'员工': ['张三', '李四', '王五', '赵六', '钱七', '孙八'],
'工资': [8000, 9000, 15000, 12000, 7000, 7500],
'奖金': [2000, 2500, 3000, 3500, 1500, 1800],
'入职年份': [2019, 2021, 2018, 2020, 2022, 2021]
})
# 按单列分组
grouped = df.groupby('部门')
# 返回 dataframegroupby 对象,尚未执行任何计算
# 按多列分组
multi_grouped = df.groupby(['部门', '入职年份'])
# 查看分组
print(grouped.groups) # 返回 {组名: 行索引列表}
print(grouped.indices) # 同上但返回 numpy 数组
print(grouped.ngroups) # 分组数量
for name, group in grouped:
print(name, group.shape) # 遍历每个组
聚合(aggregation)
1. 基本聚合函数
# 对单个列聚合 print(grouped['工资'].sum()) # 每组工资总和 print(grouped['工资'].mean()) # 每组工资均值 print(grouped['工资'].count()) # 每组非空计数 print(grouped['工资'].std()) # 标准差 print(grouped['工资'].min()) # 最小值 print(grouped['工资'].max()) # 最大值 print(grouped['工资'].median()) # 中位数 print(grouped['工资'].first()) # 第一个值 print(grouped['工资'].last()) # 最后一个值 print(grouped['工资'].nunique()) # 唯一值数 print(grouped['工资'].var()) # 方差 print(grouped['工资'].sem()) # 均值的标准误差 print(grouped['工资'].quantile(0.75)) # 0.75 分位数
2.agg()— 多函数聚合
# 方式一: 传入函数列表
result = grouped['工资'].agg(['sum', 'mean', 'std', 'count'])
print(result)
# sum mean std count
# 部门
# 人事 14500 7250 353.5 2
# 技术 27000 13500 2121.3 2
# 销售 17000 8500 707.1 2
# 方式二: 传入字典 -> 对不同列应用不同聚合
result = grouped.agg({
'工资': ['sum', 'mean'],
'奖金': ['min', 'max', 'sum']
})
print(result)
# 方式三: 命名聚合(推荐!)
result = grouped.agg(
总工资=('工资', 'sum'),
平均工资=('工资', 'mean'),
最高奖金=('奖金', 'max'),
人数=('员工', 'count')
)
print(result)
3. 常用内置字符串聚合
| 函数名 | 说明 | 函数名 | 说明 |
|---|---|---|---|
| 'sum' | 求和 | 'prod' | 乘积 |
| 'mean' | 均值 | 'std' | 标准差 |
| 'median' | 中位数 | 'var' | 方差 |
| 'min' | 最小值 | 'max' | 最大值 |
| 'count' | 非空计数 | 'size' | 包括 nan 的计数 |
| 'first' | 第一个 | 'last' | 最后一个 |
| 'nunique' | 唯一值数 | 'sem' | 均值标准误 |
| 'quantile' | 分位数 | 'ohlc' | open-high-low-close |
4. 自定义聚合函数
# 自定义函数
def range_func(x):
return x.max() - x.min()
print(grouped['工资'].agg(range_func))
print(grouped['工资'].agg(lambda x: x.max() - x.min()))
# 多列自定义
print(grouped.agg(lambda x: x.max() - x.min()))
transform — 组内变换
transform 返回与原始 dataframe 等长的 series,非常适合做组内标准化。
# 组内减去均值(去中心化)
df['工资_组内去中心'] = grouped['工资'].transform(lambda x: x - x.mean())
# 组内 z-score 标准化
df['工资_zscore'] = grouped['工资'].transform(
lambda x: (x - x.mean()) / x.std()
)
# 组内填充均值
df['工资_组内补均值'] = grouped['工资'].transform(
lambda x: x.fillna(x.mean())
)
# 常用内置 transform
df['工资_排名'] = grouped['工资'].transform('rank') # 组内排名
df['工资_百分比'] = grouped['工资'].transform('pct_change') # 组内变化率
df['工资_组均值'] = grouped['工资'].transform('mean') # 组均值广播
agg vs transform 对比:
# agg 返回: 每个组一行
print(grouped['工资'].agg('mean')) # shape: (3,)
# transform 返回: 与原 dataframe 相同行数
print(grouped['工资'].transform('mean')) # shape: (6,)
filter — 组级筛选
# 保留组大小 >2 的组 filtered = grouped.filter(lambda g: len(g) > 2) # 保留工资总和 > 20000 的组 filtered = grouped.filter(lambda g: g['工资'].sum() > 20000) # 保留组内工资最大值 > 10000 的组 filtered = grouped.filter(lambda g: g['工资'].max() > 10000)
apply — 最灵活的组操作
# apply 可以返回任意形状的 dataframe/series
def top_n(group, n=1):
return group.nlargest(n, '工资')
result = grouped.apply(top_n, n=1)
print(result)
# 返回标量
result = grouped.apply(lambda g: g['工资'].max() - g['工资'].min())
# apply 可返回多列
def summary(g):
return pd.series({
'人数': len(g),
'平均工资': g['工资'].mean(),
'工资范围': g['工资'].max() - g['工资'].min()
})
result = grouped.apply(summary)
print(result)
其他分组方式
1. 按函数分组
# 按索引长度的奇偶分组 df.groupby(lambda idx: idx % 2 == 0).sum() # 按工资是否 > 10000 分组 df.groupby(lambda idx: df.loc[idx, '工资'] > 10000).sum()
2. 按映射字典分组
mapping = {'张三': '组a', '李四': '组a', '王五': '组b',
'赵六': '组b', '钱七': '组c', '孙八': '组c'}
df.groupby(mapping).sum() # 注意: 按 index 映射
df.groupby(df['员工'].map(mapping)).sum() # 按列值映射
3.groupby的as_index参数
# as_index=true (默认): 分组键作为索引
result = df.groupby('部门', as_index=true).sum()
# as_index=false: 分组键保留为列(类似 sql group by)
result = df.groupby('部门', as_index=false).sum()
4. 多个groupby参数
# level: 按 multiindex 的层级分组
# axis: 按列分组 (axis=1)
# sort: 是否对组键排序(默认 true,设为 false 提升性能)
# dropna: 是否丢弃 nan 组键(默认 true)
# observed: 分类数据是否只显示出现的组
# 性能优化设置
df.groupby('部门', sort=false, dropna=true)
实用分组技巧
累计操作
df['工资_累计和'] = grouped['工资'].cumsum() # 组内累计和 df['工资_累计最大'] = grouped['工资'].cummax() # 组内累计最大值 df['工资_累计积'] = grouped['工资'].cumprod() # 组内累计积
组内偏移
df['工资_上一行'] = grouped['工资'].shift(1) # 组内上移 df['工资_下一行'] = grouped['工资'].shift(-1) # 组内下移 df['工资_变化'] = grouped['工资'].diff() # 组内差分 df['工资_变化率'] = grouped['工资'].pct_change() # 组内变化率
组内排名
df['工资排名'] = grouped['工资'].rank(method='dense', ascending=false) # method: 'average', 'min', 'max', 'first', 'dense'
取每组的头/尾
df.groupby('部门').head(2) # 每组前 2 行
df.groupby('部门').tail(1) # 每组最后 1 行
df.groupby('部门').nth(0) # 每组第 0 个
df.groupby('部门').nth([0, -1]) # 每组第 0 和最后一个
df.groupby('部门').nlargest(2, '工资') # 每组工资最高的 2 行
df.groupby('部门').nsmallest(1, '奖金') # 每组奖金最低的 1 行
抽样
df.groupby('部门').sample(n=1, random_state=42) # 每组抽 1 个
df.groupby('部门').sample(frac=0.5, random_state=42) # 每组抽 50%
链式操作
# pandas 支持方法链
result = (df
.groupby('部门')
.agg(总工资=('工资', 'sum'), 人数=('员工', 'count'))
.query('人数 > 1')
.sort_values('总工资', ascending=false)
.reset_index()
)
print(result)
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论