分类数据:Categorical
本教程共 54 篇 · 第 50 篇 · 更新于 2026-08-11 · 约 6 分钟阅读
本节目标:搞懂分类数据 Categorical 的创建与 categories 管理,会做有序分类,并知道它什么时候省内存、和 get_dummies 怎么选。
什么是分类数据
分类变量(categorical variable)的取值有限且固定:性别、地区、评分等级都是。统计软件里这叫”因子”(factor)。pandas 用 category 类型表示它,对应 R 语言的 factor。
分类数据有两个特点:值只能来自一份固定的”类别表”;可以有逻辑顺序,但不能做加减乘除。比如”强烈同意 > 同意 > 反对”,可你不能算”同意 + 反对”。
创建分类列
三种常见方式:
import pandas as pd
# 方式一:构造时指定 dtype
s = pd.Series(["a", "b", "c", "a"], dtype="category")
print(s)
# 0 a
# 1 b
# 2 c
# 3 a
# dtype: category
# Categories (3, str): ['a', 'b', 'c']
# 方式二:已有列转换
df = pd.DataFrame({"城市": ["北京", "上海", "北京", "广州"]})
df["城市"] = df["城市"].astype("category")
# 方式三:pd.cut 切分连续数值,结果自带有序分类
print(pd.cut([1, 5, 9], bins=[0, 3, 6, 10], labels=["低", "中", "高"]))
分类数据内部就两块。一份是去重的 categories(类别表),另一份是一串整数 codes,每个元素指向类别表的哪个位置。显示时还是原来的值,你基本感觉不到编码的存在。
cat 访问器:管理 categories
分类 Series 有个专属访问器 .cat。三个属性打底:
import pandas as pd
s = pd.Series(["one", "two", "four", "-"], dtype="category")
print(s.cat.categories) # Index(['-', 'four', 'one', 'two'], dtype='str')
print(s.cat.ordered) # False,默认无序
print(s.cat.codes) # 每个位置的整数编码
管理类别的方法有六个,按用途分三组:
- 改名:
rename_categories(["一", "二", "四", "横杠"]),只换显示,不动数据。 - 增删:
add_categories(["three"])加类别;remove_categories(["-"])删类别,被删的值变 NaN。 - 整理:
remove_unused_categories()清掉数据里没出现过的类别;set_categories([...])一步把类别表设成指定列表,不在列表里的值变 NaN;reorder_categories([...])只调顺序不改成员。
import pandas as pd
s = pd.Series(["one", "two", "four", "-"], dtype="category")
# 设定标准类别表,"-" 不在其中,自动变 NaN
s2 = s.cat.set_categories(["one", "two", "three", "four"])
print(s2.cat.categories) # Index(['one', 'two', 'three', 'four'], dtype='str')
print(s2)
# 0 one
# 1 two
# 2 four
# 3 NaN
set_categories 是报表场景的标配:把”可能的取值”固定下来,即使某个月没数据,统计结果里也留着一行。
有序分类:逻辑顺序优先
默认分类是无序的,排序按类别表顺序来。“one、two、three”按字典序排是 one、three、two,逻辑顺序却是 one、two、three。想让排序、min、max 都按逻辑顺序走,用 CategoricalDtype 指定:
import pandas as pd
from pandas.api.types import CategoricalDtype
# 指定顺序的类别类型
等级 = CategoricalDtype(["低", "中", "高"], ordered=True)
s = pd.Series(["高", "低", "中", "低"], dtype=等级)
print(s.sort_values())
# 1 低
# 3 低
# 2 中
# 0 高
print(s.min(), s.max()) # 低 高
注意两点:无序分类调用 min/max 会抛 TypeError;as_ordered() / as_unordered() 可以临时改有序性。分类的排序永远是”按类别表排”,不是按字符串或数字本身排。这是它和普通列最大的区别。
内存优势:重复越多越省
分类列只存一份类别表加整数编码。字符串重复度越高,省得越多:
import pandas as pd
s = pd.Series(["低", "中", "高"] * 1000) # 3000 行,只有 3 种值
print(s.nbytes) # 字符串列占用
print(s.astype("category").nbytes) # 分类列占用,通常省 90% 以上
反面也要知道:如果每行都是唯一值,分类反而更费内存(类别表本身有开销),这种列别转。转换前先 value_counts() 看看基数,类别数远小于行数才划算。
分类列在统计上也有福利。value_counts() 会把没出现过的类别也列出来,计数记 0:
import pandas as pd
s = pd.Series(pd.Categorical(["a", "b", "c", "c"], categories=["c", "a", "b", "d"]))
print(s.value_counts())
# c 2
# a 1
# b 1
# d 0
groupby 想要同样效果,加 observed=False,没数据的组也会留一行(NaN):
import pandas as pd
cats = pd.Categorical(["a", "b", "b"], categories=["a", "b", "c"])
df = pd.DataFrame({"cats": cats, "数值": [1, 2, 3]})
print(df.groupby("cats", observed=False).mean())
# cats
# a 1.0
# b 2.5
# c NaN
做”完整枚举”报表时,这个特性正好用上:每周、每月、每个地区都有一行,哪怕没有数据。
与 get_dummies 怎么选
§32 讲过 get_dummies 做哑变量:一列性别拆成”男、女”两列 0/1。它和 Categorical 是两条路线:
| 对比 | Categorical | get_dummies |
|---|---|---|
| 数据形态 | 保持一列 | 拆成多列 0/1 |
| 主要用途 | 排序、省内存、分组 | 喂给机器学习模型 |
| 顺序 | 可指定 | 无顺序概念 |
Tip判断口诀:数据要”进模型”,用 get_dummies;数据要”留在表里做分析”,用 Categorical。两个还能结合——先把列存成 category 省内存,建模前再 get_dummies 展开。
注意事项
- 赋值限制:只能赋类别表里已有的值,赋新值直接报错,得先 add_categories。
- 拼接退化:concat 两个类别表不同的分类列,结果可能退化成普通 str 列。想保持分类,用
pandas.api.types.union_categoricals合并类别表再拼。 - 不能运算:分类列做加减乘除不支持,它本质是类别不是数字。
- 当索引:分类列设为索引会得到 CategoricalIndex,排序按类别顺序,含重复标签也能高效定位,是”重复值很多的索引”的省内存方案。
Warning记牢”categories 是总枚举,codes 是每个位置指向哪个枚举”,很多报错就明白了:报 “Cannot setitem on a Categorical with a new category” 就是在提醒你——新值不在类别表里,先 add_categories 或 set_categories。
小结
分类数据 = 类别表 + 整数编码,省内存、能排序、天然适配分组。字符串重复度高的列优先转 category;需要逻辑顺序时用 CategoricalDtype 指定。数据准备到这告一段落,下一节开始可视化,把数据画出来看看。