Pandas分类数据
cat对象
cat对象的属性
在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。
对于一个具体的分类,有两个组成部分,其一为类别的本身,它以Index类型存储,其二为是否有序,它们都可以通过cat的属性被访问。另外,每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序,该属性可以通过codes访问。
类别的增加、删除和修改
对于类别的增加可以使用add_categories。
若要删除某一个类别可以使用remove_categories,同时所有原来序列中的该类会被设置为缺失。
此外可以使用set_categories直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。
如果想要删除未出现在序列中的类别,可以使用remove_unused_categories来实现。rename_categories方法进行修改完成,同时需要注意的是,这个方法会对原序列的对应值也进行相应修改。
有序分类
序的建立
有序类别和无序类别可以通过as_unordered和reorder_categories互相转化,需要注意的是后者传入的参数必须是由当前序列的无需类别构成的列表,不能够增加新的类别,也不能缺少原来的类别,并且必须指定参数ordered=True,否则方法无效。
排序和比较
只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。
由于序的建立,因此就可以进行比较操作。分类变量的比较操作分为两类,第一种是==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list),第二种是>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
区间类别
利用cut和qcut进行区间构造
区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。
首先介绍cut的常见用法:
其中,最重要的参数是bin,如果传入整数n,则代表把整个传入数组的按照最大和最小值等间距地分为n段。
bins的另一个常见用法是指定区间分割点的列表(使用np.infty可以表示无穷大)。
另外两个常用参数为labels和retbins,分别代表了区间的名字和是否返回分割点(默认不返回)。
从用法上来说,qcut和cut几乎没有差别,只是把bins参数变成的q参数,qcut中的q是指quantile。这里的q为整数n时,指按照n等分位数把数据分箱,还可以传入浮点列表指代相应的分位数分割点。
一般区间的构造
对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态,其中开闭状态可以指定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函数,只不过后两个是通过计算得到的风格点,而前者是直接传入自定义的分割点。from_arrays是分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况。from_tuples传入的是起点和终点元组构成的列表。一个等差的区间序列由起点、终点、区间个数和区间长度决定,其中三个量确定的情况下,剩下一个量就确定了,interval_range中的start, end, periods, freq参数就对应了这四个量,从而就能构造出相应的区间。
区间的属性与方法
IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型。与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两端点均值和区间长度。IntervalIndex还有两个常用方法,包括contains和overlaps,分别指逐个判断每个区间是否包含某元素,以及是否和一个pd.Interval对象有交集。
练习
EX1:统计未出现的类别
// An highlighted block
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:钻石数据集
// An highlighted block
df = pd.read_csv('../data/diamonds.csv')
s_obj, s_cat = df.cut, df.cut.astype('category')
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)
res.tail(3)
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 # 方法一:利用cat.codes
clarity_cat = df.clarity.cat.categories
df.clarity = df.clarity.replace(dict(zip(clarity_cat, np.arange(len(clarity_cat))))) # 方法二:使用replace映射
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()
df.avg_cut.unique()
df.avg_cut.cat.categories
df.avg_cut = df.avg_cut.cat.remove_categories(['Very Low', 'Very High'])
df.avg_cut.head(3)
interval_avg = pd.IntervalIndex(pd.qcut(avg, q=q))
interval_avg.right.to_series().reset_index(drop=True).head(3)
interval_avg.left.to_series().reset_index(drop=True).head(3)
interval_avg.length.to_series().reset_index(drop=True).head(3)