在数据分析中,我们常通过“分组(groupby) + 聚合(aggregate)”来比较不同类别的统计特征。
例如:比较不同地区、性别、职业的平均成绩、数量、占比等。
groupby() 的常见用法:单列/多列分组、as_index、size vs count、nunique。agg() 执行多指标聚合,包含命名聚合与按列指定函数。value_counts()、crosstab()、pivot_table() 做频次与透视分析。df.groupby('列名') 或 df.groupby(['列1','列2'])mean(), sum(), count(), max(), min(), median(), std(), nunique()df.groupby('地区')['成绩'].agg(['mean','max','min'])df.groupby('地区').agg({'成绩':['mean','max'], '姓名':'nunique'})df.groupby('地区').agg(
平均分=('成绩','mean'),
最高分=('成绩','max'),
人数=('姓名','count')
)
size() vs count():size() 统计组内行数(不管是否缺失);count() 统计非缺失值的数量(按列)。as_index:groupby(..., as_index=False) 可让分组键变成普通列,便于后续合并与展示。value_counts()(一维),pd.crosstab()(二维频次表)。pivot_table(values=..., index=..., columns=..., aggfunc=...)import pandas as pd
# 创建示例数据
data = {
'姓名': ['张三', '李四', '王五', '赵六', '孙七'],
'地区': ['北方', '北方', '南方', '南方', '北方'],
'成绩': [85, 80, 90, 92, 88]
}
df = pd.DataFrame(data)
# 按地区分组,计算平均成绩
mean_scores = df.groupby('地区')['成绩'].mean()
mean_scores
C:\Users\Zhouq\AppData\Roaming\Python\Python39\site-packages\pandas\core\computation\expressions.py:21: UserWarning: Pandas requires version '2.8.4' or newer of 'numexpr' (version '2.8.3' currently installed). from pandas.core.computation.check import NUMEXPR_INSTALLED C:\Users\Zhouq\AppData\Roaming\Python\Python39\site-packages\pandas\core\arrays\masked.py:60: UserWarning: Pandas requires version '1.3.6' or newer of 'bottleneck' (version '1.3.5' currently installed). from pandas.core import (
地区 北方 84.333333 南方 91.000000 Name: 成绩, dtype: float64
# 多种聚合函数
agg_result = df.groupby('地区')['成绩'].agg(['mean', 'max', 'min', 'count', 'std'])
agg_result
| mean | max | min | count | std | |
|---|---|---|---|---|---|
| 地区 | |||||
| 北方 | 84.333333 | 88 | 80 | 3 | 4.041452 |
| 南方 | 91.000000 | 92 | 90 | 2 | 1.414214 |
agg_named = df.groupby('地区', as_index=False).agg(
平均分=('成绩','mean'),
最高分=('成绩','max'),
最低分=('成绩','min'),
人数=('姓名','count')
)
agg_named
| 地区 | 平均分 | 最高分 | 最低分 | 人数 | |
|---|---|---|---|---|---|
| 0 | 北方 | 84.333333 | 88 | 80 | 3 |
| 1 | 南方 | 91.000000 | 92 | 90 | 2 |
df2 = pd.DataFrame({
'姓名': ['张三','李四','王五','赵六','孙七','周八','吴九','郑十'],
'地区': ['北方','北方','南方','南方','北方','南方','南方','北方'],
'性别': ['男','女','男','女','男','男','女','女'],
'成绩': [85,80,90,92,88,76,95,83]
})
group2 = df2.groupby(['地区','性别']).agg(
平均分=('成绩','mean'),
人数=('姓名','count')
)
group2
| 平均分 | 人数 | ||
|---|---|---|---|
| 地区 | 性别 | ||
| 北方 | 女 | 81.5 | 2 |
| 男 | 86.5 | 2 | |
| 南方 | 女 | 93.5 | 2 |
| 男 | 83.0 | 2 |
value_counts + crosstab)¶# 频次与占比
地区频次 = df2['地区'].value_counts()
地区占比 = df2['地区'].value_counts(normalize=True) # 归一化为比例
地区频次, 地区占比
(地区 北方 4 南方 4 Name: count, dtype: int64, 地区 北方 0.5 南方 0.5 Name: proportion, dtype: float64)
# 是否及格(>=60)
df2['是否及格'] = (df2['成绩'] >= 60).astype(int)
# 分组通过率:方式1 - groupby + mean(因为 1/0 的均值就是比例)
pass_rate1 = df2.groupby('地区', as_index=False)['是否及格'].mean().rename(columns={'是否及格':'通过率'})
pass_rate1
| 地区 | 通过率 | |
|---|---|---|
| 0 | 北方 | 1.0 |
| 1 | 南方 | 1.0 |
# 分组通过率:方式2 - crosstab 归一化
table = pd.crosstab(df2['地区'], df2['是否及格'], normalize='index')
table.rename(columns={0:'未及格占比',1:'及格占比'}, inplace=True)
table
| 是否及格 | 及格占比 |
|---|---|
| 地区 | |
| 北方 | 1.0 |
| 南方 | 1.0 |
agg 按列指定不同函数(含去重人数)¶agg_mix = df2.groupby('地区').agg({
'成绩':['mean','median','max'],
'姓名':'nunique' # 去重后的人数(考虑同名时可换用唯一ID)
})
agg_mix
| 成绩 | 姓名 | |||
|---|---|---|---|---|
| mean | median | max | nunique | |
| 地区 | ||||
| 北方 | 84.00 | 84.0 | 88 | 4 |
| 南方 | 88.25 | 91.0 | 95 | 4 |
pivot_table 做“地区 × 性别”的平均成绩透视表¶pivot_avg = pd.pivot_table(
df2,
values='成绩',
index='地区',
columns='性别',
aggfunc='mean'
)
pivot_avg
| 性别 | 女 | 男 |
|---|---|---|
| 地区 | ||
| 北方 | 81.5 | 86.5 |
| 南方 | 93.5 | 83.0 |
import numpy as np
def pass_rate(x):
return (x >= 60).mean()
def iqr(x):
q1 = np.percentile(x, 25)
q3 = np.percentile(x, 75)
return q3 - q1
custom_agg = df2.groupby('地区').agg(
通过率=('成绩', pass_rate),
四分位距=('成绩', iqr)
)
custom_agg
| 通过率 | 四分位距 | |
|---|---|---|
| 地区 | ||
| 北方 | 1.0 | 3.50 |
| 南方 | 1.0 | 6.25 |
| 函数 | 说明 |
|---|---|
mean() |
平均值 |
median() |
中位数 |
mode() |
众数 |
std() |
标准差 |
var() |
方差 |
cov() |
协方差 |
sum() |
总和 |
min(), max() |
最小/最大值 |
groupby() |
数据分组 |
agg() |
多函数聚合 |
value_counts() |
各值出现频次/占比 |
pivot_table() |
数据透视表 |
quantile() |
分位数 |
skew() |
偏态 |
kurtosis()/kurt() |
峰度 |
unique() |
唯一值数组 |
nunique() |
唯一值数量 |
rank() |
排名 |
rolling() |
滑动窗口 |
diff() |
差分 |
pct_change() |
百分比变化 |
count() 会忽略缺失,size() 不忽略;计算占比前先明确分母是谁。 merge 或打印更友好的表,groupby(..., as_index=False) 会更方便。 使用下表(可复制下一格代码生成)完成各小题:
姓名, 地区, 性别, 学科, 成绩
张三, 北方, 男, 语文, 85
李四, 北方, 女, 数学, 80
王五, 南方, 男, 数学, 90
赵六, 南方, 女, 语文, 92
孙七, 北方, 男, 英语, 88
周八, 南方, 男, 语文, 76
吴九, 南方, 女, 英语, 95
郑十, 北方, 女, 英语, 83
题目:
1) 按“地区”计算平均成绩与人数。
2) 按“地区 × 性别”计算平均成绩,并输出为透视表。
3) 计算各地区的通过率(成绩≥60)。
4) 统计每个学科在不同地区的人数频次表与占比表。
# 生成练习数据
import pandas as pd
exam = pd.DataFrame({
'姓名': ['张三','李四','王五','赵六','孙七','周八','吴九','郑十'],
'地区': ['北方','北方','南方','南方','北方','南方','南方','北方'],
'性别': ['男','女','男','女','男','男','女','女'],
'学科': ['语文','数学','数学','语文','英语','语文','英语','英语'],
'成绩': [85,80,90,92,88,76,95,83]
})
exam
| 姓名 | 地区 | 性别 | 学科 | 成绩 | |
|---|---|---|---|---|---|
| 0 | 张三 | 北方 | 男 | 语文 | 85 |
| 1 | 李四 | 北方 | 女 | 数学 | 80 |
| 2 | 王五 | 南方 | 男 | 数学 | 90 |
| 3 | 赵六 | 南方 | 女 | 语文 | 92 |
| 4 | 孙七 | 北方 | 男 | 英语 | 88 |
| 5 | 周八 | 南方 | 男 | 语文 | 76 |
| 6 | 吴九 | 南方 | 女 | 英语 | 95 |
| 7 | 郑十 | 北方 | 女 | 英语 | 83 |
# === 在此作答(你可以新增更多单元格)===
# 1) 地区:平均成绩与人数
# 2) 地区 × 性别:平均成绩(透视表)
# 3) 各地区通过率(成绩>=60)
# 4) 学科在不同地区的人数频次表与占比表
仅供参考,真实研究请结合样本量、置信区间与情境解释。
# 1) 参考答案:地区 平均成绩 + 人数
ans1 = exam.groupby('地区', as_index=False).agg(
平均分=('成绩','mean'),
人数=('姓名','count')
)
ans1
| 地区 | 平均分 | 人数 | |
|---|---|---|---|
| 0 | 北方 | 84.00 | 4 |
| 1 | 南方 | 88.25 | 4 |
# 2) 参考答案:地区 × 性别 平均成绩(透视表)
ans2 = pd.pivot_table(exam, values='成绩', index='地区', columns='性别', aggfunc='mean')
ans2
| 性别 | 女 | 男 |
|---|---|---|
| 地区 | ||
| 北方 | 81.5 | 86.5 |
| 南方 | 93.5 | 83.0 |
# 3) 参考答案:各地区通过率(成绩>=60)
exam['是否及格'] = (exam['成绩'] >= 60).astype(int)
ans3 = exam.groupby('地区', as_index=False)['是否及格'].mean().rename(columns={'是否及格':'通过率'})
ans3
| 地区 | 通过率 | |
|---|---|---|
| 0 | 北方 | 1.0 |
| 1 | 南方 | 1.0 |
# 4) 参考答案:人数频次与占比表(学科×地区)
freq = pd.crosstab(exam['学科'], exam['地区'])
prop = pd.crosstab(exam['学科'], exam['地区'], normalize='index') # 每个学科内的地区占比
freq, prop
(地区 北方 南方 学科 数学 1 1 英语 2 1 语文 1 2, 地区 北方 南方 学科 数学 0.500000 0.500000 英语 0.666667 0.333333 语文 0.333333 0.666667)
groupby + agg 是分类比较的万金油:先分组、再计算、最后合并成可读表。value_counts / crosstab,多维比较用 pivot_table。 下一步:尝试把聚合结果与原表
merge做成报告表,或结合可视化(柱状图/热力图)呈现。