category 能省内存是因为将重复值编码为整数索引,仅存储唯一值和对应 codes;低基数列(唯一值占比<5%)才适合转换,需注意避免退化操作和 categories 不一致问题。

为什么 category 能省内存?
因为 category 类型把重复的字符串(或其他值)转成整数编码,只存一份原始值(categories)和一列对应索引(codes)。比如 10 万行全是 "red"、"blue"、"green" 的列,用 object 类型每行都存一个字符串对象,而 category 只存 3 个字符串 + 10 万个 int8(默认)索引,内存直接降 5–10 倍。
怎么安全地转成 category?
不是所有列都适合转——只有低基数(unique 值少)、高重复率的列才值得转。盲目转换反而可能更占内存(比如唯一值超 10 万的列,categories 本身就很重)。
- 先用
df[col].nunique() / len(df)看下“唯一值占比”,低于 0.05(5%)通常值得转 - 用
df[col].astype("category")转换,但注意:它默认保留原始出现顺序,且不会自动丢弃未使用的分类(unused categories) - 如果后续会做
dropna()或过滤,建议加df[col].cat.remove_unused_categories()清理冗余分类 - 想控制编码类型(比如强制用
int8),得先设ordered=False再调cat.as_ordered()或直接用Categorical构造器
category 对常用操作的影响
多数聚合(groupby().size()、value_counts())能自动利用分类信息加速;但某些操作会悄悄退化回 object,比如 str.upper()、apply(lambda x: ...) —— 这时 codes 丢失,内存又涨回去。
- 字符串操作必须先转回
object:df[col].astype(str).str.upper() -
pd.concat()时若两个category列的categories不一致,结果列会变成object;统一用CategoricalDtype预定义可避免 - 写入 Parquet 时,默认保留
category,但 CSV 会还原为字符串——别指望 CSV 能省空间
一键压缩内存的实用函数
手动逐列判断太慢,可以用这个轻量函数扫描并转换:
立即学习“Python免费学习笔记(深入)”;
def reduce_cat_mem(df, threshold=0.05, dtype="category"):
for col in df.select_dtypes(include=["object"]).columns:
if df[col].nunique() / len(df) < threshold:
df[col] = df[col].astype(dtype)
return df
调用后记得检查 df.memory_usage(deep=True).sum() 对比前后。注意:该函数不处理数值型低基数列(如 int64 存状态码 0/1/2),那种得先转 object 或直接用 pd.Int8Dtype()。
真正容易被忽略的是:category 的内存优势只在列本身重复度高、且你**不频繁触发隐式转换**时成立。一旦开始链式 str 操作或混用不同 categories 的数据拼接,省下的内存就没了。


















