12.1 分类数据
介绍pandas 的Categorical类型,展示使用pandas进行操作时如何获得更好的性能和内存使用。并介绍一些在统计和机器学习应用中使用的分类数据。。。
12.1.1 背景和目标
一个列经常会包含重复值,这些重复值时一个小型的不同值的集合。可以使用unique 和 value_counts这样的函数从一个数组中提取出不同值并分别计数这些的频率。
values=pd.Series(['apple','orange','apple','apple']*2)
pd.unique(values)
array(['apple', 'orange'], dtype=object)
pd.value_counts(values)
apple 6
orange 2
dtype: int64
values = pd.Series([0, 1, 0, 0] * 2)
dim = pd.Series(['apple', 'orange']) # 用重复的值表示数据
dim.take(values)
0 apple
1 orange
0 apple
0 apple
0 apple
1 orange
0 apple # 这个take 不是索引嘛,,怎么就分类了
0 apple
dtype: object
这种按照整数展现的方式称为分类或字典编码展现,不同值的数组可以被称为数据的类别,字典或层级。
在数据分析中,分类展示可以显著的性能提升。。。额,哪里分类了我怎么看不出来。。。
12.1.2 pandas中的Categorical 类型
pandas有特殊的Categorical类型,用于承载基于整数的类别显示或编码的数据。
fruits = ['apple', 'orange', 'apple', 'apple'] * 2
N = len(fruits)
df = pd.DataFrame({'fruit': fruits,
'basket_id': np.arange(N),
'count': np.random.randint(3, 15, size=N),
'weight': np.random.uniform(0, 4, size=N)},
columns=['basket_id', 'fruit', 'count', 'weight'])
df
basket_id fruit count weight
0 0 apple 13 2.595562
1 1 orange 13 3.613305
2 2 apple 8 0.822683
3 3 apple 5 3.966781
4 4 apple 9 3.545468
5 5 orange 4 3.816133
6 6 apple 5 2.302654
7 7 apple 8 2.171438
fruit_cat = df['fruit'].astype('category') # df['fruit']是一个python字符串对象组成的数组。可以调用函数转换为Categorical对象。
fruit_cat
0 apple
1 orange # 这就是分类类型吗,,,,
2 apple
3 apple
4 apple
5 orange
6 apple
7 apple
Name: fruit, dtype: category
Categories (2, object): [apple, orange]
c = fruit_cat.values
type(c) # fruit_cat 的值并不是numpy数组,而是pandas.Categorical 实例。
pandas.core.arrays.categorical.Categorical
c.categories,c.codes # 分类对象的两个属性。
(Index(['apple', 'orange'], dtype='object'),
array([0, 1, 0, 0, 0, 1, 0, 0], dtype=int8))
df['fruit'] = df['fruit'].astype('category') # 将已存在的转换为一个分类对象。
my_categories = pd.Categorical(['foo', 'bar', 'baz', 'foo', 'bar']) # 从其他数据类型直接生成分类对象。
categories = ['foo', 'bar', 'baz']
codes = [0, 1, 2, 0, 0, 1]
my_cats_2 = pd.Categorical.from_codes(codes, categories) # 从另一个数据源获得了分类编码数据,可以使用cfrom_codes构造函数
my_cats_2
[foo, bar, baz, foo, foo, bar]
Categories (3, object): [foo, bar, baz]
一般分类转换时不会指定类别的顺序的,所有categories数组可能会与输入数据的顺序不同。但使用from_codes或其他任意构造函数时,就可以为类别指定一个有意义的顺序。
ordered_cat = pd.Categorical.from_codes(codes, categories,
ordered=True)
ordered_cat
[foo, bar, baz, foo, foo, bar]
Categories (3, object): [foo < bar < baz] # 就这顺序
my_cats_2.as_ordered() # 对于未排序的可以使用as_ordered()来排序。
注意分类数据可以不仅是字符串,可以是任何不可变的值类型。
12.1.3 使用Categorical 对象进行计算
np.random.seed(12345)
draws = np.random.randn(1000) # 经过分箱得到的是categories对象
bins=pd.qcut(draws,4)
bins
[(-0.684, -0.0101], (-0.0101, 0.63], (-0.684, -0.0101], (-0.684, -0.0101], (0.63, 3.928], ..., (-0.0101, 0.63], (-0.684, -0.0101], (-2.9499999999999997, -0.684], (-0.0101, 0.63], (0.63, 3.928]]
Length: 1000
Categories (4, interval[float64]): [(-2.9499999999999997, -0.684] < (-0.684, -0.0101] < (-0.0101, 0.63] < (0.63, 3.928]]
bins=pd.qcut(draws,4,labels=['Q1','Q2','Q3','Q4']) # 给各个分组取个名更加好用。
bins.codes[:10]
array([1, 2, 1, 1, 3, 3, 2, 2, 3, 3], dtype=int8)
bins = pd.Series(bins, name='quartile')
results = (pd.Series(draws)
.groupby(bins)
.agg(['count', 'min', 'max']) # 使用groupby方法来提取一些汇总值
.reset_index())
results
quartile count min max
0 Q1 250 -2.949343 -0.685484
1 Q2 250 -0.683066 -0.010115
2 Q3 250 -0.010032 0.628894
3 Q4 250 0.634238 3.927528
results['quartile']
0 Q1
1 Q2
2 Q3
3 Q4
Name: quartile, dtype: category
Categories (4, object): [Q1 < Q2 < Q3 < Q4] # 这一列保留了bins中原始的分类信息,包括顺序。
12.1.3.1 使用分类获得更高性能
如果在特定的数据集上做了大量的分析,将数据转换为分类数据可以产生大幅度的性能提升。
N = 10000000
draws = pd.Series(np.random.randn(N))
labels = pd.Series(['foo', 'bar', 'baz', 'qux'] * (N // 4))
categories = labels.astype('category') # 转换成categories对象
labels.memory_usage(),categories.memory_usage()
(80000128, 10000320) # 可以看到省了很多内存
使用分类对象进行GroupBy操作明显更快,这是因为底层算法使用了基于整数代码的数组而不是字符串数组。
12.1.4 分类方法
series 包含的分类数据有一些特殊的方法,类似于Series.str 的特殊字符串方法。这些方法提供了快捷访问类别和代码的方式。
s = pd.Series(['a', 'b', 'c', 'd'] * 2)
cat_s = s.astype('category')
cat_s
0 a
1 b
2 c
3 d
4 a
5 b
6 c
7 d
dtype: category
Categories (4, object): [a, b, c, d]
cat_s.cat.codes # 特殊属性cat提供了对分类方法的访问。
0 0
1 1
2 2
3 3
4 0
5 1
6 2
7 3
dtype: int8
actual_categories = ['a', 'b', 'c', 'd', 'e']
cat_s2 = cat_s.cat.set_categories(actual_categories) # 假设我们知道该数据的实际类别集合超出了数据中观察到的四个值
cat_s2 # 使用set_categories方法改变类别。
0 a
1 b
2 c
3 d
4 a
5 b
6 c
7 d
dtype: category
Categories (5, object): [a, b, c, d, e] # 虽然返回的结果差不多,但新类别会反应到他们的操作中。
cat_s.value_counts()
d 2
c 2
b 2
a 2
dtype: int64
cat_s2.value_counts()
d 2
c 2
b 2
a 2
e 0
dtype: int64
# 在大型数据中,分类数据经常被用于节省内存和更高性能的便捷工具,在你过滤了一个大型数据后,很多类别不会出现在数据中
# 可以使用remove_unused_categories 方法来去除观察到的类别。
cat_s3 = cat_s[cat_s.isin(['a', 'b'])]
cat_s3
0 a
1 b
4 a
5 b
dtype: category
Categories (4, object): [a, b, c, d]
cat_s3.cat.remove_unused_categories()
0 a
1 b
4 a
5 b
dtype: category
Categories (2, object): [a, b] # 真是神奇啊。。。。。
12.1.4.1 创建用于建模的虚拟变量
就是pd.get_dummies啦,将数据转换成虚拟变量
cat_s = pd.Series(['a', 'b', 'c', 'd'] * 2, dtype='category')
pd.get_dummies(cat_s)
a b c d
0 1 0 0 0
1 0 1 0 0
2 0 0 1 0
3 0 0 0 1
4 1 0 0 0
5 0 1 0 0
6 0 0 1 0
7 0 0 0 1