category类型能省内存,但仅适用于唯一值占比低于50%的低基数字符串列,如性别、省份等,可省60%–90%内存;高基数列反而增加开销。

category 类型真能省内存?先看它适合什么场景
能省,但不是所有字符串列都适合。关键看「唯一值数量 / 总行数」是否明显小于 0.5(即重复度高)。比如性别、省份、订单状态这类低基数(low-cardinality)列,转 category 后内存常减少 60%–90%;而用户昵称、日志消息这种几乎每行都不同的列,转了反而多一层索引开销。
- 唯一值占比 category
- 唯一值占比 10%–50%:视数据量而定,百万行以上通常仍受益
- 唯一值占比 > 50%:基本不省,还可能拖慢某些操作(如
str.contains())
用这行快速检查:
df['col'].nunique() / len(df)
别只看
df.info() 的 memory usage —— 它默认不 deep,得加 deep=True 才算真实字符串内存。
排序时 category 列为啥更快?但默认顺序可能不是你想要的
因为底层存的是整数编码(0, 1, 2…),排序实际是对整数数组操作,比逐个比较字符串快 3–5 倍。但注意:默认排序按「类别出现顺序」或「字典序」,不是你业务里的逻辑顺序(比如 “高” > “中” > “低”)。
- 要自定义顺序,必须显式创建
CategoricalDtype:from pandas.api.types import CategoricalDtype
cat_type = CategoricalDtype(categories=['低', '中', '高'], ordered=True)
df['level'] = df['level'].astype(cat_type) - 直接
df['level'].astype('category')不带ordered=True,后续sort_values()会按字典序排,且无法做大小比较(df['level'] > '中'报错) -
sort_values()对已设ordered=True的列,自动按定义顺序升序;降序就加ascending=False,无需额外映射
读取阶段就优化,别等加载完再转
等 pd.read_csv() 把整列读成 object 再转 category,中间已占用大量内存。应该在读取时直接指定 dtype:
- 方法一(推荐):用
dtype参数:df = pd.read_csv('data.csv', dtype={'status': 'category', 'region': 'category'}) - 方法二:对数值列同步降级,避免 int64 浪费:
dtype={'user_id': 'uint32', 'score': 'float32'} - 方法三:用
convert_dtypes()做兜底(但它不会自动把字符串转category,只转为string类型)
漏掉这步,100 万行含 3 个高重复字符串列的数据,可能多占 200MB+ 内存,且后续所有 groupby、sort_values 都更慢。
容易被忽略的坑:category 列参与 merge 或 filter 时的行为
- merge 时若左右 DataFrame 的
category 列 categories 不一致(比如左有 ['A','B'],右有 ['B','C']),结果列会自动转回 object,内存和性能优势瞬间消失
- filter 时写
df[df['cat_col'] == 'X'] 没问题,但用 isin() 要小心:df[df['cat_col'].isin(['X','Y'])] 若 'Y' 不在 categories 中,会静默返回空 —— 不报错,但结果不对
- 保存为 parquet 时默认保留 category 结构,但存 CSV 会变回字符串;重读 CSV 若没指定 dtype,又回到起点
category 列 categories 不一致(比如左有 ['A','B'],右有 ['B','C']),结果列会自动转回 object,内存和性能优势瞬间消失 df[df['cat_col'] == 'X'] 没问题,但用 isin() 要小心:df[df['cat_col'].isin(['X','Y'])] 若 'Y' 不在 categories 中,会静默返回空 —— 不报错,但结果不对 真正省内存,靠的不是“转一次”,而是从读入、处理到输出全程保持类型意识。一个列转了 category,不代表它就永远安全了——任何隐式类型推断操作(比如 pd.concat()、merge()、甚至某些 apply())都可能把它悄悄打回原形。










