第九章 分类数据
import numpy as np
import pandas as pd
一、cat对象
1. cat对象的属性
在pandas 中提供了category 类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype 方法。
在分类类型的Series定义了cat对象
df=pd.read_csv('../data/learn_pandas.csv',usecols=['Grade', 'Name', 'Gender', 'Height', 'Weight'])
s=df.Grade.astype('category')
s.head()
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 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。
【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_unordered 和reorder_categories 互相转化 后者传入的参数必须是由当前序列的无序类别构成的列表,不能够增加新的类别,也不能缺少原来的类别,并且必须指定参数ordered=True ,否则方法无效
【NOTE】类别不得直接修改
如果不想指定ordered=True 参数,那么可以先用s.cat.as_ordered() 转化为有序类别,再利用reorder_categories 进行具体的相对大小调整。
【END】
2. 排序和比较
在第二章中,曾提到了字符串和数值类型序列的排序,此时就要说明分类变量的排序:只需把列的类型修改为category 后,再赋予相应的大小关系,就能正常地使用sort_index 和sort_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()
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()
res4=df.Grade<=df.Grade.sample(frac=1).reset_index(drop=True)
res4.head()
三、区间类别
1. 利用cut和qcut进行区间构造
区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut 和qcut 方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。 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 可以表示无穷大) 另外两个常用参数为labels 和retbins ,分别代表了区间的名字和是否返回分割点(默认不返回) qcut qcut 和cut 几乎没有差别,只是把bins 参数变成q参数,qcut中的q是指quantile,这里的q为整数n的时,指按照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])
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 的功能类似于cut 或qcut 函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:
#一个等差的区间序列由起点,终点,区间个数和区间长度决定,其中三个量确定的情况下,剩下一个量就确定了,interval_range中的start,end,periods,freq参数就对应了这四个量,从而能够构造出相应的区间
my_interval=pd.Interval(0,1,'right')
0.5 in my_interval
my_interval_2=pd.Inteval(0.5,1.5,'left')
my_interval.overlaps(my_interval_2)
pd.IntervalIndex.from_breaks([1,3,6,10],closed='both')
pd.IntervalIndex.from_arrays(left=[1,3,6,10],right=[5,4,9,11],closed='neither')
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 还有两个常用方法,包括contains 和overlaps ,分别指逐个判断每个区间是否包含某元素,以及是否和一个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
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')
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)
df_count=df_new.groupby(['A','B']).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')
res=res.mask(res.isna(),0).astype('int')
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())
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
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)
- 分别对
df.cut 在object 类型和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()
- 钻石的切割质量可以分为五个等级,由次到好分别是
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])
- 分别采用两种不同的方法,把
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])
|