pandas_day09

import numpy as np
import pandas as pd

9.1 cat对象
9.1.1 cat对象的属性
pandas提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量,可以使用astype方法

df = pd.read_csv('learn_pandas.csv',usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
df.head()
Grade Name Gender Height Weight
0 Freshman Gaopeng Yang Female 158.9 46.0
1 Freshman Changqiang You Male 166.5 70.0
2 Senior Mei Sun Male 188.9 89.0
3 Sophomore Xiaojuan Sun Female NaN 41.0
4 Sophomore Gaojuan You Male 174.0 74.0
# 分类结果Grade一共有4类
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']

在一个分类类型的Series中定义了cat对象,和上一章介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作

s.cat

对于一个具体的分类,有两个组成部分:一个是类别的本身categories,以Index类型存储;二为是否有序ordered,它们都可以通过cat属性被访问

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

每一个序列的类别会被赋予唯一的整数编号codes,它们的编号取决于cat.categories中的顺序,该属性可以通过codes访问
0:Freshman, 1:Junior, 2:Senior, 3:Sophomore

s.cat.codes.head()
0    0
1    0
2    2
3    3
4    3
dtype: int8

9.1.2 类别的增加、删除和修改
通过cat对象的categories属性能够完成对类别的查询
类别不得直接修改:
在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。

# 对于类别的增加可以用add_categories:
s = s.cat.add_categories('Graduate')
s.cat.categories
Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')

删除某一个类别可以用remove_categories, 所有原来序列中的该类会被设置为缺失,如删除大一的类别:

s = s.cat.remove_categories('Freshman')
s.cat.categories
Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
# 被删除的序列置为NaN
s.head()
0          NaN
1          NaN
2       Senior
3    Sophomore
4    Sophomore
Name: Grade, dtype: category
Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']

可以使用set_categories直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失

s = s.cat.set_categories(['Sophomore', 'PhD'])
s.cat.categories
Index(['Sophomore', 'PhD'], dtype='object')
# 原先类别的Senior等都被设置为NaN
s.head(7)
0          NaN
1          NaN
2          NaN
3    Sophomore
4    Sophomore
5          NaN
6          NaN
Name: Grade, dtype: category
Categories (2, object): ['Sophomore', 'PhD']
# 如果想要删除未出现在序列中的类别,可以使用remove_unused_categories来实现
s = s.cat.remove_unused_categories()
# 移除了未出现的PhD类别
s.cat.categories
Index(['Sophomore'], dtype='object')

修改操作可以通过rename_categories方法完成,这个方法会对原序列的对应值也进行相应修改,例如把Sophomore改为中文的 本科二年级学生:

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

9.2 有序分类
9.2.1 序的建立
有序类别和无序类别可以通过as_unordered和reorder_categories 互相转化
reorder_categories传入的参数必须是由当前序列的无需类别构成的列表,不能够增加新的类别,也不能缺少原来的类别,并且必须指定参数ordered=True,否则方法无效
如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:

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

类别不得直接修改
如果不想指定 ordered=True 参数,那么可以先用 s.cat.as_ordered() 转化为有序类别,再利用reorder_categories 进行具体的相对大小调整。9.2.2 排序和比较
分类变量的排序,只需把列的类型修改为category后,再赋予相应大小关系,就能正常地使用sort_index和sort_values。如对年级进行排序:

df.Grade = df.Grade.astype('category')
df.Grade = df.Grade.cat.reorder_categories(['Freshman','Sophomore', 'Junior', 'Senior'], ordered=True)
# 根据Grade从小到大排序
# 对列的值进行排序,根据顺序规则排序
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
# 先设Grade为索引,然后对Grade索引进行排序(所以相比于上一句没有另外的索引)
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

由于序的建立,可以进行比较操作。分类变量的比较操作分为两类,第一种是 == 或!=的比较,比较对象可以是标量或者同长度的Series(或list)。第二种是>, >=,<, <=四类大小关系的比较,比较的对象和第一种类似,但所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引

# 比较对象是标量
res1 = df.Grade == 'Sophomore'
res1.head()
0    False
1    False
2    False
3     True
4     True
Name: Grade, dtype: bool
# 比较对象是同长度的Series
res2 = df.Grade == ['PhD']*df.shape[0]
res2.head()
0    False
1    False
2    False
3    False
4    False
Name: Grade, dtype: bool
# Sophomore大二,大一或大二的返回True
res3 = df.Grade <= 'Sophomore'
res3.head()
0     True
1     True
2    False
3     True
4     True
Name: Grade, dtype: bool
# 打乱之后进行比较
# sample随机抽样,frac为从总体中抽取10%的样本
# 所以每次抽取判断的结果不一样
res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True)
res4.head(10)
0     True
1     True
2    False
3    False
4     True
5     True
6     True
7    False
8     True
9     True
Name: Grade, dtype: bool

9.3 区间类别
9.3.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) 。

s = pd.Series([1,2])
# 左开右闭,左端点减掉0.001*(2-1)
# 会自动将区间进行排序
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]]
# 左闭右开, 右端点加上0.001*(2-1)
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表示无穷大)
# 因为指定在1.2旁边的端点是负无穷,所以不会返回1而是返回负无穷
# 同理1.8右侧的端点是2.2,所以返回[1.8, 2.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]]

另外两个常用参数为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']
# 默认retbins=False,不返回分割点
# 原本res是一个tuple,第0个值为区间结果
res = pd.cut(s, bins=2, labels = ['small','big'], retbins=False)
res[0]
'small'
# 该元素为返回的分割点
res[1]
'big'

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

s = df.Weight
# 按照3分位数把数据分箱:(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]
pd.qcut(s, q=3).head(7)
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]
5      (48.0, 55.0]
6      (48.0, 55.0]
Name: Weight, dtype: category
Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]
# 用0和1指定浮点数分割的最小和最大界限,相当于在1中0.2和0.8为分界
# 实际用 (33.999, 44.0] < (44.0, 69.4] < (69.4, 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]]

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

# 指定的状态为闭
my_interval = pd.Interval(0,1,'right')
my_interval
Interval(0, 1, closed='right')
my_interval = pd.Interval(0,1,'neither')
my_interval
Interval(0, 1, closed='neither')

其属性包含了mid, length, right, left, closed, 分别表示中点、长度、右端点、左端点和开闭状态
使用in可以判断元素是否属于区间

0.99 in my_interval
True
# 1处于开状态,所以是False
1 in my_interval
False

使用overlaps可以判断两个区间是否有交集:

my_interval_2 = pd.Interval(0.8, 1.5, 'left')
# (0,1) 和[0.8,1.5)有交集
my_interval.overlaps(my_interval_2)
True

pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:
from_breaks的功能类似于cut或qcut函数,from_breaks, from_arrays直接传入自定义的分割点;from_tuples, interval_range是通过计算得到的分割点

# 传入自定义分割点,返回由分割点构成的列表
pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
IntervalIndex([[1, 3], [3, 6], [6, 10]],
              closed='both',
              dtype='interval[int64]')

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

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

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

一个等差的区间序列由起点、终点、区间个数和区间长度决定,其中三个两确定的情况下,剩下一个量就确定了, 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]')
# 两种参数给的不一样,说明当没有start或者end时,需要periods和freq来补充说明如何分区间
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.8, 1.5)],
              closed='left',
              dtype='interval[float64]')

9.3.3 区间的属性与方法
IntervalIndex上也定义了一些有用的属性和方法, 如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:

# 按照3分位数把数据分箱:(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]
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]')

与单个Interval类型相似,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]')
# 前5个的左端点
id_demo.left
Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
# 前5个的右端点
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')

IntervalIndex还有两个常用方法,包括contains和overlaps, 分别指逐个判断每个区间是否包含某元素,以及是否和一个pd.Interval对象有交集

# 正如函数其名,判断每个区间是否包含该元素
id_demo.contains(18)
array([False, False, False, False, False])
# 判断是否和(40,60)有交集
id_demo.overlaps(pd.Interval(40,60))
array([ True,  True, False,  True, False])

9.4
9.4.1 EX1:统计未出现的列表,在默认参数下能够对两个列的组合出现的频数进行统计汇总:

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
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)
B cat dog
A
a 2 0
b 1 0
c 0 1
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

你可能感兴趣的:(python初学笔记)