本节介绍分类数据的常用操作:类别重命名、增删、排序、重编码、切片与合并等。


1. 重命名类别:rename_categories()

s = pd.Series(['a', 'b', 'a']).astype('category')
s.cat.rename_categories(['X', 'Y'])
# 0    X
# 1    Y
# 2    X
# Categories (2, object): ['X', 'Y']

使用字典映射(只重命名部分):

s.cat.rename_categories({'a': 'alpha', 'b': 'beta'})
# 0    alpha
# 1     beta
# 2    alpha

注意

重命名后编码不变,只改变显示内容。新类别数量必须与原一致(除非是字典映射)。


2. 重排序:reorder_categories()

s = pd.Series(['高', '低', '中']).astype('category')
print(s.cat.categories)  # Index(['低', '高', '中'])
 
s_reordered = s.cat.reorder_categories(['低', '中', '高'])
print(s_reordered.cat.categories)  # Index(['低', '中', '高'])
print(s_reordered.cat.ordered)     # False(重排序不自动设置有序)

重排序只改变类别顺序,不改变数据本身编码。若需要比较顺序,还需 as_ordered()。


3. 添加类别:add_categories()

s = pd.Series(['a', 'b']).astype('category')
s.cat.add_categories(['c', 'd'])
# Categories (4, object): ['a', 'b', 'c', 'd']

用途

当数据可能在未来出现新类别时,先添加类别可以保证结构一致。


4. 移除类别:remove_categories()

s = pd.Series(['a', 'b', 'c']).astype('category')
s.cat.remove_categories('c')
# 0      a
# 1      b
# 2    NaN      ← 'c' 被转为缺失
# Categories (2, object): ['a', 'b']

注意

移除类别不会删除数据中已有的值,而是将对应值变为 NaN。这与 dropna 不同。


5. 设置类别全集:set_categories()

set_categories() 可以同时添加、移除类别,并可设置是否有序。

s = pd.Series(['a', 'b', 'c']).astype('category')
s.cat.set_categories(['a', 'b', 'c', 'd'], ordered=True)
# Categories (4, object): ['a' < 'b' < 'c' < 'd']
 
# 移除类别时,数据中对应的值转为 NaN
s.cat.set_categories(['a', 'b'])
# 0      a
# 1      b
# 2    NaN

参数

参数说明
new_categories新类别列表
ordered是否有序(默认保持原状态)
rename如果为 True,则重命名而不是重新映射

set_categories vs add/remove

  • add_categories 只能增加
  • remove_categories 只能删除
  • set_categories 可任意组合设置全集

6. 有序与无序转换

as_ordered()

将无序分类转为有序(需类别本身有顺序语义):

s = pd.Series(['低', '高', '中']).astype('category')
s.cat.as_ordered()
# Categories (3, object): ['低' < '中' < '高']

as_unordered()

s.cat.as_unordered()
# Categories (3, object): ['低', '中', '高']

有序分类的比较

  • 有序分类支持 <、<=、>、>=
  • 无序分类只支持 == 和 !=

7. 移除未使用类别:remove_unused_categories()

s = pd.Series(['a', 'b', 'a']).astype(
    pd.CategoricalDtype(categories=['a', 'b', 'c'])
)
print(s.cat.categories)  # Index(['a', 'b', 'c'])
 
s_clean = s.cat.remove_unused_categories()
print(s_clean.cat.categories)  # Index(['a', 'b'])

意义

保留大量未使用的类别会浪费内存与排序时间,清洗后更高效。


8. 排序与比较

有序分类排序

s = pd.Series(['中', '低', '高']).astype(
    pd.CategoricalDtype(categories=['低', '中', '高'], ordered=True)
)
s.sort_values()
# 1    低
# 0    中
# 2    高

比较运算

s > '中'
# 0    False
# 1     True
# 2    False

数值比较(无顺序时报错)

s_unordered = s.cat.as_unordered()
# s_unordered > '中'  # TypeError

9. 与字符串方法结合

分类数据可以直接使用 .str 访问器:

s = pd.Series(['apple', 'banana']).astype('category')
s.str.upper()
# 0    APPLE
# 1    BANANA
# dtype: object   ← 返回 object,不再保留 category

但注意:.str 返回的是普通字符串 Series,分类信息丢失。


10. 合并与连接

concat

s1 = pd.Series(['a', 'b']).astype('category')
s2 = pd.Series(['c', 'a']).astype('category')
 
pd.concat([s1, s2])
# 0    a
# 1    b
# 2    c
# 3    a
# dtype: category

若两个分类类型一致,则结果仍为 category;若不一致,则退化为 object。

merge

df1 = pd.DataFrame({'id': [1, 2], 'cat': ['a', 'b']})
df2 = pd.DataFrame({'id': [2, 3], 'cat': ['b', 'c']})
df1['cat'] = df1['cat'].astype('category')
df2['cat'] = df2['cat'].astype('category')
 
merged = df1.merge(df2, on='id')
print(merged['cat_x'].dtype)  # object(因为两列类别不同,合并后可能退化)

11. 与缺失值处理

s = pd.Series(['a', None, 'b']).astype('category')
 
# 丢弃缺失
s.dropna()
 
# 填充缺失(注意填充值必须属于类别,否则报错)
s.fillna('a')
 
# 添加新类别再填充
s.cat.add_categories('unknown').fillna('unknown')

fillna 限制

分类数据的 fillna 填充值必须是已有类别,否则需要先 add_categories。


小结

  • rename_categories 重命名
  • reorder_categories 重排序
  • add_categories / remove_categories / set_categories 增删/设置
  • as_ordered / as_unordered 切换顺序
  • remove_unused_categories 清理未用类别
  • 合并后可能退化为 object,需注意