Task09|分类数据

第九章 分类数据

import numpy as np
import pandas as pd

一、cat对象

1. cat对象的属性

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

在分类类型的Series定义了cat对象

  • 类别本身(Index类型存储)

  • 是否有序
    可以通过cat的属性(ordered)被访问

  • 整数编号(取决于cat.categories中的顺序)

#转化普通序列为分类变量
df=pd.read_csv('../data/learn_pandas.csv',usecols=['Grade', 'Name', 'Gender', 'Height', 'Weight'])
s=df.Grade.astype('category')
s.head()
#cat对象
s.cat
#类别本身
s.cat.categories
#是否有序
s.cat.ordered
s.cat.codes
s.cat.codes.head()

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

通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?

【NOTE】类别不得直接修改

在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandascat 属性上定义了若干方法来达到相同的目的。

【END】

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

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

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

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

最后,“增改查删”中还剩下修改的操作,这可以通过rename_categories方法完成,同时需要注意的是,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生

#增加一个毕业生类别
s=s.cat.add_categories('Graduate')
#删除一个类别
s=s.cat.remove_categories('Freshman')
s.cat.categories
s.head()
#设置序列的新类别
s=s.cat.set_categories(['Sophomore','PhD'])
s.cat.categories
#移除了未出现的博士生类别
s=s.cat.remove_unused_categories()
s.cat.categories
#对原序列进行重命名
s=s.cat.rename_categories({'Sophomore':'本科二年级学生'})
s.head()

二、有序分类

1. 序的建立

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

【NOTE】类别不得直接修改

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

【END】

2. 排序和比较

在第二章中,曾提到了字符串和数值类型序列的排序,此时就要说明分类变量的排序:只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_indexsort_values。例如,对年级进行排序:

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

s=df.Grade.astype('category')
#重新排序
s=s.cat.reorder_categories(['Freshman','Sophomore','Junior','Senior'],ordered=True)
s.head()
#变成无序类别
s.cat.as_unordered().head()
#序的建立

#变成有序类别
s.cat.as_ordered()
#排序
df.Grade = df.Grade.astype('category')
#重新排序
df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)#从小到大
df.sort_values('Grade').head() # 值排序
#设置索引为Grade,并根据索引进行排序
df.set_index('Grade').sort_index().head() # 索引排序

#比较
res1=df.Grade=='Sophomore'
res1.head()
res2=df.Grade==['PhD']*df.shape[0]
res2.head()
res3=df.Grade<='Sophomore'
res3.head()#sample从序列中随机抽取n个元素
res4=df.Grade<=df.Grade.sample(frac=1).reset_index(drop=True)#打乱后比较
res4.head()

三、区间类别

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

区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cutqcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。
cut
首先介绍cut的常见用法:

  • bins
    如果传入整数n,代表把整个传入数组按照最大和最小值等间距分为n段。由于区间默认是左开右闭,需要在调整的时候把最小值包含进去
    值在最小区间左端点减去0.001*(max-min)如果对序列[1,2]划分2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2].如果需要指定左闭右开时,需要把right参数设置为false。相应区间的调整方法是在值最大的区间右端点加上0.001*(max-min)
    bins的另一个常见用法是指定区间分割点的列表(使用np.infty可以表示无穷大)
    另外两个常用参数为labelsretbins,分别代表了区间的名字和是否返回分割点(默认不返回)
    qcut
    qcutcut几乎没有差别,只是把bins参数变成q参数,qcut中的q是指quantile,这里的q为整数n的时,指按照n等分把数据分箱,还可以传入浮点列表指代相应的分位数分割点
#bins,n等分切割,指定区间分割点
s=pd.Series([1,2])
pd.cut(s,bins=2)
pd.cut(s,bins=2,right=False)
pd.cut(s,bins=[-np.infty,1.2,1.8,2.2,np.infty])
#labels和retbins
s=pd.Series([1,2])
res=pd.cut(s,bins=2,labels=['small','big'],retbins=True)
res[0]
res[1]
s=df.Weight
pd.qcut(s,q=3).head()
pd.qcut(s,q=[0,0,2,0.8,1]).head()

2. 一般区间的构造

对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态,其中开闭状态可以指定right, left, both, neither中的一类:
右闭,左闭,都闭,都不闭
mid, length, right, left, closed,,分别表示中点、长度、右端点、左端点和开闭状态。
使用in可以判断元素是否属于区间
使用overlaps可以判断两个区间是否有交集
一般而言,pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:

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

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

#pd.Interval
my_interval=pd.Interval(0,1,'right')
# 第三个参数指定右闭
# 1. in
0.5 in my_interval
# 2. overlaps
my_interval_2=pd.Inteval(0.5,1.5,'left')
my_interval.overlaps(my_interval_2)

#pd.IntervalIndex
#from_breaks类似cut或者qcut函数
pd.IntervalIndex.from_breaks([1,3,6,10],closed='both')
#from_arrays分别出纳入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况
pd.IntervalIndex.from_arrays(left=[1,3,6,10],right=[5,4,9,11],closed='neither')
#from_tuples传入的起点和终点元组构成的列表

pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11),closed='neither'])
pd.interval_range(start=1,end=5,periods=8)
pd.interval_range(end=5,periods=8,freq=0.5)

【练一练】

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

end=start+freq*periods

【END】

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

my_interval
my_interval_2
pd.IntervalIndex([my_interval,my_interval_2],closed='left')

3. 区间的属性与方法

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

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

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

id_interval=pd.IntervalIndex(pd.cut(s,3))
id_interval[:3]
id_demo=id.interval[:5]
id_demo
#区间的属性
id_demo.left
id_demo.right
id_demo.mid
id_demo.length
#contains区间是否包含某元素,overlaps判断是否有交集
id_demo.contains(4)
id_demo.overlaps(pd.Inteval(40,60))

四、练习

Ex1:统计未出现的类别

在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:
但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False

df=pd.DataFrame({'A':['a','b','c','a'],
'B':['cat','cat','dog','cat']})
pd.crosstab(df.A,df.B)
#对两个列的组合出现的频数进行汇总
df.B=df.B.astype('category').cat.add_categories('sheep')
#对未出现的类别在crosstab结果进行汇总
pd.crosstab(df.A,df.B,dropna=False)

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

def my_crosstab(s1,s2,dropna=True):

	df_new=pd.concat([s1,s2],axis=1)
	#新增一列
	df_new['count']=pd.Series([0]*s1.size)
	#分组统计a+b,作为直接查询表
	df_count=df_new.groupby(['A','B']).count()
	#改变count列
	#用于查询出现次数
	def myfunc(x):
		return df_count.loc[(x[0],x[1])]
	df_new['count']=df_new.apply(myfunc,axis=1)
	#取唯一值后,进行长卷表的转换
	res=df_new.drop_duplicates().pivot(index='A',columns='B',values='count')
	#将np.nan替换为0并转换为dtype类型
	res=res.mask(res.isna(),0).astype('int')
#mask函数,如果满足第一个条件就替换成第二个参数
	if dropna!=True:
	#如果选择了删除缺失值
		index=2
		length=res.shape[0]
		for x in s2.cat.categories:
			if x not in res.columns:
				res.insert(index,x,[0]*length)
	return res

def my_crosstab(s1,s2,dropna=True):
    idx1=(s1.cat.categories is s1.dtype.name =='category' and 
        not dropna else s1.unique())
    #如果s1的类别是种类并且不舍弃缺失值 
    idx2=(s2.cat.categories is s2.dtype.name =='category' ans
         not dropna else s2.unique())
    res = pd.DataFrame(np.zeros((idx.shape[0],idx2.shape[0])),
                      index=idx1,columns=idx2)
    for i,j in zip(s1,s2):
        res.at[i,j]+=1
        #如果满足在组合的条件中,那么相应位置+1
    res=res.rename_axis(index=s1.name,columns=s2.name).astype('int')
    return res

Ex2:钻石数据集

现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:

df=pd.read_csv('../data/diamonds.csv')
df.head(3)

  1. 分别对df.cutobject类型和category类型下使用nunique函数,并比较它们的性能。
    category的性能更好

import timeit
start=timeit.default_timer()
df.cut.nunique();
end=timeit.default_timer()
print('object datetime',end-start)
start=timeit.default_timer()
df_cut.nunique();
end=timeit.default_timer()
print('category datetime',end-start)

cell%timeit -n 30 df.cut.nunique()
cell%timeit -n 30 df_cut.nunique()

在这里插入图片描述
在这里插入图片描述

  1. 钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
df.cut=df.cut.astype('category').cat.reorder_categories(['Fair', 'Good', 'Very' 'Good', 'Premium', 'Idea'])
#重新排序
df.clarity=df.clarity.astype('category').cat.reorder_categories(['I1', 'SI2',' SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF'])
res=df.sort_values(['cut','clarity'],ascending=[False,True])
  1. 分别采用两种不同的方法,把cut, clarity这两列按照由到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
    codes赋予整数编号
df.cut=df.cut.astype('category').cat.reorder_categories(['Fair', 'Good', 'Very' 'Good', 'Premium', 'Idea'])
#重新排序
df.clarity=df.clarity.astype('category').cat.reorder_categories(['I1', 'SI2',' SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF'])
res=df.sort_values(['cut','clarity'],ascending=[False,True])
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值