概述
本节汇总
groupby中的特殊情况:时间重采样分组、分类数据observed参数、缺失值dropna参数,以及分组后产生多索引结果的处理方式。
9.1 时间重采样
通过
pd.Grouper(freq=...)可以在groupby中直接按时间频率分组,避免先set_index再resample。
import pandas as pd
import numpy as np
# 示例:每日交易数据
df = pd.DataFrame({
"日期": pd.date_range("2024-01-01", periods=30, freq="D"),
"门店": ["A", "B"] * 15,
"销售额": np.random.randint(500, 2000, 30),
})
# 按周分组求和(freq="W" 默认按周日结束)
df.groupby(pd.Grouper(key="日期", freq="W"))["销售额"].sum()
# 按月分组
df.groupby(pd.Grouper(key="日期", freq="M"))["销售额"].sum()
# 按季度分组
df.groupby(pd.Grouper(key="日期", freq="Q"))["销售额"].sum()
# 门店 + 月度双分组
df.groupby(["门店", pd.Grouper(key="日期", freq="M")])["销售额"].agg(["sum", "mean"])
# 使用 level 时间索引分组
df_ts = df.set_index("日期")
df_ts.groupby([pd.Grouper(freq="M"), "门店"])["销售额"].sum()常用频率字符串:
D(天)、W(周)、M(月)、Q(季度)、Y(年)、H(小时)、T/min(分钟)。
9.2 分类分组 observed
当分组键为
Categorical类型时,observed决定是否包含未出现的类别:
observed | 行为 |
|---|---|
False(默认) | 结果包含全部预定义类别(即使某类没有数据),可用于保持类别完整性 |
True | 仅包含实际观测到的类别,结果更精简 |
# 准备分类数据
cat_df = pd.DataFrame({
"等级": pd.Categorical(
["高", "中", "高"],
categories=["低", "中", "高"], # "低" 从未出现
ordered=True,
),
"数量": [10, 20, 30],
})
# observed=False(默认):包含 "低",计数为 0
cat_df.groupby("等级", observed=False)["数量"].agg(["sum", "count"])
# observed=True:只显示实际类别
cat_df.groupby("等级", observed=True)["数量"].agg(["sum", "count"])pandas 2.x 行为提示
早期版本
observed默认False;若希望分组结果更小更快,建议显式指定observed=True。此外,groupby(..., observed=False)时,dropna=False可同时保留缺失类别与NaN组。
9.3 缺失值 dropna
groupby(dropna=True/False)控制分组键为缺失值的组是否保留。
df_nan = pd.DataFrame({
"部门": ["A", None, "B", None, "C"],
"销售额": [100, 200, 300, 400, 500],
})
# dropna=True(默认):丢弃 NaN 组
df_nan.groupby("部门", dropna=True)["销售额"].sum()
# dropna=False:保留 NaN 组(组名显示为 NaN)
df_nan.groupby("部门", dropna=False)["销售额"].sum()补充说明
- 多列分组时,只要任一列分组键为
NaN,该行即视为缺失组;dropna=False在数据审计与缺失值分析中很有用;- 对分类数据,
dropna=False时缺失值类别会被单独列出。
# 多列分组 + dropna=False
df_nan2 = pd.DataFrame({
"部门": ["A", None, "A"],
"地区": ["北", "南", None],
"销售额": [1, 2, 3],
})
df_nan2.groupby(["部门", "地区"], dropna=False)["销售额"].sum()9.4 多索引结果
多列分组后,结果索引会变为
MultiIndex;可使用reset_index()、.droplevel()、.swaplevel()、.sort_index()等处理。
df = pd.DataFrame({
"年份": [2023, 2023, 2024, 2024, 2024],
"季度": ["Q1", "Q2", "Q1", "Q2", "Q3"],
"销售额": [100, 150, 200, 250, 300],
})
# 多列分组产生 MultiIndex
result = df.groupby(["年份", "季度"])["销售额"].sum()
# 年份 季度
# 2023 Q1 100
# Q2 150
# 2024 Q1 200
# Q2 250
# Q3 300
# Name: 销售额, dtype: int64
# 展平为普通 DataFrame
result.reset_index()
# 保留分组键为列(as_index=False)
df.groupby(["年份", "季度"], as_index=False)["销售额"].sum()
# 删除某一层级
result.droplevel("季度")
# 交换层级顺序
result.swaplevel("季度", "年份").sort_index()
# 多索引列(多列多重聚合)
df.groupby("年份")["销售额"].agg(["sum", "mean", "max"])
# 输出列也为 MultiIndex常用处理模式
# 多索引结果 → 整洁 DataFrame df.groupby(["年份", "季度"]).agg( 总销售额=("销售额", "sum"), 平均销售额=("销售额", "mean"), ).reset_index()
相关笔记
- 上一节:分组与聚合-8-分组后操作
- 章节总览:分组与聚合 章节总览
- 延伸:九、日期时间与时间序列、八、分类数据