Pandas 类别类型(Categorical)
Categorical 是 Pandas 专为有限重复标签设计的数据类型,核心作用:节省内存、加速分组/筛选/排序、自定义排序顺序。很多报表、维度字段(渠道、商品、状态、地区)都优先使用它。本文独立成文,覆盖创建、属性、有序分类、增删类别、类型转换、性能、坑点,延续整套教程风格。
前置导入
import pandas as pd
import numpy as np
一、什么是 Categorical
Categorical 由两部分组成:
categories:唯一标签列表(类别池)codes:整数编码,原始数据映射成数字存储ordered:布尔值,标记是否为有序分类
优势:
- 重复值多的时候,内存远小于
string/object groupby、筛选、value_counts 速度更快- 可以自定义标签顺序,不受字典序控制(报表、图表排序神器)
适用场景:渠道、订单状态、产品分类、省份、评级(低/中/高)
不适合:唯一值极多(订单号、手机号),会额外增加开销
# 简单示例
s = pd.Series(["线上","线下","线上","线下","线上"], dtype="category")
print(s)
print(s.cat.categories)
print(s.cat.codes)
二、创建 Categorical 的几种方式
方式1:创建Series时直接指定 dtype
s = pd.Series(["A","B","A","C"], dtype="category")
方式2:pd.Categorical 构造
cat = pd.Categorical(
["低","高","中","低"],
categories=["低","中","高"],
ordered=True
)
s = pd.Series(cat)
ordered=True 开启有序,支持比较:"低" < "中" < "高"
方式3:astype 转换(最常用)
df = pd.DataFrame({
"渠道":["线上","线下","线上","线下"],
"金额":[178,45,267,90]
})
df["渠道"] = df["渠道"].astype("category")
方式4:read_csv 读取时直接指定(性能最优)
df = pd.read_csv(
"sales.csv",
dtype={"渠道":"category"}
)
三、Categorical 核心 cat 访问器属性
s = pd.Series(["低","中","高","低"], dtype=pd.CategoricalDtype(["低","中","高"], ordered=True))
s.cat.categories # 类别列表
s.cat.codes # 编码数组,int
s.cat.ordered # 是否有序 bool
s.cat.rename_categories({"低":"L","中":"M","高":"H"}) # 重命名类别
四、有序分类(重点,报表高频)
普通字符串排序是字典序;有序分类可以自定义业务顺序。
# 优先级:低 < 中 < 高
dtype_rank = pd.CategoricalDtype(
categories=["低","中","高"],
ordered=True
)
s = pd.Series(["高","低","中","低"], dtype=dtype_rank)
# 排序会按照我们定义的顺序,不是字典序
s.sort_values()
# 支持大小比较
s > "低"
无序分类不能做
> < >= <=比较,会报错。
五、修改类别
5.1 新增/删减类别 set_categories
s = pd.Series(["线上","线下"], dtype="category")
# 新增"分销"类别,原有不在列表里的值变成NaN
s = s.cat.set_categories(["线上","线下","分销"])
参数:
set_categories(new_categories, ordered=None)- 原有值不在新列表 → 转为 NaN
5.2 重命名类别 rename_categories
s = s.cat.rename_categories({"线上":"Online","线下":"Offline"})
5.3 移除未使用类别 remove_unused_categories
数据里已经没有的类别,清理掉,节省内存
s = s.cat.remove_unused_categories()
5.4 追加类别 add_categories
s = s.cat.add_categories("分销")
六、缺失值处理
Categorical 本身支持缺失值 pd.NA,缺失值不会出现在 categories 里,codes 中用 -1 代表空值:
s = pd.Series(["线上", None, "线下"], dtype="category")
print(s.cat.codes) # 对应:0, -1, 1
七、分组、筛选、排序实战
df = pd.DataFrame({
"等级":["低","高","中","低","高"],
"金额":[100,900,400,120,850]
})
# 设置有序分类
df["等级"] = df["等级"].astype(pd.CategoricalDtype(["低","中","高"], ordered=True))
# 分组聚合,结果顺序自动按 categories 顺序
df.groupby("等级")["金额"].sum()
# 筛选
df[df["等级"] > "低"]
# 排序
df.sort_values("等级")
八、category 转成其他类型
# 转字符串 string
s_str = s.astype("string")
# 转普通object
s_obj = s.astype(object)
# 拿到编码(数字)
s_code = s.cat.codes
⚠️ 坑:category 类型不能直接使用 str 访问器,需要先转 string。
# ❌ 报错
# s.str.contains("线")
# ✅ 正确写法
s.astype("string").str.contains("线")
九、性能与内存对比
df = pd.DataFrame({"渠道": np.random.choice(["线上","线下","分销"], size=100000)})
print(df["渠道"].astype("object").memory_usage(deep=True))
print(df["渠道"].astype("category").memory_usage(deep=True))
十万行级别,category 内存通常只有 string/object 的 1/5 ~ 1/10。
前提:唯一值数量远小于总行数;如果几乎每行都不一样,category 没有优势。
十、高频坑清单
- 有序分类比较报错:没有设置
ordered=True,不能用> < - str 方法直接调用报错:category 没有 str 访问器,先转 string
- 新增标签自动变成 NaN:类别池没有定义这个值,需要
add_categories/set_categories - 排序不是预期顺序:字符串默认字典序,改用有序 CategoricalDtype
- 合并两个category列 categories 不一致:运算会自动升级成object
- codes 是内部编码,不要当成业务ID使用,categories顺序改动后codes会变
- 读取Excel默认读成object,大量重复维度字段记得转category
十一、速查表
| API | 作用 |
|---|---|
| dtype=”category” | 快速创建无序分类 |
| pd.CategoricalDtype(categories, ordered) | 自定义有序分类类型 |
| s.cat.categories | 获取类别列表 |
| s.cat.codes | 获取底层编码 |
| s.cat.set_categories | 重新设置类别池 |
| s.cat.add_categories | 追加新类别 |
| s.cat.remove_unused_categories | 删除没有出现过的类别 |
| s.cat.rename_categories | 类别重命名 |
| s.astype(“string”) | category转字符串 |
十二、综合实战模板
def cast_rank_category(series):
"""将评级字段转为有序分类:低 < 中 < 高"""
dtype = pd.CategoricalDtype(
categories=["低","中","高"],
ordered=True
)
return series.astype(dtype)
0 条笔记