一、作业
Ex2:钻石数据集 现有一份关于钻石的数据集,其中 carat, cut, clarity, price 分别表示克拉重量、切割质量、纯净度和价格。
1 分别对 df.cut 在 object 类型和 category 类型下使用 nunique 函数,并比较它们的性能。
2 钻石的切割质量可以分为五个等级,由次到好分别是 Fair, Good, Very Good, Premium, Ideal ,纯净度有八个等级,由次到好分别是 I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF ,请对切割质量按照 由好到次 的顺序排序,相同切割质量的钻石,按照纯净度进行 由次到好 的排序。
3 分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
4 对每克拉的价格按照分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别 Very Low, Low, Mid, High, Very High ,并把按这两种分箱方法得到的 category 序列依次添加到原表中。
5 第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
6 对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
解答:
df = pd.read_csv('data/diamonds.csv')
df.head(3)
2.1
%timeit df.cut.nunique()
#5.58 ms ± 1.45 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)
df1 = df.cut.astype('category')
%timeit df1.nunique()
#1.8 ms ± 135 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
2.2
df.cut = df.cut.astype('category').cat.reorder_categories( [ 'Fair', 'Good', 'Very Good', 'Premium', 'Ideal' ] ,ordered=True)
df.clarity = df.clarity.astype('category').cat.reorder_categories( [ 'I1', 'SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF'] ,ordered=True)
df.sort_values(['cut', 'clarity'], ascending=[False , True])
2.3
cut1 = df.cut.astype('category').cat.rename_categories({ 'Fair': 0 , 'Good':1 , 'Very Good':2, 'Premium':3, 'Ideal' :4 })
clarity1 = df.clarity.astype('category').cat.rename_categories( { 'I1':0 , 'SI2': 1, 'SI1':2, 'VS2':3, 'VS1':4, 'VVS2':5, 'VVS1':6, 'IF':7})
df_3 = df.copy()
df_3.cut = cut1
df_3.clarity = clarity1
df_3
2.4
#对每克拉的价格按照分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点
# 进行分箱得到五个类别 Very Low, Low, Mid, High, Very High ,并把按这两种分箱方法得到的 category 序列依次添加到原表中。
s = df_3.price
res = pd.cut(s, bins=[-np.infty,1000, 3500, 5500, 18000, np.infty], labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'], retbins=True)
df_3['Price Level'] = res[0]
df_3
res1 = pd.qcut(s, q=[0,0.2, 0.4, 0.6, 0.8, 1], labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'], retbins=True)
df_3['Price Q Level'] = res1[0]
df_3
2.5
# 第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除
# 出现了所有类别
res[0].cat.remove_unused_categories()
2.6
#对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
s = df_3.price
s_cut = pd.qcut(s, q=[0,0.2, 0.4, 0.6, 0.8, 1])
s_cut_interv = pd.IntervalIndex(s_cut)
s_cut_interv.length
二、cat对象
在说cat对象的使用前,先说一下Category这个数据类型,它的作用很强大。虽然我们没有经常性的在内存中运行上g的数据,但是我们也总会遇到执行几行代码会等待很久的情况。使用Category数据的一个好处就是:可以很好的节省在时间和空间的消耗。 参考文献: https://www.cnblogs.com/java0011/p/13796255.html
df = pd.read_csv('data/learn_pandas.csv',
usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
df.head()
在 pandas 中提供了 category 类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用 astype 方法。
- category 的创建
s = df.Grade.astype('category')
s#Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']
- 类别的增加、删除和修改
2.1 对于类别的增加可以使用 add_categories
s = s.cat.add_categories('Graduate') # 增加一个毕业生类别
s.cat.categories # Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
2.2 删除某一个类别可以使用 remove_categories
s = s.cat.remove_categories('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
2.3 可以使用 set_categories 直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。
s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士
s.cat.categories # Index(['Sophomore', 'PhD'], dtype='object')
2.4 如果想要删除未出现在序列中的类别,可以使用remove_unused_categories
s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
s.cat.categories #Index(['Sophomore'], dtype='object')
2.5 修改的操作,这可以通过 rename_categories 方法完成
s = s.cat.rename_categories({'Sophomore':'本科二年级学生'}) #需要传入一个字典
s.head()
三、有序分类
有序类别和无序类别可以通过 as_unordered 和 reorder_categories 互相转化,需要注意的是后者传入的参数必须是由当前序列的有序类别构成的列表,不能够增加新的类别,也不能缺少原来的类别,并且必须指定参数 ordered=True ,否则方法无效。
例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:
s = df.Grade.astype('category')
s = s.cat.reorder_categories(['Freshman', 'Senior' , 'Sophomore', 'Junior'], # 这里列表的顺序决定了序列的顺序 Categories (4, object): ['Freshman' < 'Senior' < 'Sophomore' < 'Junior']
ordered=True)
s
s.cat.as_unordered().head() # 恢复无序 Categories (4, object): ['Freshman', 'Senior', 'Sophomore', 'Junior']
3.2 排序和比较
在第二章中,曾提到了字符串和数值类型序列的排序,此时就要说明分类变量的排序:只需把列的类型修改为 category 后,再赋予相应的大小关系,就能正常地使用 sort_index 和 sort_values 。例如,对年级进行排序:
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() # 索引排序
由于序的建立,因此就可以进行比较操作。
分类变量的比较操作分为两类,
第一种是 == 或 != 关系的比较,比较的对象可以是标量或者同长度的 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()
四、区间类别
区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过 cut 和 qcut 方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。
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]]
另外两个常用参数为 labels 和 retbins ,分别代表了区间的名字和是否返回分割点
s = pd.Series([1,2])
res = pd.cut(s, bins=2, labels=['small', 'big'], retbins=True)
res # 返回了一个元组,元组的第一个元素是一个Categories, 第二个是分割点的数组
# ( 0 small
# 1 big
# dtype: category
# Categories (2, object): ['small' < 'big'],
# array([0.999, 1.5 , 2. ]))