Day10-Python有序数据(DataWhale)

import pandas as pd 
import numpy as np

分类数据

一、cat对象

1. cat对象的属性

(1) pandas 中提供了 category 类型,能够处理分类类型的变量

(2) astype将普通序列转换为分类变量

df = pd.read_csv('data/learn_pandas.csv',
                usecols=['Grade','Name','Gender','Height','Weight'])
s = df.Grade.astype('category')
s.head()
0     Freshman
1     Freshman
2       Senior
3    Sophomore
4    Sophomore
Name: Grade, dtype: category
Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']

1. cat对象

s.cat

# 类别种类,以 Index 类型存储
s.cat.categories
Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')
# 类别是否有序
s.cat.ordered
False
# 类别编号:依据categories的顺序
s.cat.codes.head()
0    0
1    0
2    2
3    3
4    3
dtype: int8

2. 类别的增加、删除、修改

(1) 索引 Index 类型是无法用 index_obj[0] = item 来修改,而 categories 被存储在 Index 中,因此使用该方法无法修改

1. 类别增加add_categories

s = s.cat.add_categories('Graduate')
s.cat.categories
Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')

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
Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']

3. 设置序列的新类别set_categories

原来的类别中如果存在元素不属于新类别,那么会被设置为缺失

s = s.cat.set_categories(['Sophomore','PhD'])
s.cat.categories
Index(['Sophomore', 'PhD'], dtype='object')
s.head()
0          NaN
1          NaN
2          NaN
3    Sophomore
4    Sophomore
Name: Grade, dtype: category
Categories (2, object): ['Sophomore', 'PhD']

4. 删除未出现在序列中的类别remove_unused_categories

s = s.cat.remove_unused_categories()
s.cat.categories
Index(['Sophomore'], dtype='object')

5. 类别修改rename_categories

会对原序列的对应值也进行相应修改

s = s.cat.rename_categories({
     'Sophomore':'本科二年级学生'})
s.head()
0        NaN
1        NaN
2        NaN
3    本科二年级学生
4    本科二年级学生
Name: Grade, dtype: category
Categories (1, object): ['本科二年级学生']

二、有序分类

1. 序的建立

(1) as_unordered 转换为无序

(2) reorder_categories 转换为有序,参数必须是由当前序列的无序类别构成的列表,不能够增加新的类别,也不能缺少原来的类别(即:必须包含原来所有的类别,并且不可新增)同时必须指定参数ordered=True

注意: 如果不想指定 ordered=True 参数,那么可以先用s.cat.as_ordered() 转化为有序类别,再利用 reorder_categories 进行具体的相对大小调整。

s = df.Grade.astype('category')
s = 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().head()
0     Freshman
1     Freshman
2       Senior
3    Sophomore
4    Sophomore
Name: Grade, dtype: category
Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']

2. 排序和比较

1. 排序

分类变量的排序:只需把列的类型修改为 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() # 值排序
Grade Name Gender Height Weight
0 Freshman Gaopeng Yang Female 158.9 46.0
105 Freshman Qiang Shi Female 164.5 52.0
96 Freshman Changmei Feng Female 163.8 56.0
88 Freshman Xiaopeng Han Female 164.1 53.0
81 Freshman Yanli Zhang Female 165.1 52.0
df.set_index('Grade').sort_index().head() # 索引排序
Name Gender Height Weight
Grade
Freshman Gaopeng Yang Female 158.9 46.0
Freshman Qiang Shi Female 164.5 52.0
Freshman Changmei Feng Female 163.8 56.0
Freshman Xiaopeng Han Female 164.1 53.0
Freshman Yanli Zhang Female 165.1 52.0

2. 比较

(1) = 或 != 关系的比较,比较的对象可以是标量或者同长度的 Series (或 list)

(2) >,>=,<,<= 四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的 categories ,同时要和原序列具有相同的索引

#标量
res1 = df.Grade == 'Sophomore'
res1.head()
0    False
1    False
2    False
3     True
4     True
Name: Grade, dtype: bool
#同长度的list
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()
0     True
1     True
2    False
3     True
4     True
Name: Grade, dtype: bool

三、区间类别

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

cut和qcut能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值

(1)cut的用法

参数:

bins:如果传入整数 n ,则代表把整个传入数组的按照最大和最小值等间距地分为 n 段。由于区间默认是左开右闭,需要进行调整把最小值包含进去,在pandas中解决方案是在值最小的区间左端点再减去 0.001*(max-min)。如果需要指定左闭右开时,需要把 right 参数设置为 False ,相应的区间调整方法是在值最大的区间右端点再加上 0.001*(max-min)

labels:代表了区间的名字

retbins:代表了是否返回分割点(默认不返回)

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]]
s = pd.Series([1,2])
res = pd.cut(s, bins=2, labels=['small', 'big'], retbins=False)
res
0    small
1      big
dtype: category
Categories (2, object): ['small' < 'big']
res = pd.cut(s, bins=2, labels=['small', 'big'], retbins=True)
res
(0    small
 1      big
 dtype: category
 Categories (2, object): ['small' < 'big'],
 array([0.999, 1.5  , 2.   ]))

(2) qcut用法

qcut和cut几乎没有差别,只是把bins参数变成的q参数,qcut中的q是指 quantile。这里的q为整数n时,指按照n等分位数把数据分箱,还可以传入浮点列表指代相应的分位数分割点

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

1. 一般区间的构造

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

1. pd.Interval

my_interval = pd.Interval(0, 1, 'right')
my_interval
Interval(0, 1, closed='right')

其属性包含了 mid, length, right, left, closed ,分别表示中点、长度、右端点、左端点和开闭状态

(1) in判断元素是否属于区间

0.5 in my_interval
True

(2) overlaps判断两个区间是否有交集

my_interval_2 = pd.Interval(0.5, 1.5, 'left')
my_interval.overlaps(my_interval_2)
True

2. pd.IntervalIndex

(1) from_breaks

类似于 cut 或 qcut 函数,但是直接传入自定义的分割点

pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
IntervalIndex([[1, 3], [3, 6], [6, 10]],
              closed='both',
              dtype='interval[int64]')

(2) from_arrays

分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况

pd.IntervalIndex.from_arrays(left = [1,3,6,10],right = [5,4,9,11],closed = 'neither')
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
              closed='neither',
              dtype='interval[int64]')

(3) from_tuples

传入的是起点和终点元组构成的列表

pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)],closed='neither')
IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
              closed='neither',
              dtype='interval[int64]')

3. interval_range

start, end, periods, freq 参数对应等差序列起点、终点、区间个数和区间长度。确定其中三个,第四个即可确定

pd.interval_range(start=1,end=5,periods=8)
IntervalIndex([(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]],
              closed='right',
              dtype='interval[float64]')
pd.interval_range(end=5,periods=8,freq=0.5)
IntervalIndex([(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]],
              closed='right',
              dtype='interval[float64]')

2. 练一练

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

恒等式: end = start+periods*freq

注意: 如果直接使用 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 的结果进行分析,那么需要先将其转为该种索引类型

id_interval = pd.IntervalIndex(pd.cut(s, 3))
id_interval
IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0] ... (33.945, 52.333], (33.945, 52.333], (33.945, 52.333], (70.667, 89.0], (33.945, 52.333]],
              closed='right',
              name='Weight',
              dtype='interval[float64]')

1.IntervalIndex 有若干常用属性: left, right, mid, length ,分别表示左右端点、两端点均值和区间长度

id_demo = id_interval[:5] # 选出前5个展示
id_demo
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]')
id_demo.left
Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
id_demo.right
Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')
id_demo.mid
Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')
id_demo.length
Float64Index([18.387999999999998, 18.334000000000003, 18.333,
              18.387999999999998, 18.333],
             dtype='float64')

2. contains: 逐个判断每个区间是否包含某元素

overlaps: 是否和一个 pd.Interval 对象有交集

id_demo.contains(4)
array([False, False, False, False, False])
id_demo.overlaps(pd.Interval(40,60))
array([ True,  True, False,  True, False])

四、练习

1. 统计未出现的类别

crosstab 函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:

写函数真的是硬伤

df = pd.DataFrame({
     'A':['a','b','c','a'],
                  'B':['cat','cat','dog','cat']})
pd.crosstab(df.A, df.B)
B cat dog
A
a 2 0
b 1 0
c 0 1

但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在 crosstab 结果中也进行汇总,则可以指定 dropna 参数为 False:

df.B = df.B.astype('category').cat.add_categories('sheep')
pd.crosstab(df.A, df.B, dropna=False)
B cat dog sheep
A
a 2 0 0
b 1 0 0
c 0 1 0

请实现一个带有 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
my_crosstab(df.A , df.B , dropna=False)
B cat dog sheep
A
a 2 0 0
b 1 0 0
c 0 1 0

2. 钻石数据集

现有一份关于钻石的数据集,其中 carat, cut, clarity, price 分别表示克拉重量、切割质量、纯净度和价格

df2 = pd.read_csv('data/Diamonds.csv')
df2.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.cut 在 object 类型和 category 类型下使用 nunique 函数,并比较它们的性能

思路: 首先查看现在cut存储的类型是object,之后测试category类型时需要先转换为category类型之后再进行

df = df2.copy()
df['cut'].dtypes
dtype('O')
df['cut'].unique()
array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)
##将df['cut']类型变为category
df.cut = df.cut.astype('category')
s = df.cut
s.unique()
['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']

结果:object类型返回的是数组,category类型返回的是列表,并告知分类变量的数量和类型

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

思路: 首先观察cut、clarity存储的数据,然后需要将cut、clarity变为category类型并且有序

df.clarity = df.clarity.astype('category')
s2 = df.clarity
s2.unique() #上面的等级均在数据中存在
['I1', 'SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']
Categories (8, object): ['I1' < 'SI2' < 'SI1' < 'VS2' < 'VS1' < 'VVS2' < 'VVS1' < 'IF']
#对cut、clarity进行有序处理
df.cut = df.cut.cat.reorder_categories(['Fair','Good','Very Good','Premium','Ideal'],ordered=True)
df.clarity = df.clarity.cat.reorder_categories(['I1','SI2','SI1','VS2','VS1','VVS2','VVS1','IF'],ordered=True)
#进行排序,此处要注意Fasle和True不要写成了字符
df = df.sort_values(by=['cut','clarity'],ascending=[False,True])
df.head(5)
carat cut clarity price
24297 3.22 Ideal I1 12545
17859 2.00 Ideal I1 7204
17902 1.63 Ideal I1 7229
20293 2.16 Ideal I1 8709
22466 2.00 Ideal I1 10494

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

思路: 第一种,是通过rename_categories直接进行名称的变更,第二种采用拼表的方法,存一张主数据表,通过merge拼接后,将原来的值替换出来。第二种也可以实现,但是我觉得不是很好,还有一个想法是通过构造字典,然后使用自定义函数进行替换,但是还没有想好实现的方法

#第一种
df_A = df.copy()
df_A.cut = df_A.cut.cat.rename_categories({
     'Ideal':0, 'Premium':1, 'Good' :2, 'Very Good':3, 'Fair':4})
df_A.clarity = df_A.clarity.cat.rename_categories({
     'I1':7,'SI2':6,'SI1':5,'VS2':4,'VS1':3, 'VVS2':2, 'VVS1':1, 'IF':0})
df_A.head(3)
carat cut clarity price
24297 3.22 0 7 12545
17859 2.00 0 7 7204
17902 1.63 0 7 7229
#第二种
df_B = df.copy()
#构造对照关系
df2 = pd.DataFrame({
     'cut':['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'],
                   'trans':[0,1,2,3,4]})
df3 = pd.DataFrame({
     'clarity':['I1','SI2','SI1','VS2','VS1','VVS2','VVS1','IF'],
                   'order':[7,6,5,4,3,2,1,0]})
#进行merge左连接
df_B = df_B .merge(df2,on='cut',how='left')
df_B = df_B .merge(df3,on='clarity',how='left')
#替换列名
df_B[['carat','cut','clarity','price']] = df_B[['carat','trans','order','price']]
df_B = df_B[['carat','cut','clarity','price']]
df_B.head(3)
carat cut clarity price
0 3.22 0 7 12545
1 2.00 0 7 7204
2 1.63 0 7 7229

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

思路: 首先需要求出每克拉的价格,之后使用pcut和cut分别进行分组,注意要补充端点值

df_C = df.copy()
#求出单价
df_C['LabelPrice'] = df_C['price']/df_C['carat']
#按照分位数划分5类,注意此处的q要从0开始,1结尾,否则无法分5类
df_C['cate_q']=pd.qcut(df_C['LabelPrice'], q=[0,0.2,0.4,0.6,0.8,1],labels=['Very Low','Low','Mid','High','Very High'])
#按照分割点划分5类,需要借助np.infty
df_C['cate_c'] = pd.cut(df_C['LabelPrice'], bins = [-np.infty,1000,3500,5500,18000,np.infty],labels=['Very Low','Low','Mid','High','Very High'])
df_C.head(5)
carat cut clarity price LabelPrice cate_q cate_c
24297 3.22 Ideal I1 12545 3895.962733 Mid Mid
17859 2.00 Ideal I1 7204 3602.000000 Mid Mid
17902 1.63 Ideal I1 7229 4434.969325 High Mid
20293 2.16 Ideal I1 8709 4031.944444 High Mid
22466 2.00 Ideal I1 10494 5247.000000 High Mid

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

思路: 首先确认存在的类别,之后使用类别的删除,删除未使用的类别

df_C['cate_q'].unique()
['Mid', 'High', 'Very Low', 'Very High', 'Low']
Categories (5, object): ['Very Low' < 'Low' < 'Mid' < 'High' < 'Very High']
df_C['cate_c'].unique()
['Mid', 'Low', 'High']
Categories (3, object): ['Low' < 'Mid' < 'High']
#cate_c中Very Low和Very High未出现,需要删除
df_C['cate_c'] = df_C['cate_c'].cat.remove_categories(['Very Low','Very High'])
#查看删除类别后的cate_c
df_C['cate_c'].cat.categories
Index(['Low', 'Mid', 'High'], dtype='object')

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

思路: 首先需要将分箱转变为IntervalIndex

df6 = df_C.copy()
#获取分位数数据
interval = pd.IntervalIndex(pd.qcut(df6['LabelPrice'], q=[0,0.2,0.4,0.6,0.8,1]))
#获取左、右端点和长度
df_left = pd.Series(interval.left)
df_right = pd.Series(interval.right)
df_length = pd.Series(interval.length)
#拼接为DataFrame
df_D = pd.concat([df_left,df_right,df_length],axis=1)
#重命名列
df_D.columns = ['df_left','df_right','df_length']
#将LabelPrice列并入,此处一定要注意将index重置,否则拼接后数据出现错误
df_D['LabelPrice'] = df6['LabelPrice'].reset_index(drop=True)
#将LabelPrice列提前
df_D = df_D[['LabelPrice','df_left','df_right','df_length']]
df_D.head(3)
LabelPrice df_left df_right df_length
0 3895.962733 3073.293 4031.683 958.39
1 3602.000000 3073.293 4031.683 958.39
2 4434.969325 4031.683 5456.343 1424.66

你可能感兴趣的:(Python,python,数据分析)