DataWhale & Pandas(分类数据)
学习大纲:
目录
import numpy as np
import pandas as pd
连接对象仅是字符
import pandas as pd
import numpy as np
s = pd.Series(['a', 'b', 'c', 'd'])
s
# 0 a
# 1 b
# 2 c
# 3 d
# dtype: object
s.str.cat(sep=',')
# 'a,b,c,d'
# 如果中间有nan,不特殊说明,通常直接忽略掉
t = pd.Series(['a', 'b', np.nan, 'd'])
t.str.cat(sep=',')
# 'a,b,d'
# 通过指定na_rep参数,做连接时,nan被替换
t.str.cat(sep=',', na_rep='-')
# 'a,b,-,d'
连接对象是一个list
u = pd.Series(['b', 'd', 'a', 'c'], index=[1, 3, 0, 2])
s
# 0 a
# 1 b
# 2 c
# 3 d
# dtype: object
u
# 1 b
# 3 d
# 0 a
# 2 c
# dtype: object
# 如果不指定join参数,则还是会按照list列表中的
# 顺序依次做连接
s.str.cat(u)
# 0 ab
# 1 bd
# 2 ca
# 3 dc
# dtype: object
# 通过指定连接方式,join有left,right,inner,outer四种
# 连接方式,分别安装index对号连接
s.str.cat(u, join='left')
# 0 aa
# 1 bb
# 2 cc
# 3 dd
# dtype: object
一、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()
对于一个具体的分类,有两个组成部分,其一为类别的本身,它以
Index
类型存储,其二为是否有序,它们都可以通过cat
的属性被访问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
属性上定义了若干方法来达到相同的目的。
二、有序分类
1. 序的建立
有序类别和无序类别可以通过
as_unordered
和reorder_categories
互相转化。需要注意的是后者传入的参数必须是由当前序列的无需类别构成的列表,不能够增加新的类别,也不能缺少原来的类别,并且必须指定参数
ordered=True
,否则方法无效。
类别不得直接修改
如果不想指定
ordered=True
参数,那么可以先用s.cat.as_ordered()
转化为有序类别,再利用reorder_categories
进行具体的相对大小调整。
2. 排序和比较
字符串和数值类型序列的排序,就要说明分类变量的排序:只需把列的类型修改为
category
后,再赋予相应的大小关系,就能正常地使用sort_index
和sort_values
由于序的建立,因此就可以进行比较操作。
分类变量的比较操作分为两类,
第一种是
==
或!=
关系的比较,比较的对象可以是标量或者同长度的Series
(或list
),第二种是
>,>=,<,<=
四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories
,同时要和原序列具有相同的索引。
三、区间类别
1. 利用cut和qcut进行区间构造
区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过
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)
。
2. 一般区间的构造
对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态,其中开闭状态可以指定
right, left, both, neither
中的一类
除此之外,如果直接使用
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
的结果进行分析,那么需要先将其转为该种索引类型
IntervalIndex
还有两个常用方法包括
contains
和overlaps
,分别指逐个判断每个区间是否包含某元素,以及是否和一个pd.Interval
对象有交集。
四、练习
Ex1:统计未出现的类别
在第五章中介绍了
crosstab
函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']}) pd.crosstab(df.A, df.B)
但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在
crosstab
结果中也进行汇总,则可以指定dropna
参数为False
: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): idx1 = (s1.cat.categories if s1.dtype.name == 'category' and not dropna else s1.unique()) idx2 = (s2.cat.categories if s2.dtype.name == 'category' and not dropna else s2.unique()) res = pd.DataFrame(np.zeros((idx1.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 df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']}) df.B = df.B.astype('category').cat.add_categories('sheep') my_crosstab(df.A, df.B) my_crosstab(df.A, df.B, dropna=False)
Ex2:钻石数据集
现有一份关于钻石的数据集,其中
carat, cut, clarity, price
分别表示克拉重量、切割质量、纯净度和价格,样例如下:df = pd.read_csv('../data/diamonds.csv') s_obj, s_cat = df.cut, df.cut.astype('category') df.head(3)
- 分别对
df.cut
在object
类型和category
类型下使用nunique
函数,并比较它们的性能。%timeit -n 30 s_obj.nunique() # 3.02 ms ± 443 µs per loop (mean ± std. dev. of 7 runs, 30 loops each) %timeit -n 30 s_cat.nunique() # 1.57 ms ± 513 µs per loop (mean ± std. dev. of 7 runs, 30 loops each)
- 钻石的切割质量可以分为五个等级,由次到好分别是
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', 'Ideal'],ordered=True) df.clarity = df.clarity.astype('category').cat.reorder_categories(['I1', 'SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF'],ordered=True) res = df.sort_values(['cut', 'clarity'], ascending=[False, True]) res.head(3) # carat cut clarity price # 315 0.96 Ideal I1 2801 # 535 0.96 Ideal I1 2826 # 551 0.97 Ideal I1 2830 res.tail(3) # carat cut clarity price # 47407 0.52 Fair IF 1849 # 49683 0.52 Fair IF 2144 # 50126 0.47 Fair IF 2211
- 分别采用两种不同的方法,把
cut, clarity
这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。df.cut = df.cut.cat.reorder_categories(df.cut.cat.categories[::-1]) df.clarity = df.clarity.cat.reorder_categories(df.clarity.cat.categories[::-1]) df.cut = df.cut.cat.codes df.head(3)
- 对每克拉的价格按照分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别
Very Low, Low, Mid, High, Very High
,并把按这两种分箱方法得到的category
序列依次添加到原表中。#参考答案 q = [0, 0.2, 0.4, 0.6, 0.8, 1] point = [-np.infty, 1000, 3500, 5500, 18000, np.infty] avg = df.price / df.carat df['avg_cut'] = pd.cut(avg, bins=point, labels=['Very Low', 'Low', 'Mid', 'High', 'Very High']) df['avg_qcut'] = pd.qcut(avg, q=q, labels=['Very Low', 'Low', 'Mid', 'High', 'Very High']) df.head()
- 第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
- 对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。