第8章 分类数据

第8章 分类数据

在这里插入图片描述

import pandas as pd
import numpy as np
df = pd.read_csv('data/table.csv')
df.head()
School Class ID Gender Address Height Weight Math Physics
0 S_1 C_1 1101 M street_1 173 63 34.0 A+
1 S_1 C_1 1102 F street_2 192 73 32.5 B+
2 S_1 C_1 1103 M street_2 186 82 87.2 B+
3 S_1 C_1 1104 F street_2 167 81 80.4 B-
4 S_1 C_1 1105 F street_4 159 64 84.8 B+

一、category的创建及其性质

1. 分类变量的创建

(a)用Series创建

pd.Series(["a", "b", "c", "a"], dtype="category")
0    a
1    b
2    c
3    a
dtype: category
Categories (3, object): [a, b, c]

(b)对DataFrame指定类型创建

temp_df = pd.DataFrame({'A':pd.Series(["a", "b", "c", "a"], dtype="category"),'B':list('abcd')})
temp_df.dtypes
A    category
B      object
dtype: object

(c)利用内置Categorical类型创建

cat = pd.Categorical(["a", "b", "c", "a"], categories=['a','b','c'])
pd.Series(cat)
0    a
1    b
2    c
3    a
dtype: category
Categories (3, object): [a, b, c]

(d)利用cut函数创建

默认使用区间类型为标签

pd.cut(np.random.randint(0,60,5), [0,10,30,60])
[(30, 60], (0, 10], (30, 60], (10, 30], (30, 60]]
Categories (3, interval[int64]): [(0, 10] < (10, 30] < (30, 60]]

可指定字符为标签

pd.cut(np.random.randint(0,60,5), [0,10,30,60], right=False, labels=['0-10','10-30','30-60'])
[30-60, 10-30, 10-30, 30-60, 30-60]
Categories (3, object): [0-10 < 10-30 < 30-60]

2. 分类变量的结构

一个分类变量包括三个部分,元素值(values)、分类类别(categories)、是否有序(order)

从上面可以看出,使用cut函数创建的分类变量默认为有序分类变量

下面介绍如何获取或修改这些属性

(a)describe方法

该方法描述了一个分类序列的情况,包括非缺失值个数、元素值类别数(不是分类类别数)、最多次出现的元素及其频数

s = pd.Series(pd.Categorical(["a", "b", "c", "a",np.nan], categories=['a','b','c','d']))
s.describe()
count     4
unique    3
top       a
freq      2
dtype: object
s.values
[a, b, c, a, NaN]
Categories (4, object): [a, b, c, d]

(b)categories和ordered属性

查看分类类别和是否排序

s.cat.categories
Index(['a', 'b', 'c', 'd'], dtype='object')
s.cat.ordered
False

3. 类别的修改

(a)利用set_categories修改

修改分类,但本身值不会变化

s = pd.Series(pd.Categorical(["a", "b", "c", "a",np.nan], categories=['a','b','c','d']))
s.cat.set_categories(['new_a','c'])
0    NaN
1    NaN
2      c
3    NaN
4    NaN
dtype: category
Categories (2, object): [new_a, c]

(b)利用rename_categories修改

需要注意的是该方法会把值和分类同时修改

s = pd.Series(pd.Categorical(["a", "b", "c", "a",np.nan], categories=['a','b','c','d']))
s.cat.rename_categories(['new_%s'%i for i in s.cat.categories])
0    new_a
1    new_b
2    new_c
3    new_a
4      NaN
dtype: category
Categories (4, object): [new_a, new_b, new_c, new_d]

利用字典修改值

s.cat.rename_categories({'a':'new_a','b':'new_b'})
0    new_a
1    new_b
2        c
3    new_a
4      NaN
dtype: category
Categories (4, object): [new_a, new_b, c, d]

(c)利用add_categories添加

s = pd.Series(pd.Categorical(["a", "b", "c", "a",np.nan], categories=['a','b','c','d']))
s.cat.add_categories(['e'])
0      a
1      b
2      c
3      a
4    NaN
dtype: category
Categories (5, object): [a, b, c, d, e]
s.cat.add_categories('e')
0      a
1      b
2      c
3      a
4    NaN
dtype: category
Categories (5, object): [a, b, c, d, e]

(d)利用remove_categories移除

s = pd.Series(pd.Categorical(["a", "b", "c", "a",np.nan], categories=['a','b','c','d']))
s.cat.remove_categories(['d'])
0      a
1      b
2      c
3      a
4    NaN
dtype: category
Categories (3, object): [a, b, c]

(e)删除元素值未出现的分类类型

s = pd.Series(pd.Categorical(["a", "b", "c", "a",np.nan], categories=['a','b','c','d']))
s.cat.remove_unused_categories()
0      a
1      b
2      c
3      a
4    NaN
dtype: category
Categories (3, object): [a, b, c]

二、分类变量的排序

前面提到,分类数据类型被分为有序和无序,这非常好理解,例如分数区间的高低是有序变量,考试科目的类别一般看做无序变量

1. 序的建立

(a)一般来说会将一个序列转为有序变量,可以利用as_ordered方法

s = pd.Series(["a", "d", "c", "a"]).astype('category').cat.as_ordered()
s
0    a
1    d
2    c
3    a
dtype: category
Categories (3, object): [a < c < d]

退化为无序变量,只需要使用as_unordered

s.cat.as_unordered()
0    a
1    d
2    c
3    a
dtype: category
Categories (3, object): [a, c, d]

(b)利用set_categories方法中的order参数

pd.Series(["a", "d", "c", "a"]).astype('category').cat.set_categories(['a','c','d'],ordered=True)
0    a
1    d
2    c
3    a
dtype: category
Categories (3, object): [a < c < d]

(c)利用reorder_categories方法

这个方法的特点在于,新设置的分类必须与原分类为同一集合

s = pd.Series(["a", "d", "c", "a"]).astype('category')
s.cat.reorder_categories(['a','c','d'],ordered=True)
0    a
1    d
2    c
3    a
dtype: category
Categories (3, object): [a < c < d]
#s.cat.reorder_categories(['a','c'],ordered=True) #报错
#s.cat.reorder_categories(['a','c','d','e'],ordered=True) #报错

2. 排序

先前在第1章介绍的值排序和索引排序都是适用的

s = pd.Series(np.random.choice(['perfect','good','fair','bad','awful'],50)).astype('category')
s.cat.set_categories(['perfect','good','fair','bad','awful'][::-1],ordered=True).head()
0    good
1     bad
2     bad
3    good
4    good
dtype: category
Categories (5, object): [awful < bad < fair < good < perfect]
s.sort_values(ascending=False).head()
46    perfect
43    perfect
9     perfect
11    perfect
13    perfect
dtype: category
Categories (5, object): [awful, bad, fair, good, perfect]
s.sort_values(ascending=True).head()
39    awful
47    awful
6     awful
23    awful
8     awful
dtype: category
Categories (5, object): [awful, bad, fair, good, perfect]
df_sort = pd.DataFrame({'cat':s.values,'value':np.random.randn(50)}).set_index('cat')
df_sort.head()
value
cat
good 0.371380
bad 0.799006
bad -0.039575
good 0.631024
good 0.905758
df_sort.sort_index().head()
value
cat
awful 0.713834
awful 0.108047
awful -0.290348
awful 2.646020
awful -0.212087

三、分类变量的比较操作

1. 与标量或等长序列的比较

(a)标量比较

s = pd.Series(["a", "d", "c", "a"]).astype('category')
s == 'a'
0     True
1    False
2    False
3     True
dtype: bool

(b)等长序列比较

s == list('abcd')
0     True
1    False
2     True
3    False
dtype: bool

2. 与另一分类变量的比较

(a)等式判别(包含等号和不等号)

两个分类变量的等式判别需要满足分类完全相同

s = pd.Series(["a", "d", "c", "a"]).astype('category')
s == s
0    True
1    True
2    True
3    True
dtype: bool
s != s
0    False
1    False
2    False
3    False
dtype: bool
s_new = s.cat.set_categories(['a','d','e'])
# s == s_new #报错
s_new
0      a
1      d
2    NaN
3      a
dtype: category
Categories (3, object): [a, d, e]

(b)不等式判别(包含>=,<=,<,>)

两个分类变量的不等式判别需要满足两个条件:① 分类完全相同 ② 排序完全相同

s = pd.Series(["a", "d", "c", "a"]).astype('category')
#s >= s #报错
s = pd.Series(["a", "d", "c", "a"]).astype('category').cat.reorder_categories(['a','c','d'],ordered=True)
s >= s
0    True
1    True
2    True
3    True
dtype: bool

四、问题与练习

【问题一】 如何使用union_categoricals方法?它的作用是什么?

from https://juejin.im/post/5b680a7af265da0f7a1d1de4

blood_type1 = pd.Categorical(["A", "AB"])
blood_type2 = pd.Categorical(["B", "O"])
pd.concat([pd.Series(blood_type1), pd.Series(blood_type2)])
0     A
1    AB
0     B
1     O
dtype: object
  • 可以发现,分类数据经过 pd.concat 合并后类型转为了 object 类型。如果想要保持分类类型的话,可以借助 union_categoricals 来完成。
from pandas.api.types import union_categoricals

pd.Series(union_categoricals([blood_type1, blood_type2]))
0     A
1    AB
2     B
3     O
dtype: category
Categories (4, object): [A, AB, B, O]

【问题二】 利用concat方法将两个序列纵向拼接,它的结果一定是分类变量吗?什么情况下不是?

blood_type1 = pd.Categorical(["A", "AB"])
blood_type2 = pd.Categorical(["B", "O"])
pd.concat([pd.Series(blood_type1), pd.Series(blood_type2)])
0     A
1    AB
0     B
1     O
dtype: object
  • 可以发现,分类数据经过 pd.concat 合并后类型转为了 object 类型。
blood_type1 = pd.Categorical([1,2])
blood_type2 = pd.Categorical([2,3])
pd.concat([pd.Series(blood_type1), pd.Series(blood_type2)])
0    1
1    2
0    2
1    3
dtype: int64
  • 类型转为了int64

【问题三】 当使用groupby方法或者value_counts方法时,分类变量的统计结果和普通变量有什么区别?

df = pd.read_csv('data/table.csv')
df.head()
School Class ID Gender Address Height Weight Math Physics
0 S_1 C_1 1101 M street_1 173 63 34.0 A+
1 S_1 C_1 1102 F street_2 192 73 32.5 B+
2 S_1 C_1 1103 M street_2 186 82 87.2 B+
3 S_1 C_1 1104 F street_2 167 81 80.4 B-
4 S_1 C_1 1105 F street_4 159 64 84.8 B+
df['Physics'].value_counts()
B+    9
B     8
B-    6
A     4
A-    3
A+    3
C     2
Name: Physics, dtype: int64
df['Physics'].astype('category').value_counts()
B+    9
B     8
B-    6
A     4
A-    3
A+    3
C     2
Name: Physics, dtype: int64
df['Physics']=df['Physics'].astype('category')
for name ,group in df.groupby('Physics'):
    print(name)
    display(group)
A
School Class ID Gender Address Height Weight Math Physics
13 S_1 C_3 1304 M street_2 195 70 85.2 A
19 S_2 C_1 2105 M street_4 170 81 34.2 A
26 S_2 C_3 2302 M street_5 171 88 32.7 A
30 S_2 C_4 2401 F street_2 192 62 45.3 A
A+
School Class ID Gender Address Height Weight Math Physics
0 S_1 C_1 1101 M street_1 173 63 34.0 A+
7 S_1 C_2 1203 M street_6 160 53 58.8 A+
22 S_2 C_2 2203 M street_4 155 91 73.8 A+
A-
School Class ID Gender Address Height Weight Math Physics
5 S_1 C_2 1201 M street_5 188 68 97.0 A-
11 S_1 C_3 1302 F street_1 175 57 87.7 A-
28 S_2 C_3 2304 F street_6 164 81 95.5 A-
B
School Class ID Gender Address Height Weight Math Physics
8 S_1 C_2 1204 F street_5 162 63 33.8 B
12 S_1 C_3 1303 M street_7 188 82 49.7 B
20 S_2 C_2 2201 M street_5 193 100 39.1 B
24 S_2 C_2 2205 F street_7 183 76 85.4 B
29 S_2 C_3 2305 M street_4 187 73 48.9 B
31 S_2 C_4 2402 M street_7 166 82 48.7 B
33 S_2 C_4 2404 F street_2 160 84 67.7 B
34 S_2 C_4 2405 F street_6 193 54 47.6 B
B+
School Class ID Gender Address Height Weight Math Physics
1 S_1 C_1 1102 F street_2 192 73 32.5 B+
2 S_1 C_1 1103 M street_2 186 82 87.2 B+
4 S_1 C_1 1105 F street_4 159 64 84.8 B+
10 S_1 C_3 1301 M street_4 161 68 31.5 B+
16 S_2 C_1 2102 F street_6 161 61 50.6 B+
18 S_2 C_1 2104 F street_5 159 97 72.2 B+
21 S_2 C_2 2202 F street_7 194 77 68.5 B+
25 S_2 C_3 2301 F street_4 157 78 72.3 B+
32 S_2 C_4 2403 F street_6 158 60 59.7 B+
B-
School Class ID Gender Address Height Weight Math Physics
3 S_1 C_1 1104 F street_2 167 81 80.4 B-
6 S_1 C_2 1202 F street_4 176 94 63.5 B-
9 S_1 C_2 1205 F street_6 167 63 68.4 B-
14 S_1 C_3 1305 F street_5 187 69 61.7 B-
17 S_2 C_1 2103 M street_4 157 61 52.5 B-
23 S_2 C_2 2204 M street_1 175 74 47.2 B-
C
School Class ID Gender Address Height Weight Math Physics
15 S_2 C_1 2101 M street_7 174 84 83.3 C
27 S_2 C_3 2303 F street_7 190 99 65.9 C
  • 好像没啥区别

【问题四】 下面的代码说明了Series创建分类变量的什么“缺陷”?如何避免?(提示:使用Series中的copy参数)

cat = pd.Categorical([1, 2, 3, 10], categories=[1, 2, 3, 4, 10])
s = pd.Series(cat, name="cat")
cat
[1, 2, 3, 10]
Categories (5, int64): [1, 2, 3, 4, 10]
s.iloc[0:2] = 10
cat
[10, 10, 3, 10]
Categories (5, int64): [1, 2, 3, 4, 10]
  • 解答

问题应该是我们对于Series里面的内容进行改变时,Categories中相对应的内容也进行了改变,避免的方式时在创建Series时copy 参数设为True这样一来就会自动拷贝一份输入的Categories,这时候我i们再对Series进行改变,Categories中相对应的内容不变。

copy:一个布尔值。如果为True,则拷贝输入数据data

cat = pd.Categorical([1, 2, 3, 10], categories=[1, 2, 3, 4, 10])
s = pd.Series(cat, name="cat",copy=True)
cat
[1, 2, 3, 10]
Categories (5, int64): [1, 2, 3, 4, 10]
s.iloc[0:2] = 10
cat
[1, 2, 3, 10]
Categories (5, int64): [1, 2, 3, 4, 10]

【练习一】 现继续使用第四章中的地震数据集,请解决以下问题:

(a)现在将深度分为七个等级:[0,5,10,15,20,30,50,np.inf],请以深度等级Ⅰ,Ⅱ,Ⅲ,Ⅳ,Ⅴ,Ⅵ,Ⅶ为索引并按照由浅到深的顺序进行排序。

pd.read_csv('data/Earthquake.csv').head()
日期 时间 维度 经度 方向 距离 深度 烈度
0 2003.05.20 12:17:44 AM 39.04 40.38 west 0.1 10.0 0.0
1 2007.08.01 12:03:08 AM 40.79 30.09 west 0.1 5.2 4.0
2 1978.05.07 12:41:37 AM 38.58 27.61 south_west 0.1 0.0 0.0
3 1997.03.22 12:31:45 AM 39.47 36.44 south_west 0.1 10.0 0.0
4 2000.04.02 12:57:38 AM 40.80 30.24 south_west 0.1 7.0 0.0
a=pd.read_csv('data/Earthquake.csv')
a['深度']=pd.cut(a['深度'], [0,5,10,15,20,30,50,np.inf], right=False, labels=['Ⅰ','Ⅱ','Ⅲ','Ⅳ','Ⅴ','Ⅵ','Ⅶ'])
a.sort_values('深度')
日期 时间 维度 经度 方向 距离 深度 烈度
1182 1999.07.05 12:57:31 AM 41.24 32.78 north_west 0.9 0.0
1486 1994.12.24 12:40:49 AM 37.48 28.35 north_east 1.0 0.0
7092 2001.12.12 12:59:55 AM 37.42 37.28 north_east 3.2 0.0
2810 2017.04.21 12:25:56 AM 38.78 29.06 south_east 1.5 3.8
1483 1992.08.21 12:10:52 AM 37.61 27.48 north_east 1.0 0.0
... ... ... ... ... ... ... ... ...
7771 1981.08.04 12:52:19 AM 38.90 37.00 north 3.7 0.0
525 1968.03.21 12:42:51 AM 38.80 27.60 south 0.6 4.3
3921 1929.04.27 12:18:06 AM 40.51 31.43 south_west 1.9 4.8
3896 2008.03.26 12:16:15 AM 37.04 30.27 east 1.8 0.0
1214 1956.07.18 12:46:53 AM 39.96 27.30 north_east 0.9 4.6

10062 rows × 8 columns

参考答案
df = pd.read_csv('data/Earthquake.csv')
df.head()
df_a = df.copy()
df_a['深度'] = pd.cut(df_a['深度'], [-1e-10,5,10,15,20,30,50,np.inf],labels=['Ⅰ','Ⅱ','Ⅲ','Ⅳ','Ⅴ','Ⅵ','Ⅶ'])
df_a.set_index('深度').sort_index().head()
日期 时间 维度 经度 方向 距离 烈度
深度
2009.09.09 12:54:13 AM 42.42 43.03 north_east 95.4 0.0
1997.06.16 12:18:04 AM 37.92 29.17 north_east 3.2 0.0
2011.10.25 12:29:45 AM 38.96 43.64 south_east 1.6 3.9
1995.07.23 12:05:04 AM 37.61 29.29 north_east 3.2 0.0
2013.06.10 12:39:19 AM 38.53 43.85 south_east 1.6 3.7

(b)在(a)的基础上,将烈度分为4个等级:[0,3,4,5,np.inf],依次对南部地区的深度和烈度等级建立多级索引排序。


a['烈度']=pd.cut(a['烈度'],[0,3,4,5,np.inf],right=False)
a.set_index(['深度','烈度'])
# a.sort_values(['深度','烈度'])
日期 时间 维度 经度 方向 距离
深度 烈度
[0.0, 3.0) 2003.05.20 12:17:44 AM 39.04 40.38 west 0.1
[4.0, 5.0) 2007.08.01 12:03:08 AM 40.79 30.09 west 0.1
[0.0, 3.0) 1978.05.07 12:41:37 AM 38.58 27.61 south_west 0.1
[0.0, 3.0) 1997.03.22 12:31:45 AM 39.47 36.44 south_west 0.1
[0.0, 3.0) 2000.04.02 12:57:38 AM 40.80 30.24 south_west 0.1
... ... ... ... ... ... ...
[3.0, 4.0) 2015.11.18 12:17:48 AM 42.31 42.94 north 81.6
[0.0, 3.0) 1990.01.28 12:22:43 AM 42.70 26.20 north_west 89.5
[0.0, 3.0) 2001.08.09 12:58:14 AM 42.77 26.47 north 90.6
[0.0, 3.0) 1994.06.05 12:20:03 AM 42.41 43.06 north_east 94.3
[0.0, 3.0) 2009.09.09 12:54:13 AM 42.42 43.03 north_east 95.4

10062 rows × 6 columns

a[a['方向']=='south'].set_index(['深度','烈度']).sort_index()
日期 时间 维度 经度 方向 距离
深度 烈度
[0.0, 3.0) 2000.05.14 12:18:59 AM 38.60 39.92 south 0.3
[0.0, 3.0) 1982.01.05 12:53:17 AM 39.61 28.50 south 0.5
[0.0, 3.0) 1986.07.20 12:01:18 AM 37.81 35.91 south 0.5
[0.0, 3.0) 1996.08.14 12:14:37 AM 40.70 35.43 south 0.5
[0.0, 3.0) 2002.09.16 12:54:15 AM 41.19 34.16 south 0.5
... ... ... ... ... ... ... ...
[4.0, 5.0) 1933.01.02 12:56:58 AM 38.01 38.24 south 2.6
[4.0, 5.0) 1936.08.02 12:21:09 AM 37.88 29.70 south 2.9
[4.0, 5.0) 1971.04.30 12:10:04 AM 37.76 36.18 south 3.5
[4.0, 5.0) 1964.11.20 12:59:19 AM 40.20 28.06 south 3.6
[4.0, 5.0) 1970.03.30 12:15:44 AM 38.96 29.73 south 4.0

605 rows × 6 columns

参考答案
df_a['烈度'] = pd.cut(df_a['烈度'], [-1e-10,3,4,5,np.inf],labels=['Ⅰ','Ⅱ','Ⅲ','Ⅳ'])
df_a.set_index(['深度','烈度']).sort_index().head()
日期 时间 维度 经度 方向 距离
深度 烈度
1978.05.07 12:41:37 AM 38.58 27.61 south_west 0.1
2000.02.07 12:11:45 AM 40.05 34.07 south_east 0.1
1971.05.20 12:08:46 AM 37.72 30.00 north_east 0.1
1985.01.28 12:20:56 AM 38.85 29.06 north_east 0.1
1990.07.05 12:43:04 AM 37.87 29.18 east 0.1

【练习二】 对于分类变量而言,调用第4章中的变形函数会出现一个BUG(目前的版本下还未修复):例如对于crosstab函数,按照官方文档的说法,即使没有出现的变量也会在变形后的汇总结果中出现,但事实上并不是这样,比如下面的例子就缺少了原本应该出现的行’c’和列’f’。基于这一问题,请尝试设计my_crosstab函数,在功能上能够返回正确的结果。

foo = pd.Categorical(['a', 'b'], categories=['a', 'b', 'c'])
bar = pd.Categorical(['d', 'e'], categories=['d', 'e', 'f'])
pd.crosstab(foo, bar)
col_0 d e
row_0
a 1 0
b 0 1
# pd.crosstab(foo.categories)
pd.crosstab(index=foo.categories,columns=bar.categories,values=1,aggfunc='count')
def my_crosstab(foo,bar):
    df=pd.DataFrame({i:[0]*len(foo.categories)for i in bar.categories},index=foo.categories)
    for i in range(len(foo.categories)):
        df.iat[i,i]=1
    df.rename_axis(index={None:'row_0'},columns={None:'col_0'},inplace=True)
    return df
my_crosstab(foo ,bar )
col_0 d e f
row_0
a 1 0 0
b 0 1 0
c 0 0 1
查看答案后改进
pd.crosstab(index=foo.categories,columns=bar.categories,values=1,aggfunc='count')
def my_crosstab(foo,bar):
    col=bar.categories.union(bar)
    row=foo.categories.union(foo)
    df=pd.DataFrame({i:[0]*len(row)for i in col},index=row)
    for i in range(len(row)):
        df.iat[i,i]=1
    df.rename_axis(index={None:'row_0'},columns={None:'col_0'},inplace=True)
    return df
my_crosstab(foo ,bar )
col_0 d e f
row_0
a 1 0 0
b 0 1 0
c 0 0 1
参考答案
bar.categories.union(set(bar))
Index(['d', 'e', 'f'], dtype='object')
def my_crosstab(foo,bar):
    num = len(foo)
    s1 = pd.Series([i for i in list(foo.categories.union(set(foo)))],name='1nd var')
    print('s1')
    display(s1)
    s2 = [i for i in list(bar.categories.union(set(bar)))]
    print('s2')
    display(s2)
    df = pd.DataFrame({i:[0]*len(s1) for i in s2},index=s1)
    for i in range(num):
        df.at[foo[i],bar[i]] += 1
    return df.rename_axis('2st var',axis=1)
my_crosstab(foo,bar)
s1



0    a
1    b
2    c
Name: 1nd var, dtype: object


s2



['d', 'e', 'f']
2st var d e f
1nd var
a 1 0 0
b 0 1 0
c 0 0 0

猜你喜欢

转载自blog.csdn.net/weixin_45569785/article/details/106981554