第九章 分类数据

一、cat对象

1.作用:使用户能否处理分类类型的变量。

2.如何获取:

​ 1)astype方法将列转换为分类类型的列

​ 2)列.cat获取列的分类类型变量

s=df.Grade.astype('category')
s.cat

3.如何知道分类列中的类别:

s.cat.categories

4.如何知道被分类的列是否有序:

s.cat.ordered

5.如何知道cat里的类别编号:

s.cat.codes

6.如何对类别进行增删改:

1)增加:s.cat.add_categories('Graduate')

2)删除:s.cat.remove_categories('Freshman')

所有原来序列中的该类会被设置为缺失

3)设置新类别:s.cat.set_categories(['Sophomore','PhD'])

所有原来序列中的不是这两个类的会被设置为缺失

4)删除未出现在序列中的类别:s.cat.remove_unused_categories()

即设置了多余的category,把多出的删掉

5)修改:s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})

二、有序分类

有序类别:reorder_categories,传入的参数必须是由当前序列的无需类别构成的列表,不能够增加新的类别,也不能缺少原来的类别,并且必须指定参数 ordered=True ,否则方法无效。

s.cat.reorder_categories(['Freshman', 'Sophomore','Junior', 'Senior'],ordered=True)

s.head()的结果:

0     Freshman
1     Freshman
2       Senior
3    Sophomore
4    Sophomore
Name: Grade, dtype: category
Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']

注:这里的有序并不是排序的意思,看最后一行,只是确定了值大小

无序类别:s.cat.as_unordered()

结果:仍然看最后一行,类型恢复了列表形式

0     Freshman
1     Freshman
2       Senior
3    Sophomore
4    Sophomore
Name: Grade, dtype: category
Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']

2. 排序和比较

sort_index和sort_values

经过上面指定的顺序后,就可以用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')
df.set_index('Grade').sort_index().head()

比较:

  1. == 或 !=:比较对象是标量或同长度的Series(或list)

如:标量:res1 = df.Grade == 'Sophomore'

同长度的list: res2 = df.Grade == ['PhD']*df.shape[0]

  1. ">",">=","<","<=" : 比较对象的元素必须属于原序列的categories,并且和原序列具有相同的索引

如:res3 = df.Grade <= 'Sophomore'

三、区间类别

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

cut

构造区间,返回series的值属于哪个区间

bins参数:

1.传入整数n,则代表把整个传入数组的按照最大和最小值等间距地分为n段。默认左开右闭,左边会-0.001

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]]
'''

2.指定区间分割点的列表(使用np.infty可以表示无穷大)

s = pd.Series([1,2])
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]]
'''
right参数:

默认为true,即左开右闭,改为false后右边增加0.001

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)]
'''
参数labels: 代表区间的名字
参数retbins:代表是否返回分割点(默认不返回)
s = pd.Series([1,2])
res = pd.cut(s, bins=2, labels=['small', 'big'], retbins=True)
res[0]
'''
0    small
1      big
dtype: category
Categories (2, object): ['small' < 'big']
'''
res[1] # 该元素为返回的分割点
'''
array([0.999, 1.5  , 2.   ])
'''

qcut

q参数:

这里的q为整数n时,指按照n等分位数把数据分箱,还可以传入浮点列表指代相应的分位数分割点。

1.q为整数

q=3, 所以分为3个区间 (33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0],可以看出并不是等分的,而是按分位数分的

s = df.Weight
pd.qcut(s, q=3).head()
'''
0    (33.999, 48.0]
1      (55.0, 89.0]
2      (55.0, 89.0]
3    (33.999, 48.0]
4      (55.0, 89.0]
Name: Weight, dtype: category
Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]
'''

2.q传入浮点列表指代相应的分位数分割点

仍然是3个区间,分位数为0.2,0.8

pd.qcut(s, q=[0,0.2,0.8,1]).head()
'''
0      (44.0, 69.4]
1      (69.4, 89.0]
2      (69.4, 89.0]
3    (33.999, 44.0]
4      (69.4, 89.0]
Name: Weight, dtype: category
Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]
'''

2. 一般区间的构造

对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态,其中开闭状态可以指定right, left, both, neither中的一类:

构造一个区间pd.Interval

my_interval = pd.Interval(0, 1, 'right')
my_interval
'''
Interval(0, 1, closed='right')
'''
判断元素是否属于区间in:
0.5 in my_interval
'''
True
'''
判断两个区间是否有交集overlap:
In [52]: my_interval_2 = pd.Interval(0.5, 1.5, 'left')

In [53]: my_interval.overlaps(my_interval_2)
Out[53]: True

构造一组区间pd.IntervalIndex

4种方法:

  • 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')
    #[(1, 5), (3, 4), (6, 9), (10, 11)]
    
  •  pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)],
                                  closed='neither')
    
  • #等差的区间序列由起点、终点、区间个数和区间长度决定,3个参数即可确定区间
    pd.interval_range(start=1,end=5,periods=8)
    '''
    [(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]]
    '''
    pd.interval_range(end=5,periods=8,freq=0.5)
    '''
    [(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]]
    '''
    

练一练

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

啊,这真是目前最简单的练一练了orz

关系:end = start + freq * periods

3. 区间的属性与方法

主要用来分析之前用cut和qcut的构造出来的区间,需要先将它们转为IntervalIndex

id_interval = pd.IntervalIndex(pd.cut(s, 3))

常用属性: left, right, mid,length

In [61]: id_demo = id_interval[:5] # 选出前5个展示

In [62]: id_demo
Out[62]: 
IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
              closed='right',
              name='Weight',
              dtype='interval[float64]')

In [63]: id_demo.left
Out[63]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')

In [64]: id_demo.right
Out[64]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')

In [65]: id_demo.mid
Out[65]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')

In [66]: id_demo.length
Out[66]: 
Float64Index([18.387999999999998, 18.334000000000003, 18.333,
              18.387999999999998, 18.333],
             dtype='float64')

常用方法: containsoverlaps

分别指逐个判断每个区间是否包含某元素,以及是否和一个 pd.Interval 对象有交集。

In [67]: id_demo.contains(4)
Out[67]: array([False, False, False, False, False])

In [68]: id_demo.overlaps(pd.Interval(40,60))
Out[68]: array([ True,  True, False,  True, False])

四、练习

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):
    #如果dropna=True则返回不重复的s1的值,否则直接返回category
    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())
    #构造idx1和idx2长度的dataframe,并设置index和column分别为idx1和idx2
    res = pd.DataFrame(np.zeros((idx1.shape[0], idx2.shape[0])), index=idx1, columns=idx2)
    #计算每一个值出现的次数,dataframe.at方法类似于loc
    for i, j in zip(s1, s2):
        res.at[i, j] += 1
    #重命名索引名称,并设置所有值类型为int
    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)
'''
carat   cut clarity price
0   0.23    Ideal   SI2 326
1   0.21    Premium SI1 326
2   0.23    Good    VS1 327
'''

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

统计时间:%timeit -n 循环次数

s_obj, s_cat = df.cut, df.cut.astype('category')
%timeit -n 30 s_obj.nunique()
%timeit -n 30 s_cat.nunique()

2 钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。

技巧:sort_values可以指定多个columns和对应的升序降序

df.clarity = df.clarity.astype('category')
df.clarity = df.clarity.cat.reorder_categories(['I1', 'SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF'], ordered = True)
df.sort_values(['cut', 'clarity'], ascending=[False, True])

3 分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。

#方法一:
#把categories的顺序倒过来,即['Ideal', 'Premium', 'Very Good', 'Good', 'Fair'],对应的code为0,1,2,3,4
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
#方法二:
clarity_cat = df.clarity.cat.categories
df.clarity = df.clarity.replace(dict(zip(clarity_cat, np.arange(len(clarity_cat)))))

4 对每克拉的价格按照分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。

avg = df.price / df.carat
df['avg_qcut'] = pd.qcut(avg, q=[0,0.2, 0.4, 0.6, 0.8,1],labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'])
df['avg_cut'] = pd.cut(avg, bins=[-np.infty, 1000, 3500, 5500, 18000, np.infty],labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'])

5 第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。

df.avg_cut.cat.remove_unused_categories()

6 对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。

index = pd.IntervalIndex(pd.cut(avg, bins=[-np.infty, 1000, 3500, 5500, 18000, np.infty]))
index.left
index.right
index.length

你可能感兴趣的:(第九章 分类数据)