分组聚合
分组聚合(GroupBy)是数据分析中最强大的功能之一,允许我们将数据按照某个标准分组,然后对每个组进行计算。本章将详细介绍 Pandas 的分组聚合功能。
分组聚合概述
分组聚合的核心思想类似于 SQL 中的 GROUP BY 语句:
基础 groupby 操作
创建示例数据
import pandas as pd
import numpy as np
# 创建销售数据
sales = pd.DataFrame({
'date': pd.date_range('2024-01-01', periods=20),
'product': np.random.choice(['手机', '电脑', '平板'], 20),
'region': np.random.choice(['北京', '上海', '广州'], 20),
'salesperson': np.random.choice(['张三', '李四', '王五'], 20),
'quantity': np.random.randint(1, 20, 20),
'price': np.random.randint(1000, 8000, 20)
})
# 计算每笔订单总额
sales['total'] = sales['quantity'] * sales['price']
print(sales.head(10))
基本分组
# 按单列分组
grouped = sales.groupby('product')
print(type(grouped))
# <class 'pandas.core.groupby.generic.DataFrameGroupBy'>
# 遍历每个组
for name, group in grouped:
print(f"\n产品: {name}")
print(group[['region', 'quantity', 'total']].head(2))
分组并计算
# 按产品分组,计算销售总额
product_sales = sales.groupby('product')['total'].sum()
print(product_sales)
# product
# 手机 98500
# 电脑 75600
# 平板 62300
# Name: total, dtype: int64
# 按产品和地区分组
region_product = sales.groupby(['product', 'region'])['total'].sum()
print(region_product)
常用聚合函数
内置聚合函数
# 单一聚合
print(sales.groupby('product')['total'].agg('sum')) # 求和
print(sales.groupby('product')['total'].agg('mean')) # 平均值
print(sales.groupby('product')['total'].agg('median')) # 中位数
print(sales.groupby('product')['total'].agg('std')) # 标准差
print(sales.groupby('product')['total'].agg('min')) # 最小值
print(sales.groupby('product')['total'].agg('max')) # 最大值
print(sales.groupby('product')['total'].agg('count')) # 计数
多个聚合函数
# 同时使用多个聚合函数
result = sales.groupby('product')['total'].agg(['sum', 'mean', 'count', 'min', 'max'])
print(result)
# 重命名聚合列
result = sales.groupby('product')['total'].agg(
total_sum=('sum'),
total_mean=('mean'),
order_count=('count')
)
print(result)
对多列同时聚合
# 对不同列使用不同聚合
result = sales.groupby('product').agg({
'quantity': 'sum',
'total': 'mean',
'price': 'max'
})
print(result)
高级分组操作
分组后的过滤
# 筛选销售额超过平均值的组
def filter_func(group):
return group['total'].mean() > 70000
filtered = sales.groupby('product').filter(filter_func)
print(filtered['product'].unique())
分组后的变换
# 计算每个组的累计占比
sales['group_pct'] = sales.groupby('product')['total'].transform(
lambda x: x / x.sum() * 100
)
print(sales[['product', 'total', 'group_pct']].head(10))
# 计算每个组的平均值(填充到每行)
sales['group_mean'] = sales.groupby('product')['total'].transform('mean')
print(sales[['product', 'total', 'group_mean']].head(10))
分组后的apply
# 自定义聚合函数
def my_agg(x):
return pd.Series({
'total_sum': x.sum(),
'avg': x.mean(),
'range': x.max() - x.min()
})
result = sales.groupby('product')['total'].apply(my_agg)
print(result)
多级分组
按多个列分组
# 按产品和地区分组
grouped = sales.groupby(['product', 'region'])
print(grouped['total'].sum())
# 多级索引 DataFrame 转换为普通 DataFrame
result = grouped.sum().reset_index()
print(result)
多级索引操作
# 创建多级索引
grouped = sales.groupby(['product', 'region']).sum()
# 选择特定分组
print(grouped.loc['手机']) # 选择第一级索引
print(grouped.loc[('手机', '北京')]) # 选择特定组合
print(grouped.xs('北京', level='region')) # 跨级选择
数据透视表
透视表是分组聚合的可视化表达。
创建透视表
# 基本透视表
pivot = pd.pivot_table(sales,
values='total',
index='product',
columns='region',
aggfunc='sum',
fill_value=0)
print(pivot)
# 多个聚合函数
pivot = pd.pivot_table(sales,
values=['total', 'quantity'],
index='product',
columns='region',
aggfunc={'total': 'sum', 'quantity': 'mean'},
fill_value=0)
print(pivot)
透视表选项
# 添加行列汇总
pivot = pd.pivot_table(sales,
values='total',
index='product',
columns='region',
aggfunc='sum',
fill_value=0,
margins=True, # 添加汇总
margins_name='总计') # 汇总名称
# 使用不同聚合函数
pivot = pd.pivot_table(sales,
values='total',
index='product',
columns='region',
aggfunc='mean',
fill_value=0)
交叉表
交叉表是一种特殊的透视表,用于计算频率或计数。
基本交叉表
# 创建交叉表
crosstab = pd.crosstab(sales['product'], sales['region'])
print(crosstab)
# 多行多列
crosstab = pd.crosstab(sales['product'],
[sales['region'], sales['salesperson']])
print(crosstab)
交叉表选项
# 添加边际
crosstab = pd.crosstab(sales['product'],
sales['region'],
margins=True)
# 使用值作为聚合
crosstab = pd.crosstab(sales['product'],
sales['region'],
values=sales['total'],
aggfunc='sum')
分组实战示例
示例1:销售数据分析
# 按销售员分析业绩
salesperson_stats = sales.groupby('salesperson').agg({
'total': ['sum', 'mean', 'count'],
'quantity': 'sum',
'price': 'mean'
}).round(2)
# 重命名列
salesperson_stats.columns = ['总销售额', '平均订单额', '订单数', '总销量', '平均单价']
salesperson_stats = salesperson_stats.sort_values('总销售额', ascending=False)
print("销售员业绩排名:")
print(salesperson_stats)
# 按月分析趋势
sales['month'] = sales['date'].dt.to_period('M')
monthly_stats = sales.groupby('month')['total'].sum()
print("\n月度销售趋势:")
print(monthly_stats)
示例2:用户留存分析
# 创建用户数据
user_data = pd.DataFrame({
'user_id': [1, 1, 1, 2, 2, 3, 3, 3, 3],
'month': ['2024-01', '2024-02', '2024-03'] * 3,
'activity': [10, 15, 8, 5, 12, 20, 18, 25, 22]
})
# 用户首次活跃月份
first_month = user_data.groupby('user_id')['month'].min().reset_index()
first_month.columns = ['user_id', 'first_month']
# 合并并计算留存月份
user_data = user_data.merge(first_month, on='user_id')
user_data['months_since_start'] = (
pd.to_datetime(user_data['month']).dt.to_period('M').astype(int) -
pd.to_datetime(user_data['first_month']).dt.to_period('M').astype(int)
)
# 计算每月留存
retention = user_data.groupby('months_since_start')['user_id'].nunique()
print("用户留存分析:")
print(retention)
示例3:产品分析
# 产品维度分析
product_analysis = sales.groupby('product').agg({
'total': ['sum', 'mean'],
'quantity': 'sum',
'price': ['min', 'max', 'mean'],
'region': 'nunique',
'salesperson': 'nunique'
}).round(2)
product_analysis.columns = [
'销售额总计', '平均订单额', '总销量',
'最低单价', '最高单价', '平均单价',
'销售区域数', '销售员数'
]
# 计算客单价
product_analysis['客单价'] = product_analysis['销售额总计'] / product_analysis['总销量']
print("产品分析:")
print(product_analysis.sort_values('销售额总计', ascending=False))
示例4:RFM 分析
# 模拟电商用户交易数据
np.random.seed(42)
rfm_data = pd.DataFrame({
'user_id': range(1, 101),
'recency': np.random.randint(1, 90, 100), # 距离最后交易天数
'frequency': np.random.randint(1, 30, 100), # 交易次数
'monetary': np.random.randint(500, 50000, 100) # 总消费金额
})
# 计算 RFM 分数
# 使用分位数进行打分
rfm_data['R_score'] = pd.qcut(rfm_data['recency'], q=5, labels=[5, 4, 3, 2, 1], duplicates='drop')
rfm_data['F_score'] = pd.qcut(rfm_data['frequency'].rank(method='first'), q=5, labels=[1, 2, 3, 4, 5])
rfm_data['M_score'] = pd.qcut(rfm_data['monetary'].rank(method='first'), q=5, labels=[1, 2, 3, 4, 5])
# 转换为数值
rfm_data['R_score'] = rfm_data['R_score'].astype(int)
rfm_data['F_score'] = rfm_data['F_score'].astype(int)
rfm_data['M_score'] = rfm_data['M_score'].astype(int)
# 计算总分
rfm_data['RFM_score'] = rfm_data['R_score'] + rfm_data['F_score'] + rfm_data['M_score']
# 用户分群
def segment_user(row):
if row['RFM_score'] >= 13:
return 'VIP客户'
elif row['RFM_score'] >= 9:
return '潜力客户'
elif row['RFM_score'] >= 6:
return '普通客户'
else:
return '流失风险'
rfm_data['segment'] = rfm_data.apply(segment_user, axis=1)
# 统计各分群
segment_stats = rfm_data.groupby('segment').agg({
'user_id': 'count',
'recency': 'mean',
'frequency': 'mean',
'monetary': 'mean'
}).round(2)
segment_stats.columns = ['用户数', '平均R', '平均F', '平均M']
print("用户分群统计:")
print(segment_stats.sort_values('用户数', ascending=False))
窗口函数
滚动窗口
# 创建时间序列数据
ts_data = pd.DataFrame({
'date': pd.date_range('2024-01-01', periods=10),
'value': [100, 110, 105, 115, 120, 118, 125, 130, 128, 135]
})
# 滚动窗口(最近3个值的平均)
ts_data['rolling_mean_3'] = ts_data['value'].rolling(window=3).mean()
# 滚动窗口求和
ts_data['rolling_sum_3'] = ts_data['value'].rolling(window=3).sum()
# 滚动窗口最大/最小值
ts_data['rolling_max_3'] = ts_data['value'].rolling(window=3).max()
ts_data['rolling_min_3'] = ts_data['value'].rolling(window=3).min()
print(ts_data)
扩展窗口
# 扩展窗口(从开始到当前的累计值)
ts_data['expanding_mean'] = ts_data['value'].expanding().mean()
ts_data['expanding_sum'] = ts_data['value'].expanding().sum()
时间偏移
# 滞后(shift)
ts_data['lag_1'] = ts_data['value'].shift(1) # 前一天的值
ts_data['lag_2'] = ts_data['value'].shift(2) # 前两天的值
# 领先(diff)
ts_data['diff_1'] = ts_data['value'].diff(1) # 与前一天的变化
小结
本章我们学习了:
- 基础 groupby:创建分组、遍历组、基础聚合
- 聚合函数:内置函数、多个聚合、重命名
- 高级分组:过滤、变换、apply
- 多级分组:多列分组、多级索引操作
- 透视表:创建透视表、多种聚合选项
- 交叉表:频率统计、交叉表选项
- 实战案例:销售分析、留存分析、产品分析、RFM分析
- 窗口函数:滚动窗口、扩展窗口、时间偏移
练习
- 对销售数据进行多维度分析(产品、地区、时间)
- 创建一个完整的 RFM 用户分群
- 使用透视表分析数据趋势
参考资源
下一步
分组聚合是数据分析的核心技能。继续练习这些技术,并尝试应用到实际数据分析项目中!