分类数据在分组、聚合、透视等统计场景中有天然优势。本节介绍相关用法。
1. 频次统计:value_counts()
s = pd.Series(['a', 'b', 'a', 'c', 'b']).astype('category')
s.value_counts()
# a 2
# b 2
# c 1
# Name: count, dtype: int64参数
| 参数 | 说明 |
|---|---|
normalize | 是否返回比例(默认 False) |
sort | 是否按频次排序 |
ascending | 是否升序 |
dropna | 是否排除缺失值(默认 True) |
s.value_counts(normalize=True) # 比例
s.value_counts(ascending=True) # 升序分类数据的 value_counts 默认 包含所有类别(即使频次为 0),与普通列不同。
2. 分组聚合:groupby()
分类列可以直接作为 groupby 键:
df = pd.DataFrame({
'城市': ['北京', '上海', '北京', '广州', '上海'],
'销售额': [100, 200, 150, 300, 250]
})
df['城市'] = df['城市'].astype('category')
# 分组求和
df.groupby('城市')['销售额'].sum()
# 城市
# 北京 250
# 上海 450
# 广州 300
# Name: 销售额, dtype: int64groupby + 有序分类
df['等级'] = pd.Categorical(['高', '低', '中', '高', '低'],
categories=['低', '中', '高'], ordered=True)
# 按等级分组并按顺序输出
df.groupby('等级')['销售额'].sum()
# 等级
# 低 450
# 中 150
# 高 400
# Name: 销售额, dtype: int64有序分类在 groupby 后会自动按类别顺序排列,无需额外排序。
observed 参数(重要)
pandas 2.x 中,分组时若 categorical 列存在未观察到的类别,默认 observed=False 会包含这些类别:
df['城市'] = df['城市'].cat.add_categories(['深圳'])
df.groupby('城市')['销售额'].sum(observed=False) # 包含深圳(0 销售额)
df.groupby('城市')['销售额'].sum(observed=True) # 只包含出现的类别| observed | 行为 |
|---|---|
False(默认) | 包含所有类别(含空组) |
True | 只包含实际出现的类别 |
注意
pandas 2.x 中
observed参数在groupby/pivot_table/crosstab中默认变为False,可能导致输出中出现空组。
3. 透视表:pivot_table()
df = pd.DataFrame({
'地区': ['华东', '华北', '华东', '华北'],
'季度': ['Q1', 'Q1', 'Q2', 'Q2'],
'销售额': [100, 200, 150, 180]
})
df['地区'] = df['地区'].astype('category')
df['季度'] = df['季度'].astype('category')
pd.pivot_table(
df,
values='销售额',
index='地区',
columns='季度',
aggfunc='sum',
fill_value=0
)4. 交叉表:crosstab()
import pandas as pd
df = pd.DataFrame({
'性别': ['男', '女', '男', '女'],
'城市': ['北京', '上海', '北京', '广州']
})
df['性别'] = df['性别'].astype('category')
df['城市'] = df['城市'].astype('category')
pd.crosstab(df['性别'], df['城市'])
# 城市 北京 上海 广州
# 性别
# 男 2 0 0
# 女 0 1 1crosstab 同样有 observed 参数。
5. 描述性统计
s = pd.Series(['低', '中', '高', '中']).astype(
pd.CategoricalDtype(categories=['低', '中', '高'], ordered=True)
)
s.describe()
# count 4
# unique 3
# top 中
# freq 2
# Name: count, dtype: object
# 有序分类支持 min/max
s.min() # '低'
s.max() # '高'注意
无序分类不支持
min()/max(),会抛出TypeError。
6. 与 get_dummies 结合
直接生成哑变量
df = pd.DataFrame({'城市': ['北京', '上海', '北京', '广州']})
df['城市'] = df['城市'].astype('category')
pd.get_dummies(df['城市'], prefix='城市')
# 城市_北京 城市_广州 城市_上海
# 0 1 0 0
# 1 0 0 1
# 2 1 0 0
# 3 0 1 0对 DataFrame 操作
df = pd.DataFrame({
'城市': ['北京', '上海'],
'级别': ['A', 'B']
})
df = df.astype('category')
pd.get_dummies(df, columns=['城市', '级别'])分类数据在 get_dummies 中的优势
- 类别顺序固定,哑变量列顺序稳定
- 未出现的类别也能生成列,保证结构一致
7. 与排名 / 分位数结合
scores = pd.Series([88, 92, 70, 60, 99])
# 用 qcut 生成等级
grades = pd.qcut(scores, q=3, labels=['C', 'B', 'A'])
print(grades)
# 0 B
# 1 A
# 2 C
# 3 C
# 4 A
# Categories (3, object): ['C' < 'B' < 'A']
# 与原始数据组合并分组统计
df = pd.DataFrame({'分数': scores, '等级': grades})
df.groupby('等级')['分数'].agg(['mean', 'count'])小结
value_counts()包含全部类别groupby支持有序分类自动排序observed参数控制是否显示空组pivot_table/crosstab适合多维统计ordered分类支持 min/max 等比较get_dummies生成稳定哑变量