Skip to content

Python Pandas - 分类数据

现实世界的数据集通常包含具有重复文本值的列,这些值代表不同的组或类别(categories),例如 ‘Gender’(男性/女性)、‘Country’(美国/加拿大/墨西哥)或 ‘Status’(待处理/已完成/失败)。Pandas 提供了一个专门的 Categorical 数据类型(dtype)来高效处理这类数据。

分类变量(Categorical variables)只能取有限的、固定的可能值集合,称为 ‘categories’(类别)。它们可能具有有意义的顺序(例如 ‘Low’ < ‘Medium’ < ‘High’),但标准的数值运算(如加法)不适用。使用 Categorical dtype 提供了几个优点:

  • 内存效率(Memory Efficiency): 如果一个字符串列有很多重复的少数几个独特值,将其转换为 Categorical 可以显著减少内存使用。Pandas 只存储一次独特的类别,并使用整数代码(integer codes)表示值。
  • 逻辑顺序(Logical Ordering): 你可以为类别定义自定义的逻辑顺序(例如 [‘Small’, ‘Medium’, ‘Large’])。排序或查找最小值/最大值将使用此逻辑顺序,而不是默认的字母(lexical)顺序。
  • 向其他库传递信号(Signal to Other Libraries): 它清楚地向其他 Python 库(如可视化或统计建模库)表明某一列应被视为分类列,从而启用适当的图表类型或统计方法。

分类数据可以通过几种方式创建:

在创建 Series 或 DataFrame 时指定 dtype="category":

import pandas as pd
# 创建一个带有分类数据的 Series
s = pd.Series(["a", "b", "c", "a"], dtype="category")
print(s)

输出:

0 a
1 b
2 c
3 a
dtype: category
Categories (3, object): ['a', 'b', 'c']

注意,尽管 Series 有四个元素,Pandas 仅识别出三个独特的类别:‘a’、‘b’ 和 ‘c’。

你可以使用 pd.Categorical 构造函数显式地创建一个 Categorical 对象:

pd.Categorical(values, categories=None, ordered=False)

示例 1:基本创建

import pandas as pd
cat_obj = pd.Categorical(['a', 'b', 'c', 'a', 'b', 'c'])
print(cat_obj)

输出:

['a', 'b', 'c', 'a', 'b', 'c']
Categories (3, object): ['a', 'b', 'c']

示例 2:指定类别

import pandas as pd
values = ['a', 'b', 'c', 'a', 'b', 'c', 'd'] # 'd' 不在指定的类别中
defined_categories = ['c', 'b', 'a']
cat_obj = pd.Categorical(values, categories=defined_categories)
print(cat_obj)

输出:

['a', 'b', 'c', 'a', 'b', 'c', NaN]
Categories (3, object): ['c', 'b', 'a']

这里,categories 参数显式定义了允许的类别。values 中未出现在 defined_categories 中的任何值(例如 ‘d’)都会变成 NaN(Not a Number)。

示例 3:指定顺序

import pandas as pd
values = ['a', 'b', 'c', 'a', 'b', 'c', 'd']
defined_categories = ['c', 'b', 'a']
# 创建一个有序分类
cat_obj = pd.Categorical(values, categories=defined_categories, ordered=True)
print(cat_obj)

输出:

['a', 'b', 'c', 'a', 'b', 'c', NaN]
Categories (3, object): ['c' < 'b' < 'a']

设置 ordered=True 会根据 categories 中提供的顺序建立逻辑顺序。在这种情况下,‘c’ 被认为小于 ‘b’,‘b’ 被认为小于 ‘a’。

你可以使用 .astype() 方法将现有的 Series 或列转换为分类类型。这是一种非常常用的方法。

import pandas as pd
s = pd.Series(["a", "b", "c", "a"])
print("原始 Series:")
print(s)
s_cat = s.astype('category')
print("\n分类 Series:")
print(s_cat)

输出:

Original Series:
0 a
1 b
2 c
3 a
dtype: object
Categorical Series:
0 a
1 b
2 c
3 a
dtype: category
Categories (3, object): ['a', 'b', 'c']

.describe() 方法提供分类数据的摘要,包括计数 (count)、独特类别数 (unique)、最频繁的类别 (top) 及其频率 (freq)。

import pandas as pd
import numpy as np
cat = pd.Categorical(["a", "c", "c", np.nan], categories=["b", "a", "c"])
df = pd.DataFrame({"cat_column": cat, "str_column": ["a", "c", "c", np.nan]})
print("描述 DataFrame (包含分类摘要):")
print(df.describe(include=['category']))
print("\n直接描述分类列:")
print(df["cat_column"].describe())

输出:

Describe DataFrame (includes categorical summary):
cat_column
count 3
unique 2
top c
freq 2
Describe the categorical column directly:
count 3
unique 2
top c
freq 2
Name: cat_column, dtype: object

使用 .cat 访问器来处理分类属性:

import pandas as pd
import numpy as np
s = pd.Series(["a", "c", "c", np.nan], dtype=pd.CategoricalDtype(categories=["b", "a", "c"], ordered=True))
# 获取类别
print("Categories:", s.cat.categories)
# 检查是否有序
print("Is ordered:", s.cat.ordered)

输出:

Categories: Index(['b', 'a', 'c'], dtype='object')
Is ordered: True

使用 s.cat.rename_categories() 方法:

import pandas as pd
s = pd.Series(["a", "b", "c", "a"], dtype="category")
print("原始类别:", s.cat.categories)
# 使用字典或函数重命名类别
s_renamed = s.cat.rename_categories({"a": "Alpha", "b": "Beta", "c": "Gamma"})
# 或者使用函数:s_renamed = s.cat.rename_categories(lambda c: f"Group {c.upper()}")
print("重命名后的类别:", s_renamed.cat.categories)
print("\n重命名类别后的 Series:")
print(s_renamed)

输出:

Original categories: Index(['a', 'b', 'c'], dtype='object')
Renamed categories: Index(['Alpha', 'Beta', 'Gamma'], dtype='object')
Series with renamed categories:
0 Alpha
1 Beta
2 Gamma
3 Alpha
dtype: category
Categories (3, object): ['Alpha', 'Beta', 'Gamma']

使用 s.cat.add_categories() 方法:

import pandas as pd
s = pd.Series(["a", "b", "c", "a"], dtype="category")
print("原始类别:", s.cat.categories)
s_new_cats = s.cat.add_categories(["d", "e"])
print("\n添加后的类别:", s_new_cats.cat.categories)

输出:

Original categories: Index(['a', 'b', 'c'], dtype='object')
Categories after adding: Index(['a', 'b', 'c', 'd', 'e'], dtype='object')

使用 s.cat.remove_categories() 方法。注意,任何属于被移除类别的现有数据点将变为 NaN。

import pandas as pd
s = pd.Series(["a", "b", "c", "a"], dtype="category")
print("原始对象:")
print(s)
# 移除类别 'a'
s_removed = s.cat.remove_categories(["a"])
print("\n移除后:")
print(s_removed)
print("\n剩余类别:", s_removed.cat.categories)

输出:

Original object:
0 a
1 b
2 c
3 a
dtype: category
Categories (3, object): ['a', 'b', 'c']
After removal:
0 NaN
1 b
2 c
3 NaN
dtype: category
Categories (2, object): ['b', 'c']
Remaining categories: Index(['b', 'c'], dtype='object')

如果你过滤数据或移除类别,某些类别可能不再存在于数据中。使用 s.cat.remove_unused_categories() 从 .categories 属性中移除它们。

import pandas as pd
s = pd.Series(pd.Categorical(["a", "b", "a"], categories=['a', 'b', 'c']))
print("原始 Series:")
print(s)
print("原始类别:", s.cat.categories)
# 移除未使用的类别 ('c')
s_useful = s.cat.remove_unused_categories()
print("\n移除未使用的类别后的 Series:")
print(s_useful)
print("更新后的类别:", s_useful.cat.categories)

输出:

Original Series:
0 a
1 b
2 a
dtype: category
Categories (3, object): ['a', 'b', 'c']
Original Categories: Index(['a', 'b', 'c'], dtype='object')
Series after removing unused:
0 a
1 b
2 a
dtype: category
Categories (2, object): ['a', 'b']
Updated Categories: Index(['a', 'b'], dtype='object')

比较在特定条件下有效:

  • 相等性 (==, !=): 可以将分类 Series 与长度相同的类似列表对象(list, Series, array)或标量值进行比较。
  • 有序比较 (>, >=, <, <=): 仅当 ordered=True 时才可能进行比较。你可以比较:
    • 一个有序的分类 Series 与另一个有序的分类 Series 进行比较,如果它们具有完全相同的类别和顺序。
    • 一个有序的分类 Series 与一个属于其类别的标量值进行比较。

示例:比较有序分类

import pandas as pd
# 定义有序类别
cat_dtype = pd.CategoricalDtype(categories=['low', 'medium', 'high'], ordered=True)
cat1 = pd.Series(['low', 'medium', 'high'], dtype=cat_dtype)
cat2 = pd.Series(['medium', 'medium', 'medium'], dtype=cat_dtype)
print("cat1 > cat2:")
print(cat1 > cat2)
print("\ncat1 == 'medium':")
print(cat1 == 'medium')

输出:

cat1 > cat2:
0 False
1 False
2 True
dtype: bool
cat1 == 'medium':
0 False
1 True
2 False
dtype: bool