一、cat对象
1. cat对象的属性
在 pandas 中,category类型用于处理有限、固定取值的分类变量(如年级、性别、学历等),相比普通字符串类型,它能大幅节省内存、提升运算效率,还能自定义类别顺序。
(1)分类数据的创建
import pandas as pd
# 读取数据,提取Grade列(年级),转换为category类型
df = pd.read_csv('../data/learn_pandas.csv', usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
s = df.Grade.astype('category')
s.head()
运行结果:
0 Freshman
1 Freshman
2 Senior
3 Sophomore
4 Sophomore
Name: Grade, dtype: category
Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']
dtype: category:标识这是分类类型Categories:展示该列所有不重复的类别,以Index类型存储
(2)cat 对象的核心属性
分类类型的Series会自动生成.cat访问器(类似.str处理字符串),用于操作分类数据,核心属性如下:
| 属性 | 作用 | 示例 |
|---|---|---|
s.cat.categories |
获取所有类别(Index 类型) | Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object') |
s.cat.ordered |
判断类别是否有序(默认 False) | False(无序分类,如性别;有序分类如成绩等级需手动指定) |
s.cat.codes |
类别对应的整数编码(按categories顺序编号,从 0 开始) |
示例中Freshman=0、Junior=1、Senior=2、Sophomore=3 |
2.类别的增改查删
categories本质是Index类型,不可直接修改 (如s.cat.categories[0] = '新类别'会报错),必须通过cat对象的专用方法操作。
(1) 查:查询类别
s.cat.categories:查看所有类别s.cat.codes:查看每个元素的编码s.cat.ordered:查看是否有序
(2)增:添加新类别
使用add_categories()方法,在现有类别后追加新类别,不会修改原数据:
# 新增"Graduate"(毕业生)类别
s = s.cat.add_categories('Graduate')
s.cat.categories
运行结果:
Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
(3)删:删除类别
(a)remove_categories():删除指定类别
删除后,原数据中属于该类别的值会被设为NaN(缺失值):
# 删除"Freshman"(大一)类别
s = s.cat.remove_categories('Freshman')
s.cat.categories
s.head()
运行结果:
# 类别列表
Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
# 原数据中Freshman变为NaN
0 NaN
1 NaN
2 Senior
3 Sophomore
4 Sophomore
Name: Grade, dtype: category
Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
(b)remove_unused_categories():删除未使用的类别
自动移除数据中没有出现过的类别,不影响原数据:
# 假设当前类别包含'Sophomore'和'PhD',但数据中无'PhD'
s = s.cat.remove_unused_categories()
s.cat.categories
运行结果:
Index(['Sophomore'], dtype='object') # 仅保留数据中存在的类别
(c)set_categories():直接重置类别列表
完全替换原有类别,原数据中不属于新类别的值会被设为 NaN:
# 重置类别为['Sophomore', 'PhD']
s = s.cat.set_categories(['Sophomore','PhD'])
s.cat.categories
s.head()
运行结果:
# 新类别列表
Index(['Sophomore', 'PhD'], dtype='object')
# 原数据中不属于新类别的值变为NaN
0 NaN
1 NaN
2 NaN
3 Sophomore
4 Sophomore
Name: Grade, dtype: category
Categories (2, object): ['Sophomore', 'PhD']
(4) 改:重命名类别
使用rename_categories()方法,会同步修改原数据中的对应值,不会产生 NaN:
# 将"Sophomore"重命名为"本科二年级学生"
s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})
s.head()
运行结果:
0 NaN
1 NaN
2 NaN
3 本科二年级学生
4 本科二年级学生
Name: Grade, dtype: category
Categories (1, object): ['本科二年级学生']
- 支持字典映射(旧类别名:新类别名),也支持函数批量重命名
- 仅修改类别名称,不改变类别顺序和编码
- 禁止直接修改
s.cat.categories:必须用专用方法,否则报错set_categories会清空原数据:若新类别不包含旧类别,对应值会变为 NaN,需谨慎使用- 忽略
ordered属性:无序分类的排序结果是按类别字典序,而非业务顺序- 用
remove_unused_categories清理冗余类别:定期清理未使用的类别,保持数据整洁
二.有序分类
1.序的建立
(1) 核心方法
有序类别和无序类别可以通过两个核心方法互相转化:
| 方法 | 作用 | 关键要求 |
|---|---|---|
reorder_categories(new_categories, ordered=True) |
将无序分类转为有序分类,并自定义顺序 | 1. new_categories 必须是原类别列表的重排 ,不能新增 / 缺失类别2. 必须显式指定 ordered=True(否则方法无效) |
as_unordered() |
将有序分类转回无序分类 | 无额外要求,直接调用即可 |
as_ordered() |
将无序分类转为有序分类(默认按原类别字典序) | 可配合 reorder_categories 调整顺序 |
(2)示例
import pandas as pd
# 1. 读取数据,创建无序分类
df = pd.read_csv('../data/learn_pandas.csv', usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
s = df.Grade.astype('category')
# 2. 自定义年级顺序(大一 < 大二 < 大三 < 大四),转为有序分类
s = s.cat.reorder_categories(
['Freshman', 'Sophomore', 'Junior', 'Senior'],
ordered=True
)
print("有序分类:\n", s.head())
# 输出:Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']
# 3. 转回无序分类
s_unordered = s.cat.as_unordered()
print("\n转回无序分类:\n", s_unordered.head())
# 输出:Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior'](无<符号)
(3)补充技巧
如果不想在 reorder_categories 中指定 ordered=True,可以分两步操作:
# 先转为有序,再调整顺序
s = df.Grade.astype('category').cat.as_ordered()
s = s.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'])
2.排序
普通字符串列的排序是字典序 ,而有序分类的排序会严格遵循自定义的类别顺序,完全符合业务逻辑。
(1)两种排序方式
(a)按值排序:sort_values()
直接对 DataFrame 按分类列排序,顺序为自定义的类别顺序:
# 先将Grade列转为有序分类
df.Grade = df.Grade.astype('category')
df.Grade = df.Grade.cat.reorder_categories(
['Freshman', 'Sophomore', 'Junior', 'Senior'],
ordered=True
)
# 按Grade值排序
df_sorted = df.sort_values('Grade')
print(df_sorted.head())
运行结果会按「大一 → 大二 → 大三 → 大四」的顺序排列,而非字典序。
(b)按索引排序:sort_index()
将分类列设为索引后,按索引排序,同样遵循自定义顺序:
# 将Grade设为索引,再排序
df_index_sorted = df.set_index('Grade').sort_index()
print(df_index_sorted.head())
3.比较
有序分类建立顺序后,支持两类比较操作:
(1)相等 / 不等比较(== / !=)
-
支持对象:标量 、同长度 Series 、列表
-
特点:无论有序 / 无序分类都支持,仅判断值是否相等
-
示例:
1. 标量比较:筛选大二学生
res1 = df.Grade == 'Sophomore'
print(res1.head())输出:
0 False
1 False
2 False
3 True
4 True
Name: Grade, dtype: bool
2. 列表比较:和全为'PhD'的列表比较
res2 = df.Grade == ['PhD'] * df.shape[0]
print(res2.head())输出全为False(无PhD类别)
(2) 大小比较(> / >= / < / <=)
-
仅有序分类支持,严格遵循自定义的类别顺序
-
要求:参与比较的元素必须属于原序列的
categories,且和原序列有相同索引 -
示例:
筛选大二及以下年级(Freshman、Sophomore)
res3 = df.Grade <= 'Sophomore'
print(res3.head())输出:
0 True
1 True
2 False
3 True
4 True
Name: Grade, dtype: bool
错误示例:打乱索引后比较(索引不一致,会报错)
res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True)
有序分类的底层是整数编码 + 顺序定义:
- 类别顺序对应编码的大小(
Freshman=0<Sophomore=1<Junior=2<Senior=3)- 排序、比较操作本质是对整数编码的操作,因此效率远高于字符串
错误操作 问题 正确做法 reorder_categories新增 / 缺失类别直接报错 必须传入原类别的完整重排列表 无序分类使用大小比较 报错 TypeError先通过 reorder_categories转为有序分类比较时索引不一致 报错 ValueError确保比较双方索引完全一致 直接修改 s.cat.categories报错 必须用 reorder_categories等专用方法
三.区间类别
1.利用 cut 和 qcut 进行区间构造
区间是特殊的分类类型,在数据分析中常用于将连续数值离散化(分箱),核心工具是pd.cut()和pd.qcut()。
(1)pd.cut():等距分箱
核心参数与用法
| 参数 | 作用 | 关键说明 |
|---|---|---|
bins |
分箱规则 | 1. 传入整数n:按数据最大 / 最小值等距 分为n段2. 传入列表:自定义分箱分割点(支持np.infty表示无穷大) |
right |
区间开闭 | 默认True(左开右闭);设为False则为左闭右开 |
labels |
区间别名 | 为每个区间自定义名称(替代默认的区间字符串) |
retbins |
是否返回分割点 | 默认False;设为True则返回分箱结果 + 分割点数组 |
关键细节:区间边界自动调整
- 默认左开右闭时,为包含最小值,pandas 会在最小区间左端点减去
0.001*(max-min) - 左闭右开时,会在最大区间右端点加上
0.001*(max-min),确保包含最大值
示例
import pandas as pd
import numpy as np
# 示例1:等距分箱
s = pd.Series([1, 2])
print("默认左开右闭分箱:\n", pd.cut(s, bins=2))
# 输出:
# 0 (0.999, 1.5]
# 1 (1.5, 2.0]
# dtype: category
# Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
print("\n左闭右开分箱:\n", pd.cut(s, bins=2, right=False))
# 输出:
# 0 [1.0, 1.5)
# 1 [1.5, 2.001)
# dtype: category
# Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]
# 示例2:自定义分箱分割点
print("\n自定义分箱:\n", pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty]))
# 示例3:自定义别名+返回分割点
res = pd.cut(s, bins=2, labels=['small', 'big'], retbins=True)
print("\n自定义别名分箱:\n", res[0])
print("分箱分割点:\n", res[1])
(2)pd.qcut():分位数分箱
核心参数与用法
- 核心参数
q:替代cut的bins,表示分位数- 传入整数
n:按n 等分位数分箱(每个区间样本量近似相等) - 传入列表:自定义分位数分割点(如
[0, 0.2, 0.8, 1]表示按 20%、80% 分位数分箱)
- 传入整数
- 其他参数(
right/labels/retbins)与cut完全一致
与cut的核心区别
| 特性 | pd.cut() |
pd.qcut() |
|---|---|---|
| 分箱逻辑 | 按数值等距分箱 | 按分位数等样本量分箱 |
| 适用场景 | 数据分布均匀,需固定区间宽度 | 数据分布不均匀,需保证每个区间样本量均衡 |
| 区间宽度 | 固定相等 | 不固定(由数据分布决定) |
示例
# 基于体重数据分箱
s = df.Weight # 假设df为学生数据,Weight为体重列
# 示例1:3等分位数分箱
print("3等分位数分箱:\n", pd.qcut(s, q=3).head())
# 示例2:自定义分位数分箱
print("\n自定义分位数分箱:\n", pd.qcut(s, q=[0, 0.2, 0.8, 1]).head())
2.一般区间的构造
(1)pd.Interval:单个区间对象
用于表示单个区间,核心要素:左端点、右端点、开闭状态。
核心属性与方法
| 属性 / 方法 | 作用 |
|---|---|
left/right |
区间左 / 右端点 |
mid |
区间中点 |
length |
区间长度 |
closed |
区间开闭状态(right/left/both/neither) |
in 运算符 |
判断元素是否属于区间 |
overlaps() |
判断两个区间是否有交集 |
示例
# 创建左开右闭区间(0,1]
my_interval = pd.Interval(0, 1, 'right')
print("区间对象:", my_interval)
print("0.5是否在区间内:", 0.5 in my_interval) # True
# 创建左闭右开区间[0.5,1.5)
my_interval_2 = pd.Interval(0.5, 1.5, 'left')
print("两个区间是否相交:", my_interval.overlaps(my_interval_2)) # True
(2)pd.IntervalIndex:区间索引对象
用于存储一组区间,要求所有区间开闭状态一致,支持 4 种构造方法:
(a)from_breaks():按分割点构造(类似cut)
直接传入自定义分割点,生成连续区间:
# 按[1,3,6,10]分割,生成[1,3],[3,6],[6,10],左右闭区间
idx1 = pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
print(idx1)
(b)from_arrays():按左右端点列表构造
分别传入左端点、右端点列表,支持非连续 / 有交集的区间:
idx2 = pd.IntervalIndex.from_arrays(
left = [1,3,6,10],
right = [5,4,9,11],
closed = 'neither'
)
print(idx2)
(c)from_tuples():按元组列表构造
传入(左端点,右端点)元组列表,适合手动定义区间:
idx3 = pd.IntervalIndex.from_tuples(
[(1,5),(3,4),(6,9),(10,11)],
closed='neither'
)
print(idx3)
(d)interval_range():构造等差区间序列
通过start/end/periods/freq构造等差区间(三量确定即可):
# 从1到5,生成8个等距区间
print(pd.interval_range(start=1, end=5, periods=8))
# 到5结束,8个区间,每个长度0.5
print(pd.interval_range(end=5, periods=8, freq=0.5))
(e)补充:直接构造IntervalIndex
直接传入Interval对象列表,会强制统一开闭状态:
# 两个不同开闭的区间,强制转为左闭右开
idx4 = pd.IntervalIndex([my_interval, my_interval_2], closed='left')
print(idx4)
- 区间分类是特殊的有序分类,区间顺序由左端点大小决定
- 底层存储为整数编码,支持排序、比较、分组等操作,效率远高于字符串区间
IntervalIndex是 pandas 的专用索引类型,可作为 DataFrame 的索引,支持区间查找、切片等操作
错误操作 问题 正确做法 cut分箱时遗漏边界值最小值 / 最大值被排除 利用 pandas 自动边界调整,或手动指定 right=Falseqcut分箱时数据量不足分位数重复导致报错 减少分箱数,或用 duplicates='drop'处理IntervalIndex中区间开闭不一致强制转换导致区间逻辑错误 构造时统一 closed参数直接修改区间类别 报错( categories不可直接修改)用 cat对象的专用方法操作
3.区间的属性与方法
(1)从 cut/qcut 结果转换为 IntervalIndex
cut/qcut 的返回结果是 Categorical 类型,若要使用区间索引的专属属性 / 方法,需要先将其转换为 IntervalIndex:
import pandas as pd
import numpy as np
# 示例:对体重列分箱后转为IntervalIndex
s = df.Weight # 假设df为学生数据,Weight为体重列
id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 3等距分箱后转为区间索引
# 切片查看前3个区间
print(id_interval[:3])
# 输出:
# IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],
# closed='right', name='Weight', dtype='interval[float64]')
(2)IntervalIndex 核心属性
IntervalIndex 继承了单个 Interval 的核心属性,批量获取所有区间的端点、中点、长度:
| 属性 | 作用 | 示例输出 |
|---|---|---|
left |
所有区间的左端点 ,返回 Float64Index |
Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64') |
right |
所有区间的右端点 ,返回 Float64Index |
Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64') |
mid |
所有区间的中点 (左右端点均值),返回 Float64Index |
Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64') |
length |
所有区间的长度 (右端点 - 左端点),返回 Float64Index |
Float64Index([18.387999999999998, 18.334000000000003, 18.333, 18.387999999999998, 18.333], dtype='float64') |
示例
# 选取前5个区间做演示
id_demo = id_interval[:5]
print("前5个区间:\n", id_demo)
# 批量获取属性
print("\n左端点:\n", id_demo.left)
print("右端点:\n", id_demo.right)
print("中点:\n", id_demo.mid)
print("区间长度:\n", id_demo.length)
(3)IntervalIndex 核心方法
(a)contains():批量判断元素是否在区间内
逐个判断每个区间是否包含指定元素,返回布尔数组:
# 判断4是否在每个区间内
print(id_demo.contains(4))
# 输出:array([False, False, False, False, False])
# 原因:所有区间的左端点都远大于4,因此无包含关系
(b)overlaps():批量判断区间交集
逐个判断每个区间是否与指定的 pd.Interval 对象有交集,返回布尔数组:
# 判断每个区间是否与(40,60)有交集
print(id_demo.overlaps(pd.Interval(40, 60)))
# 输出:array([ True, True, False, True, False])
# 解析:
# 1. (33.945, 52.333] 与 (40,60) 相交 → True
# 2. (52.333, 70.667] 与 (40,60) 相交 → True
# 3. (70.667, 89.0] 与 (40,60) 无交集 → False
# 4. (33.945, 52.333] 与 (40,60) 相交 → True
# 5. (70.667, 89.0] 与 (40,60) 无交集 → False
IntervalIndex是 pandas 专门为区间数据设计的索引类型,底层存储区间的端点信息,支持向量化操作,效率远高于循环处理继承了
Index的所有特性(如切片、筛选),同时新增了区间专属的属性和方法与
Categorical类型的区别:IntervalIndex是索引类型,可直接作为 DataFrame 的索引,支持区间查找、切片等操作;Categorical是列类型,用于分类数据存储直接对
cut/qcut的Categorical结果调用IntervalIndex方法:必须先转换为IntervalIndex忽略区间开闭状态:
contains()/overlaps()会严格遵循区间的开闭规则非数值区间的属性计算:
mid/length仅支持数值型区间,时间区间需用Timedelta计算