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])
[(10, 30], (0, 10], (10, 30], (30, 60], (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'])
[10-30, 30-60, 30-60, 10-30, 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
(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]
(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 fair
2 bad
3 perfect
4 perfect
dtype: category
Categories (5, object): [awful < bad < fair < good < perfect]
s.sort_values(ascending=False).head()
29 perfect
17 perfect
31 perfect
3 perfect
4 perfect
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 | -1.746975 |
fair | 0.836732 |
bad | 0.094912 |
perfect | -0.724338 |
perfect | -1.456362 |
df_sort.sort_index().head()
value | |
---|---|
cat | |
awful | 0.245782 |
awful | 0.063991 |
awful | 1.541862 |
awful | -0.062976 |
awful | 0.472542 |
三、分类变量的比较操作
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 #报错
(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方法?它的作用是什么?
使用union_categoricals需要保证两个categories必须是相同的dtype。作用是把两个union_categoricals连接在一起
【问题二】 利用concat方法将两个序列纵向拼接,它的结果一定是分类变量吗?什么情况下不是?
不一定,只有分类的数量和类别一样,才是分类变量
s = pd.Series(["a", "d", "c", "a"]).astype('category')
s1 = pd.Series(['1', '2'], dtype='category')
pd.concat([s, s1])
0 a
1 d
2 c
3 a
0 1
1 2
dtype: object
【问题三】 当使用groupby方法或者value_counts方法时,分类变量的统计结果和普通变量有什么区别?
分类变量会转成对应的普通变量
【问题四】 下面的代码说明了Series创建分类变量的什么“缺陷”?如何避免?(提示:使用Series中的copy参数)
缺陷是修改series变量的时候,原分类跟着变了。创建的时候设置参数copy=True,这样修改series的时候原分类就不会变了
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]
改为:
cat = pd.Categorical([1, 2, 3, 10], categories=[1, 2, 3, 4, 10])
s = pd.Series(cat, name="cat", copy=True)
# s = pd.Series(cat, name="cat")
cat
[1, 2, 3, 10]
Categories (4, int64): [1, 2, 3, 10]
s.iloc[0:2] = 10
cat
[1, 2, 3, 10]
Categories (4, int64): [1, 2, 3, 10]
【练习一】 现继续使用第四章中的地震数据集,请解决以下问题:
(a)现在将深度分为七个等级:[0,5,10,15,20,30,50,np.inf],请以深度等级Ⅰ,Ⅱ,Ⅲ,Ⅳ,Ⅴ,Ⅵ,Ⅶ为索引并按照由浅到深的顺序进行排序。
(b)在(a)的基础上,将烈度分为4个等级:[0,3,4,5,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 |
data = pd.read_csv('data/Earthquake.csv')
#第一题
data['深度']=pd.cut(data['深度'],[-0.1,5,10,15,20,30,50,np.inf],labels=['Ⅰ','Ⅱ','Ⅲ','Ⅳ','Ⅴ','Ⅵ','Ⅶ'])
#第二题
data['烈度'] = pd.cut(data['烈度'], [-0.1,3,4,5,np.inf],labels=['Ⅰ','Ⅱ','Ⅲ','Ⅳ'])
data.set_index(['深度','烈度']).sort_index()
【练习二】 对于分类变量而言,调用第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 |
#抄答案~
def my_crosstab(foo,bar):
num = len(foo)
s1 = pd.Series([i for i in list(foo.categories.union(set(foo)))],name='1nd var')
s2 = [i for i in list(bar.categories.union(set(bar)))]
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)
转载:https://github.com/datawhalechina/joyful-pandas