来看看 Pandas 中分类(category)数据如何处理吧。
创建分类对象
1.使用dtype="category"创建
在创建分类数据之前,先来了解下什么是分类(Category)数据呢?分类数据直白来说就是取值为有限的,或者说是固定数量的可能值。例如:性别、血型。
这里以血型为例,假定每个用户有以下的血型,我们如何创建一个关于血型的分类对象呢?
一种有效的方法就是明确指定 dtype="category"
import pandas as pd
import numpy as np
index = pd.Index(data=['Tom','Bob','Mary','James','Andy','Alice'])
user_info = pd.Series(data=['A','AB',np.nan,'AB','O','B'],index=index,name='blood_type',dtype='category')
print(user_info)
# Tom A
# Bob AB
# Mary NaN
# James AB
# Andy O
# Alice B
# Name: blood_type, dtype: category
# Categories (4, object): ['A', 'AB', 'B', 'O']
2.使用pd.Categorical 创建
# categories:指定存储的分类信息
c = pd.Categorical(["A", "AB", np.nan, "AB", "O", "B"], categories=["A", "B", "AB"])
print(c)
# ['A', 'AB', NaN, 'AB', NaN, 'B']
# Categories (3, object): ['A', 'B', 'AB']
3.astype
除了上面这些方法外,经常遇到的情况是已经创建了一个 Series,如何将它转为分类数据呢?来看看 astype 用法吧。
user_info = pd.Series(data=["A", "AB", np.nan, "AB", "O", "B"], index=index, name="blood_type")
user_info = user_info.astype("category")
print(user_info)
# Tom A
# Bob AB
# Mary NaN
# James AB
# Andy O
# Alice B
# Name: blood_type, dtype: category
# Categories (4, object): ['A', 'AB', 'B', 'O']
此外,一些其他的方法返回的结果也是分类数据。如 cut 、 qcut。具体可以见 Pandas基本功能详解中的离散化部分。
常用操作
统计
可以对分类数据使用 .describe() 方法。
c = user_info.describe()
print(c)
# count 5
# unique 4
# top AB
# freq 2
# Name: blood_type, dtype: object
解释下每个指标的含义,count 表示非空的数据有5条,unique 表示去重后的非空数据有4条,top 表示出现次数最多的值为 AB,freq 表示出现次数最多的值的次数为2次。
分类数据的取值
我们可以使用 .cat.categories 来获取分类数据所有可能的取值。
c = user_info.cat.categories
print(c)
# Index(['A', 'AB', 'B', 'O'], dtype='object')
修改分类名称
user_info = user_info.cat.rename_categories(["A+", "AB+", "B+", "O+"])
print(user_info)
# Tom A+
# Bob AB+
# Mary NaN
# James AB+
# Andy O+
# Alice B+
# Name: blood_type, dtype: category
# Categories (4, object): ['A+', 'AB+', 'B+', 'O+']
修改分类名称后,也会将数据中的对应进行修改。
类似的,除了重命名,也会遇到添加类别,删除分类的操作,这些都可以通过 .cat.add_categories ,.cat.remove_categories 来实现。
user_info = user_info.cat.add_categories(["C"])
print(user_info)
# Tom A+
# Bob AB+
# Mary NaN
# James AB+
# Andy O+
# Alice B+
# Name: blood_type, dtype: category
# Categories (5, object): ['A+', 'AB+', 'B+', 'O+', 'C']
查看数据分布统计
分类数据也是支持使用 value_counts 方法来查看数据分布的。
c = user_info.value_counts()
print(c)
# AB+ 2
# O+ 1
# B+ 1
# A+ 1
# C 0
# Name: blood_type, dtype: int64
.str
分类数据也是支持使用 .str 属性来访问的。例如想要查看下是否包含字母 "A",可以使用 .srt.contains 方法。
c = user_info.str.contains('A')
print(c)
# Tom True
# Bob True
# Mary NaN
# James True
# Andy False
# Alice False
# Name: blood_type, dtype: object
返回一个bool值的Series序列,利用花式索引可以获取到符合条件的数据。
更多关于 .str 的详细介绍可以见 Pandas文本数据处理。
合并数据
pd.concat([Series,Series])
blood_type1 = pd.Categorical(["A", "AB"])
blood_type2 = pd.Categorical(["B", "O"])
c = pd.concat([pd.Series(blood_type1), pd.Series(blood_type2)])
print(c)
# 0 A
# 1 AB
# 0 B
# 1 O
# dtype: object
可以发现,分类数据经过 pd.concat 合并后类型转为了 object 类型。如果想要保持分类类型的话,可以借助 union_categoricals 来完成。
from pandas.api.types import union_categoricals
c = union_categoricals([blood_type1, blood_type2])
print(c)
# 0 A
# 1 AB
# 0 B
# 1 O
# dtype: object
# ['A', 'AB', 'B', 'O']
# Categories (4, object): ['A', 'AB', 'B', 'O']
分类数据与Series序列的内存对比
blood_type = pd.Series(["AB","O"]*1000)
blood_type.nbytes
16000
blood_type.astype("category").nbytes
2016
对比下,是不是发现分类数据非常节省内存。但是当类别的数量接近数据的长度,那么 Categorical 将使用与等效的 object 表示几乎相同或更多的内存。
blood_type = pd.Series(['AB%04d' % i for i in range(2000)])
blood_type.nbytes
16000
blood_type.astype("category").nbytes
20000