Python grouoby高阶用法
·
目录
8. 分组 rank / diff / shift 等时序操作
10. as_index=False 控制 index 行为
✅ 示例:groupby + resample(按用户+季度统计)
方法三:nlargest() 每组取 Top N(值列唯一时推荐)
DataFrame.groupby(
by=None,
axis=0,
level=None,
as_index=True,
sort=True,
group_keys=True,
observed=False,
dropna=True
)
| 参数名 | 说明 |
|---|---|
| by | 分组键:列名、数组、函数或列表(用于指定按哪个字段或规则分组) |
| axis | 分组的方向(默认 0:按行分组;1 表示按列分组) |
| level | 用于多层索引(MultiIndex)的分组 |
| as_index | 是否将分组字段作为结果的索引(默认 True) |
| sort | 是否对分组键进行排序(默认 True) |
| group_keys | 是否将分组键作为层级添加到结果中(用于 apply) |
| observed | 针对分类变量,是否只显示实际出现的组合(默认 False) |
| dropna | 是否丢弃分组键中为 NaN 的组(默认 True) |
import pandas as pd
df = pd.DataFrame({
'A': ['foo', 'bar', 'foo', 'bar', 'foo', 'bar', 'foo', 'foo'],
'B': ['one', 'one', 'two', 'three', 'two', 'two', 'one', 'three'],
'C': [1, 2, 3, 4, 5, 6, 7, 8],
'D': [10, 20, 30, 40, 50, 60, 70, 80]
})
1. 多重聚合(多个函数、多个字段)
# 对不同的列应用不同的聚合函数
agg_result = df.groupby('A').agg({
'C': ['sum', 'mean', 'max'],
'D': ['min', 'std']
})
输出多层列索引,可使用 .columns 查看
df.columns = ['_'.join(col).strip() for col in df.columns.values]
2. 使用 lambda 自定义聚合函数
df.groupby('group_col')['value'].agg(lambda x: x.max() - x.min())
df.groupby('group_col').agg(
range_of_value=('value', lambda x: x.max() - x.min())
)
# 定义自定义聚合函数
def range_func(x):
return x.max() - x.min()
custom_agg = df.groupby('A').agg({
'C': ['sum', range_func],
'D': lambda x: x.quantile(0.75) - x.quantile(0.25)
})
3. 按多个列分组
df.groupby(['col1', 'col2'])['value'].mean().unstack()
4. 分组后过滤组(filter)
# 过滤掉组内元素少于3的组
filtered = df.groupby('A').filter(lambda x: len(x) >= 3)
# 过滤掉组内C列总和小于10的组
filtered2 = df.groupby('A').filter(lambda x: x['C'].sum() > 10)
5. 分组后变换(transform)
返回与原 DataFrame 相同长度的结果(常用于归一化、标准化)
# 使用transform保持原始DataFrame形状
df['C_mean'] = df.groupby('A')['C'].transform('mean')
df['D_rank'] = df.groupby('A')['D'].transform(lambda x: x.rank(ascending=False))
df['normalized'] = df.groupby('group_col')['value'].transform(lambda x: (x - x.mean()) / x.std())
6. 分组后 apply 自定义函数
def custom_func(group):
group['ratio'] = group['sales'] / group['sales'].sum()
return group
df.groupby('region').apply(custom_func)
7. groupby + rolling 滚动窗口
df['rolling_avg'] = df.groupby('user_id')['value'].rolling(3).mean().reset_index(level=0, drop=True)
8. 分组 rank / diff / shift 等时序操作
df['rank'] = df.groupby('group')['score'].rank(ascending=False)
df['change'] = df.groupby('product')['price'].diff()
9. 自定义 groupby + map 聚合结果到原表
avg_map = df.groupby('group_col')['value'].mean()
df['group_avg'] = df['group_col'].map(avg_map)
10. as_index=False 控制 index 行为
df.groupby('group_col', as_index=False)['value'].sum()
11. 时间序列分组
# 创建时间序列数据
time_df = pd.DataFrame({
'date': pd.date_range('2023-01-01', periods=100, freq='D'),
'value': np.random.randn(100),
'category': np.random.choice(['A', 'B', 'C'], 100)
})
# 按时间频率分组
time_grouped = time_df.groupby([
pd.Grouper(key='date', freq='M'),
'category'
]).sum()
12. cut+groupby
import pandas as pd
df = pd.DataFrame({
'name': ['Alice', 'Bob', 'Charlie', 'David', 'Eva'],
'age': [15, 25, 35, 45, 55]
})
# 分箱
bins = [0, 18, 30, 50, 100]
labels = ['0-18', '19-30', '31-50', '51+']
df['age_group'] = pd.cut(df['age'], bins=bins, labels=labels)
# 分组统计人数
result = df.groupby('age_group')['name'].count()
print(result)
使用 qcut 进行等频分箱
df['quantile_group'] = pd.qcut(df['age'], q=4)
df.groupby('quantile_group').mean()
13. groupby +resample
groupby + resample 是处理 带有分组(如不同地区、用户、类别)+ 时间序列聚合 的常见组合操作,适用于如下场景:
“对每个用户的时间序列数据按季度重采样,计算每季度的总量/平均值/最大值等。”
✅ 基本前提
- 数据必须包含一个时间列(通常转为索引);
- 分组字段(如
user_id,category等)在列中; - 重采样只能在时间索引下操作,所以要用
.set_index('date'); - 所以推荐的写法是:先分组再重采样,或者使用
df.groupby([group_col, pd.Grouper(...)])。
✅ 示例:groupby + resample(按用户+季度统计)
import pandas as pd
import numpy as np
# 1. 构造样本数据
date_rng = pd.date_range(start='2023-01-01', end='2024-12-31', freq='7D')
np.random.seed(0)
df = pd.DataFrame({
'user': np.random.choice(['Alice', 'Bob', 'Charlie'], size=len(date_rng)),
'value': np.random.randint(10, 100, size=len(date_rng)),
'date': date_rng
})
# 2. 设置时间索引
df = df.set_index('date')
# 3. groupby + resample(按用户 + 季度统计平均值)
result = df.groupby('user').resample('Q').mean()
# 可选:重置索引
result = result.reset_index()
print(result.head())
🔁 等价写法:使用 pd.Grouper
更推荐这种写法,清晰且兼容性更好:
result = df.groupby([ 'user', pd.Grouper(freq='Q') ])['value'].mean().reset_index()
pd.Grouper
pd.Grouper是 Pandas 中一个强大的工具,常用于配合groupby()实现 基于时间频率的分组聚合,尤其适合:✅ 时间列不是索引时也能方便地分组
✅ 多维分组(如 "user + 月")
✅ 灵活控制频率、起止对齐、闭区间等✅ 基本语法
pd.Grouper(key='时间列', freq='频率', closed='right', label='right')
参数 作用说明 key指定用于分组的时间列名 freq采样频率,例如 'M'、'Q'、'Y' 等 closed时间区间的闭合方向(left/right) label分组标签取区间左侧还是右侧 origin起始点(如 'start_day'、'epoch') ✅ 示例 1:按月统计每个用户的平均值
import pandas as pd import numpy as np # 构造数据 date_rng = pd.date_range(start='2024-01-01', end='2024-06-30', freq='D') df = pd.DataFrame({ 'user': np.random.choice(['Alice', 'Bob'], size=len(date_rng)), 'value': np.random.randint(1, 100, size=len(date_rng)), 'date': date_rng }) # 按 user + 月分组 result = df.groupby([ 'user', pd.Grouper(key='date', freq='M') ]).mean().reset_index() print(result.head())✅ 示例 2:按季度统计总和 + 最大值
# 多聚合 df.groupby([ 'user', pd.Grouper(key='date', freq='Q') ])['value'].agg(['sum', 'max']).reset_index()✅ 示例 3:时间列不是索引也能用
df = df.reset_index(drop=True) # 时间列不做索引 result = df.groupby([ pd.Grouper(key='date', freq='W') ])['value'].sum()🧠 实际应用场景
业务场景 示例代码 每月用户活跃数 df.groupby([pd.Grouper(key='time', freq='M'), 'user']).count()每季度每品类平均销售额 df.groupby(['category', pd.Grouper(key='date', freq='Q')]).mean()每日每地区最大温度 df.groupby(['region', pd.Grouper(key='timestamp', freq='D')]).max()🎯 小结:与
resample()区别
对比点 resample()groupby() + pd.Grouper索引要求 只能在 DatetimeIndex 上操作 不要求时间列是索引 是否支持多组 否(只能按时间) ✅ 支持多个分组键(如 user + 月) 用法复杂度 简洁,但局限性大 稍复杂,但更灵活 ✅ 输出样例
user date value 0 Alice 2023-03-31 48.666667 1 Alice 2023-06-30 55.500000 2 Alice 2023-09-30 51.333333 3 Alice 2023-12-31 58.000000 4 Alice 2024-03-31 52.500000🧠 拓展用法
用法 示例 多聚合 agg(['sum', 'mean'])按月分组 pd.Grouper(freq='M')按小时分组 pd.Grouper(freq='H')分组 + 插值 .groupby([...]).apply(lambda g: g.interpolate())📌 注意事项
- 必须确保
index是时间类型:DatetimeIndex。groupby().resample()会创建 MultiIndex(group + 时间)。- 如果后续还要做
plot()、join()等操作,建议用.reset_index()
进阶技巧:named aggregation
df.groupby('category').agg(
avg_price=('price', 'mean'),
total_sales=('sales', 'sum'),
max_discount=('discount', 'max')
)
模拟场景
1、分组后提取每组前两名
方法一:按组排序 + groupby().head(n)
df_sorted = df.sort_values(by=['group', 'value'], ascending=[True, False])
top2 = df_sorted.groupby('group').head(2)
方法二:加排名 + 过滤(保留排名 ≤ 2)
df['rank'] = df.groupby('group')['value'].rank(method='first', ascending=False)
top2 = df[df['rank'] <= 2]
方法三:nlargest() 每组取 Top N(值列唯一时推荐)
top2 = (
df.groupby('group', group_keys=False)
.apply(lambda g: g.nlargest(2, 'value'))
)
2、按用户滚动计算 7 天平均值
import pandas as pd
df = pd.DataFrame({
'user': ['A'] * 5 + ['B'] * 5,
'date': pd.date_range('2024-01-01', periods=5).tolist() * 2,
'value': [10, 15, 20, 25, 30, 5, 8, 9, 6, 7]
})
df = df.sort_values(['user', 'date'])
滚动均值:
df['rolling_7d_avg'] = (
df.groupby('user')
.rolling('7D', on='date')['value']
.mean()
.reset_index(level=0, drop=True)
)
如果你的数据是等间隔(日、小时)而不是 datetime 类型:
df['rolling_avg'] = df.groupby('user')['value'].transform(lambda x: x.rolling(3).mean())
3、前两名写入新列(横向展开)
top2_wide = (
df.sort_values(['class', 'score'], ascending=[True, False])
.groupby('class')
.head(2)
.reset_index(drop=True)
)
# 添加序号
top2_wide['rank'] = top2_wide.groupby('class').cumcount() + 1
# 横向展开(pivot)
wide = top2_wide.pivot(index='class', columns='rank', values=['student', 'score'])
wide.columns = [f"{a}_top{b}" for a, b in wide.columns]
wide = wide.reset_index()
输出:
class student_top1 student_top2 score_top1 score_top2
0 A Jerry Tom 92 88
1 B Rose Jack 90 85
更多推荐
所有评论(0)