概述

除了聚合、变换、过滤、应用外,GroupBy 对象还内置了许多快捷方法,用于查看每组头部/尾部数据、抽样、生成描述统计,以及对每组执行时间重采样与滚动窗口计算。


8.1 head()、tail()、sample()

这些方法返回每组前 n 行 / 后 n 行 / 随机抽样 n 行,结果仍为原始行数据。

import pandas as pd
 
df = pd.DataFrame({
    "部门": ["A", "A", "A", "A", "B", "B", "B"],
    "员工": ["张三", "李四", "王五", "赵六", "孙七", "周八", "吴九"],
    "薪资": [8000, 9500, 8200, 9000, 12000, 11000, 13000],
})
 
# 每组前 2 行
df.groupby("部门").head(2)
 
# 每组后 1 行
df.groupby("部门").tail(1)
 
# 每组随机抽取 1 行
df.groupby("部门").sample(n=1, random_state=42)
 
# 每组按比例抽样
df.groupby("部门").sample(frac=0.5, random_state=42)
 
# head/tail 与 apply 对比
df.groupby("部门").apply(lambda x: x.head(2))

注意

GroupBy.head(2) 返回的索引可能不连续,需要时使用 .reset_index(drop=True)。


8.2 describe()

describe() 按组生成描述统计量:count、mean、std、min、25%、50%、75%、max。

# 单列描述统计
df.groupby("部门")["薪资"].describe()
 
# 多列描述统计
df.groupby("部门")[["薪资"]].describe()
 
# 传递百分位数
df.groupby("部门")["薪资"].describe(percentiles=[0.1, 0.9])
 
# 包含所有列(含 object 列)
df.groupby("部门").describe(include="all")

输出为多层列索引,可通过 .loc 或 xs 提取:

desc = df.groupby("部门")["薪资"].describe()
desc.loc["A", "mean"]

8.3 分组内 resample()

先分组,再对每组执行 时间重采样,用于「按部门分别统计月度/季度销量」等场景。

import numpy as np
 
# 模拟销售数据
sales = pd.DataFrame({
    "部门": ["A", "A", "A", "A", "B", "B", "B", "B"],
    "日期": pd.date_range("2024-01-01", periods=8, freq="D"),
    "金额": np.random.randint(100, 500, 8),
})
 
# 必须先设置时间索引
sales = sales.set_index("日期")
 
# 按部门分组后按月重采样求和
sales.groupby("部门")["金额"].resample("M").sum()
 
# 更常见的写法:分组 + Grouper 重采样
sales.groupby(["部门", pd.Grouper(freq="M")])["金额"].sum()
 
# 重采样 + 多种聚合
sales.groupby("部门")["金额"].resample("M").agg(["sum", "mean", "count"])
 
# 分组后 resample 并重置索引
sales.groupby("部门")["金额"].resample("M").sum().reset_index()

分组 + 重采样的索引顺序

结果为 MultiIndex:第一层是分组键,第二层是时间索引,可用 .reset_index() 展平。


8.4 分组内 rolling()

先分组,再对每组执行 滚动窗口计算,用于「各部门滚动 3 日均值」等场景。

# 模拟每日各部门销量
daily = pd.DataFrame({
    "部门": ["A"] * 5 + ["B"] * 5,
    "日期": list(pd.date_range("2024-01-01", periods=5, freq="D")) * 2,
    "销量": [10, 15, 20, 25, 30, 100, 90, 80, 70, 60],
})
 
daily = daily.set_index("日期")
 
# 每组滚动 3 日均值
daily.groupby("部门")["销量"].rolling(3).mean()
 
# rolling + agg 多统计量
daily.groupby("部门")["销量"].rolling(3).agg(["mean", "sum", "max"])
 
# 滚动窗口 + 重置索引
daily.groupby("部门")["销量"].rolling(3).mean().reset_index()
 
# 指定 min_periods
daily.groupby("部门")["销量"].rolling(3, min_periods=1).mean()
 
# 与 transform 结合(将滚动均值广播回原表)
daily["滚动3日均值"] = daily.groupby("部门")["销量"].rolling(3, min_periods=1).mean().reset_index(level=0, drop=True)

注意

分组 + rolling() 返回带 MultiIndex 的 Series,通常需要 reset_index(level=0, drop=True) 或 .droplevel(0) 后再赋回原 DataFrame。


相关笔记