首页 / Pandas 入门教程 / 分类数据:Categorical

Pandas 入门教程

分类数据:Categorical

本教程共 54 篇 · 第 50 篇 · 更新于 2026-08-11 · 约 6 分钟阅读

PandasPandas 入门教程Categorical分类数据category

本节目标:搞懂分类数据 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 是两条路线:

对比Categoricalget_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 指定。数据准备到这告一段落,下一节开始可视化,把数据画出来看看。