category本质是字符串去重+整数编码,存一份唯一值列表和整数id数组;当唯一值占比超50%(如用户id、url)时转category会更占内存,因词典开销叠加整数索引。

Category 本质是“字符串去重 + 整数编码”
它不存 100 万次 "北京",而是存一个去重列表 ['北京', '上海', '广州'],再用一个长度为 100 万的 int8 数组记录每行对应哪个索引(比如 0、1、2)。内存大头从重复字符串对象(含引用计数、哈希缓存等)变成紧凑整数数组 + 一份短字符串列表。
什么时候转 category 反而更占内存?
当字符串列唯一值太多时,category 就失效了。比如用户 ID、URL、长随机字符串——这些列 nunique() / len(df) 接近 1(>50%),categories 列表本身就会存下几乎全部原始字符串,再加上一层整数索引,总内存比原 object 还高。
- 安全阈值:建议只对
nunique() / len(df) (即 5% 唯一率)的列转 - 检查方法:
df["col"].nunique() / len(df) - 误转后果:内存不降反升,且后续
.str操作会触发隐式转回object,瞬间暴涨
直接 astype('category') 为什么常出问题?
它看似简单,但默认行为容易埋坑:
- 含
NaN时,NaN会被当作一个合法 category,导致value_counts(dropna=False)多出一行,groupby结果偏移 - 多个 DataFrame 分别转,
categories顺序/内容不一致,merge或concat会静默失败(值相同但编码不同,匹配不上) - 反复调用
.astype('category')会重建 categories,白耗 CPU
推荐写法:df["col"] = pd.Categorical(df["col"], categories=df["col"].dropna().unique(), ordered=False)
转完 category,哪些操作会悄悄变慢甚至失效?
节省内存 ≠ 全面提速。以下操作实际会退化:
-
.str.contains()、.str.replace()等方法不支持 category,会自动转回object,触发完整拷贝和内存飙升 -
sort_values()默认按categories定义顺序排,不是字典序;若你依赖字符串自然顺序(如时间字符串"2023-01""2023-10"),得显式设ordered=True或改用pd.Categorical(..., ordered=True) -
query("col == 'A'")没问题,但query("col in ['A','B']")会隐式转换,应改用df["col"].isin(["A","B"])
最易被忽略的一点:index 是字符串时,df.index.dtype == 'object' 同样吃内存——别只盯列,记得检查并重置索引。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











