编程实践(Pandas)Task09

本文详细介绍了Pandas中如何处理分类数据,包括将序列转换为category类型,利用cat对象进行类别增删改查,以及有序分类的创建与排序。此外,还探讨了区间类型的构造,如cut和qcut函数的使用,以及IntervalIndex的创建与操作。内容涵盖了从基本概念到高级应用,旨在帮助读者深入理解并熟练运用Pandas处理分类与区间数据。
摘要由CSDN通过智能技术生成

一、cat对象

1. cat对象的属性

pandas 中提供了 category 类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用 astype 方法。

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]

在一个分类类型的 Series 中定义了 cat 对象,它和 str 对象类似,定义了一些属性和方法来进行分类类别的操作。

s.cat
<pandas.core.arrays.categorical.CategoricalAccessor object at 0x0000021206AFAFD0>

对于一个具体的分类,有两个组成部分,都可以通过 cat 的属性被访问:

  • 其一为类别的本身,它以 Index 类型存储;
  • 其二为是否有序。
s.cat.categories # 类别本身
Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')
s.cat.ordered # 是否有序
False

另外,每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于 cat.categories 中的顺序,该属性可以通过 codes 访问:

s.cat.codes.head()
0    0
1    0
2    2
3    3
4    3
dtype: int8

2. 类别的增加、删除和修改

类别不得直接修改
索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。

动作函数
类别增加add_categories
类别删除remove_categories
设置新类别set_categories
删除未出现在序列中的类别remove_unused_categories
序列修改rename_categories

对于类别的增加可以使用 add_categories

s = s.cat.add_categories('Graduate') # 增加一个毕业生类别
s.cat.categories
Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')

若要删除某一个类别可以使用 remove_categories ,同时所有原来序列中的该类会被设置为缺失。例如,删除大一的类别:

s = s.cat.remove_categories('Freshman') # 删除"Freshman"类别
s.cat.categories
Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
s.head()
0          NaN
1          NaN
2       Senior
3    Sophomore
4    Sophomore
Name: Grade, dtype: category
Categories (4, object): [Junior, Senior, Sophomore, Graduate]

此外可以使用 set_categories 直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。

s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士
s.cat.categories
Index(['Sophomore', 'PhD'], dtype='object')
s.head()
0          NaN
1          NaN
2          NaN
3    Sophomore
4    Sophomore
Name: Grade, dtype: category
Categories (2, object): [Sophomore, PhD]

如果想要删除未出现在序列中的类别,可以使用 remove_unused_categories 来实现:

s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
s.cat.categories
Index(['Sophomore'], dtype='object')
s.head()
0          NaN
1          NaN
2          NaN
3    Sophomore
4    Sophomore
Name: Grade, dtype: category
Categories (1, object): [Sophomore]

修改的操作可以通过 rename_categories 方法完成,同时需要注意的是,这个方法会对原序列的对应值也进行相应修改。

s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})
s.cat.categories
Index(['本科二年级学生'], dtype='object')
s.head()
0        NaN
1        NaN
2        NaN
3    本科二年级学生
4    本科二年级学生
Name: Grade, dtype: category
Categories (1, object): [本科二年级学生]

二、有序分类

1. 序的建立

有序类别和无序类别可以通过 as_unorderedreorder_categories 互相转化,需要注意的是后者传入的参数必须是由当前序列的无序类别构成的列表,不能够增加新的类别,也不能缺少原来的类别,并且必须指定参数 ordered=True ,否则方法无效。

# 对年级高低进行相对大小的类别划分,然后再恢复无序状态:
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]
s = s.cat.reorder_categories(['Freshman', 'Sophomore','Junior', 'Senior'],ordered=True)
s.head()
0     Freshman
1     Freshman
2       Senior
3    Sophomore
4    Sophomore
Name: Grade, dtype: category
Categories (4, object): [Freshman < Sophomore < Junior < Senior]
s.cat.as_unordered().head()
0     Freshman
1     Freshman
2       Senior
3    Sophomore
4    Sophomore
Name: Grade, dtype: category
Categories (4, object): [Freshman, Sophomore, Junior, Senior]

类别不得直接修改
如果不想指定 ordered=True 参数,那么可以先用 s.cat.as_ordered() 转化为有序类别,再利用 reorder_categories 进行具体的相对大小调整。

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']
s1 = s.cat.as_ordered()
s1.head()
0     Freshman
1     Freshman
2       Senior
3    Sophomore
4    Sophomore
Name: Grade, dtype: category
Categories (4, object): ['Freshman' < 'Junior' < 'Senior' < 'Sophomore']
s1.cat.reorder_categories(['Freshman', 'Sophomore','Junior', 'Senior']).head()
0     Freshman
1     Freshman
2       Senior
3    Sophomore
4    Sophomore
Name: Grade, dtype: category
Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']

2. 排序和比较

分类变量的排序:只需把列的类型修改为 category 后,再赋予相应的大小关系,就能正常地使用 sort_indexsort_values

df.Grade = df.Grade.astype('category')
df.Grade = df.Grade.cat.reorder_categories(['Freshman',
                                            'Sophomore',
                                            'Junior',
                                            'Senior'],ordered=True)
df.Grade.head()
0     Freshman
1     Freshman
2       Senior
3    Sophomore
4    Sophomore
Name: Grade, dtype: category
Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']
df.sort_values('Grade').head() # 值排序
GradeNameGenderHeightWeight
0FreshmanGaopeng YangFemale158.946.0
105FreshmanQiang ShiFemale164.552.0
96FreshmanChangmei FengFemale163.856.0
88FreshmanXiaopeng HanFemale164.153.0
81FreshmanYanli ZhangFemale165.152.0
df.set_index('Grade').sort_index().head() # 索引排序
NameGenderHeightWeight
Grade
FreshmanGaopeng YangFemale158.946.0
FreshmanQiang ShiFemale164.552.0
FreshmanChangmei FengFemale163.856.0
FreshmanXiaopeng HanFemale164.153.0
FreshmanYanli ZhangFemale165.152.0

由于序的建立,因此就可以进行比较操作。分类变量的比较操作分为两类,第一种是 == 或 != 关系的比较,比较的对象可以是标量或者同长度的 Series (或 list ),第二种是 >,>=,<,<= 四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的 categories ,同时要和原序列具有相同的索引。

res1 = df.Grade == 'Sophomore'
res1.head()
0    False
1    False
2    False
3     True
4     True
Name: Grade, dtype: bool
res2 = df.Grade == ['PhD']*df.shape[0]
res2.head()
0    False
1    False
2    False
3    False
4    False
Name: Grade, dtype: bool
res3 = df.Grade <= 'Sophomore'
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) # 打乱后比较
res4.head()
0     True
1     True
2     True
3    False
4     True
Name: Grade, dtype: bool

三、区间类别

1. 利用cut和qcut进行区间构造

首先介绍 cut 的常见用法:
其中,最重要的参数是 bin ,如果传入整数 n ,则代表把整个传入数组的按照最大和最小值等间距地分为 n 段。由于区间默认是左开右闭,需要进行调整把最小值包含进去,在 pandas 中的解决方案是在值最小的区间左端点再减去 0.001*(max-min) ,因此如果对序列 [1,2] 划分为2个箱子时,第一个箱子的范围 (0.999,1.5] ,第二个箱子的范围是 (1.5,2] 。如果需要指定左闭右开时,需要把 right 参数设置为 False ,相应的区间调整方法是在值最大的区间右端点再加上 0.001*(max-min) 。

s = pd.Series([1,2])
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]]
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)]

bins 的另一个常见用法是指定区间分割点的列表(使用 np.infty 可以表示无穷大):

pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])
0    (-inf, 1.2]
1     (1.8, 2.2]
dtype: category
Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]

另外两个常用参数为 labelsretbins ,分别代表了区间的名字和是否返回分割点(默认不返回):

s = pd.Series([1,2])
res = pd.cut(s, bins=2, labels=['small', 'big'], retbins=True)
res[0]
0    small
1      big
dtype: category
Categories (2, object): ['small' < 'big']
res[1] # 该元素为返回的分割点
array([0.999, 1.5  , 2.   ])

从用法上来说, qcut 和 cut 几乎没有差别,只是把 bins 参数变成的 q 参数, qcut 中的 q 是指 quantile 。这里的 q 为整数 n 时,指按照 n 等分位数把数据分箱,还可以传入浮点列表指代相应的分位数分割点。

s = pd.Series([1,2])
pd.qcut(s, q=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]]
s = df.Weight
pd.qcut(s, q=[0,0.2,0.8,1]).head()
0      (44.0, 69.4]
1      (69.4, 89.0]
2      (69.4, 89.0]
3    (33.999, 44.0]
4      (69.4, 89.0]
Name: Weight, dtype: category
Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]

2. 一般区间的构造

对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态,其中开闭状态可以指定 right, left, both, neither 中的一类:

my_interval = pd.Interval(0, 1, 'right')
my_interval
Interval(0, 1, closed='right')

其属性包含了 mid, length, right, left, closed ,分别表示中点、长度、右端点、左端点和开闭状态。

使用 in 可以判断元素是否属于区间:

2 in my_interval
False

使用 overlaps 可以判断两个区间是否有交集:

my_interval_2 = pd.Interval(0.5, 1.5, 'left')
my_interval.overlaps(my_interval_2)
True

一般而言, pd.IntervalIndex 对象有四类方法生成,分别是 from_breaks, from_arrays, from_tuples, interval_range ,它们分别应用于不同的情况:

from_breaks 的功能类似于 cutqcut 函数,只不过后两个是通过计算得到的风格点,而前者是直接传入自定义的分割点:

pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
IntervalIndex([[1, 3], [3, 6], [6, 10]],
              closed='both',
              dtype='interval[int64]')

from_arrays 是分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:

pd.IntervalIndex.from_arrays(left = [1,3,6,10],
                             right = [5,4,9,11],
                             closed = 'neither')
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
              closed='neither',
              dtype='interval[int64]')

from_tuples 传入的是起点和终点元组构成的列表:

pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)],closed='neither')
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
              closed='neither',
              dtype='interval[int64]')

一个等差的区间序列由起点、终点、区间个数和区间长度决定,其中三个量确定的情况下,剩下一个量就确定了, interval_range 中的 start, end, periods, freq 参数就对应了这四个量,从而就能构造出相应的区间:

pd.interval_range(start=1,end=5,periods=8)
IntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],
              closed='right',
              dtype='interval[float64]')
pd.interval_range(end=5,periods=8,freq=0.5)
IntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],
              closed='right',
              dtype='interval[float64]')
练一练

无论是 interval_range 还是下一章时间序列中的 date_range 都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出 interval_range 中四个参数之间的恒等关系。

# 等差数列公式 an = a1 + (n-1)*d
# 所以恒等关系为 end = start + (peroiods-1) * freq

除此之外,如果直接使用 pd.IntervalIndex([...], closed=...) ,把 Interval 类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的 closed 类型,因为 pd.IntervalIndex 只允许存放同一种开闭区间的 Interval 对象。

pd.IntervalIndex([my_interval, my_interval_2], closed='left')
IntervalIndex([[0.0, 1.0), [0.5, 1.5)],
              closed='left',
              dtype='interval[float64]')

3. 区间的属性与方法

IntervalIndex 上也定义了一些有用的属性和方法。同时,如果想要具体利用 cut 或者 qcut 的结果进行分析,那么需要先将其转为该种索引类型:

id_interval = pd.IntervalIndex(pd.cut(s, 3))

与单个 Interval 类型相似, IntervalIndex 有若干常用属性: left, right, mid, length ,分别表示左右端点、两端点均值和区间长度。

id_demo = id_interval[:5] # 选出前5个展示
id_demo
IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
              closed='right',
              name='Weight',
              dtype='interval[float64]')
id_demo.left
Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
id_demo.right
Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')
id_demo.mid
Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')
id_demo.length
Float64Index([18.387999999999998, 18.334000000000003, 18.333,
              18.387999999999998, 18.333],
             dtype='float64')

IntervalIndex 还有两个常用方法,包括 containsoverlaps ,分别指逐个判断每个区间是否包含某元素,以及是否和一个 pd.Interval 对象有交集。

id_demo.contains(4)
array([False, False, False, False, False])
id_demo.overlaps(pd.Interval(40,60))
array([ True,  True, False,  True, False])

四、练习

Ex1:统计未出现的类别

在第五章中介绍了 crosstab 函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:

df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})
pd.crosstab(df.A, df.B)

B	cat	dog
A		
a	2	0
b	1	0
c	0	1

但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在 crosstab 结果中也进行汇总,则可以指定 dropna 参数为 False

df.B = df.B.astype('category').cat.add_categories('sheep')
pd.crosstab(df.A, df.B, dropna=False)

B	cat	dog	sheep
A			
a	2	0	0
b	1	0	0
c	0	1	0

请实现一个带有 dropna 参数的 my_crosstab 函数来完成上面的功能。

(函数后补)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值