目录

1. 多重聚合(多个函数、多个字段)

2. 使用 lambda 自定义聚合函数

3. 按多个列分组

4. 分组后过滤组(filter)

5. 分组后变换(transform)

6. 分组后 apply 自定义函数

7. groupby + rolling 滚动窗口

8. 分组 rank / diff / shift 等时序操作

9. 自定义 groupby + map 聚合结果到原表

10. as_index=False 控制 index 行为

11. 时间序列分组

12. cut+groupby

13. groupby +resample

✅ 基本前提

✅ 示例:groupby + resample(按用户+季度统计)

🔁 等价写法:使用 pd.Grouper

pd.Grouper

✅ 基本语法

✅ 示例 1:按月统计每个用户的平均值

✅ 示例 2:按季度统计总和 + 最大值

✅ 示例 3:时间列不是索引也能用

🧠 实际应用场景

🎯 小结:与 resample() 区别

✅ 输出样例

🧠 拓展用法

📌 注意事项

进阶技巧:named aggregation

1、分组后提取每组前两名

方法一:按组排序 + groupby().head(n)

方法二:加排名 + 过滤(保留排名 ≤ 2)

方法三:nlargest() 每组取 Top N(值列唯一时推荐)

2、按用户滚动计算 7 天平均值

3、前两名写入新列(横向展开)


 

 

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
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐