import pandas as pd
import numpy as np
(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
(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) 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']
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]]
对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态,其中开闭状态可以指定 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]')
无论是 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]')
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])
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 |
现有一份关于钻石的数据集,其中 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 |