返回介绍

12.1 Categorical Data 类别数据

发布于 2023-07-21 00:42:01 字数 10152 浏览 0 评论 0 收藏 0

这一届会介绍 pandas 的 Categorical 类型。

1 Background and Motivation(背景和动力)

表格中的列克可能会有重复的部分。我们可以用 unique 和 value_counts,从一个数组从提取不同的值,并计算频度:

import numpy as np
import pandas as pd
values = pd.Series(['apple', 'orange', 'apple', 'apple'] * 2)
values
0     apple
1    orange
2     apple
3     apple
4     apple
5    orange
6     apple
7     apple
dtype: object
pd.unique(values)
array(['apple', 'orange'], dtype=object)
pd.value_counts(values)
apple     6
orange    2
dtype: int64

对于不同的类型数据值,一个更好的方法是用维度表(dimension table)来表示,然后用整数键(integer keys)来指代维度表:

values = pd.Series([0, 1, 0, 0] * 2)
values
0    0
1    1
2    0
3    0
4    0
5    1
6    0
7    0
dtype: int64
dim = pd.Series(['apple', 'orange'])
dim
0     apple
1    orange
dtype: object

用 take 方法来重新存储原始的,由字符串构成的 Series:

dim.take(values)
0     apple
1    orange
0     apple
0     apple
0     apple
1    orange
0     apple
0     apple
dtype: object

这种用整数表示的方法叫做类别(categorical)或字典编码(dictionary-encoded)表示法。表示不同类别值的数组,被称作类别,字典,或层级。本书中我们将使用类别(categorical and categories)来称呼。表示类别的整数值被叫做,类别编码(category code),或编码(code)。

2 Categorical Type in pandas(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_idfruitcountweight
00apple52.255245
11orange81.309949
22apple62.330312
33apple32.927920
44apple131.322311
55orange100.474809
66apple40.827271
77apple82.480494

这里,df['fruit']是一个 python 的字符串对象。我们将其转换为类型对象:

fruits_cat = df['fruit'].astype('category')
fruits_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]

fruits_cat 的值并不是一个 numpy 数组,而是一个 pandas.Categorical 实例:

c = fruits_cat.values
type(c)
pandas.core.categorical.Categorical

这个 Categorical 对象有 categories 和 codes 属性:

c.categories
Index(['apple', 'orange'], dtype='object')
c.codes
array([0, 1, 0, 0, 0, 1, 0, 0], dtype=int8)

可以把转换的结果变为 DataFrame 列:

df['fruit'] = df['fruit'].astype('category')
df.fruit
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]

也可以直接把其他的 python 序列变为 pandas.Categorical 类型:

my_categories = pd.Categorical(['foo', 'bar', 'baz', 'foo', 'bar'])
my_categories
[foo, bar, baz, foo, bar]
Categories (3, object): [bar, baz, foo]

如果已经得到了分类编码数据(categorical encoded data),我们可以使用 from_codes 构造器:

categories = ['foo', 'bar', 'baz']
codes = [0, 1, 2, 0, 0, 1]
my_cats_2 = pd.Categorical.from_codes(codes, categories)
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]

输出的结果中, [foo < bar < baz] 表示 foo 在 bar 之间,以此类推。一个没有顺序的类型实例(unordered categorical instance)可以通过 as_ordered 来排序:

my_cats_2.as_ordered()
[foo, bar, baz, foo, foo, bar]
Categories (3, object): [foo < bar < baz]

最后一点需要注意的,类型数据没必要一定是字符串,它可以是任何不可变的值类型 (any immutable value types)。

3 Computations with Categoricals(类型计算)

Categorical 类型和其他类型差不多,不过对于某些函数,比如 groupby 函数,在 Categorical 数据上会有更好的效果。很多函数可以利用 ordered 标记。

假设有一些随机的数字,用 pandas.quct 进行分箱(binning)。得到的类型是 pandas.Categorical;虽然之前用到过 pandas.cut,但是没有具体介绍里面的细节:

np.random.seed(12345)
draws = np.random.randn(1000)
draws[:5]
array([-0.20470766,  0.47894334, -0.51943872, -0.5557303 ,  1.96578057])

计算分箱后的分位数:

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.95, -0.684], (-0.0101, 0.63], (0.63, 3.928]] Length: 1000 Categories (4, interval[float64]): [(-2.95, -0.684] < (-0.684, -0.0101] < (-0.0101, 0.63] < (0.63, 3.928]]

具体分位数并不如季度的名字直观,我们直接在 qcut 中设定 labels:

bins = pd.qcut(draws, 4, labels=['Q1', 'Q2', 'Q3', 'Q4'])
bins
[Q2, Q3, Q2, Q2, Q4, ..., Q3, Q2, Q1, Q3, Q4]
Length: 1000
Categories (4, object): [Q1 < Q2 < Q3 < Q4]
bins.codes[:10]
array([1, 2, 1, 1, 3, 3, 2, 2, 3, 3], dtype=int8)

bins caetegorical 并没有包含边界星系,我们可以用 groupby 来提取:

bins = pd.Series(bins, name='quartile')
results = (pd.Series(draws)
           .groupby(bins)
           .agg(['count', 'min', 'max'])
           .reset_index())
results
 quartilecountminmax
0Q1250-2.949343-0.685484
1Q2250-0.683066-0.010115
2Q3250-0.0100320.628894
3Q42500.6342383.927528

quartile 列包含了原始的类别信息,包含 bins 中的顺序:

results['quartile']
0    Q1
1    Q2
2    Q3
3    Q4
Name: quartile, dtype: category
Categories (4, object): [Q1 < Q2 < Q3 < Q4]

Better performance with categoricals (使用 categoricals 得到更好的效果)

使用 categorical 能让效果提高。如果一个 DataFrame 的列是 categorical 类型,使用的时候会减少很多内存的使用。假设我们有一个一千万的元素和一个类别:

N = 10000000
draws = pd.Series(np.random.randn(N))
labels = pd.Series(['foo', 'bar', 'baz', 'qux'] * (N // 4))

把 labels 变为 categorical:

categories = labels.astype('category')

可以看到 labels 会比 categories 使用更多的内存:

labels.memory_usage()
80000080
categories.memory_usage()
10000272

当然,转换成 category 也是要消耗计算的,不过这种消耗是一次性的:

%time _ = labels.astype('category')
CPU times: user 303 ms, sys: 70.1 ms, total: 373 ms
Wall time: 385 ms

在 categories 上使用 groupby 会非常快,因为用的是基于整数的编码,而不是由字符串组成的数组。

4 Categorical Methods(类别方法)

如果是包含 categorical 数据的 Series 数据,有和 Series.str 类似的一些比较特殊的方法。对于访问 categories 和 code 很方便:

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 可以访问 categorical 方法:

cat_s.cat.codes
0    0
1    1
2    2
3    3
4    0
5    1
6    2
7    3
dtype: int8
cat_s.cat.categories
Index(['a', 'b', 'c', 'd'], dtype='object')

假设我们知道实际的类别超过了当前观测到的四个类别,那么我们可以使用 set_categories 方法来扩展:

actual_categories = ['a', 'b', 'c', 'd', 'e']
cat_s2 = cat_s.cat.set_categories(actual_categories)
cat_s2
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]

数据本身似乎没有改变,不过在对其进行操作的时候会反应出来。例如,value_counts:

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

在大型数据集,categoricals 经常用来作为省内存和提高效果的工具。在对一个很大的 DataFrame 或 Series 进行过滤后,很多类型可能不会出现在数据中。我们用 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]

Creating dummy variables for modeling(为建模创建哑变量)

在使用机器学习的一些工具时,经常要转变类型数据为哑变量(dummy variables ),也被称作是独热编码(one-hot encoding)。即在 DataFrame 中,给一列中不同的类别创建不同的列,用 1 表示出现,用 0 表示未出现。

例子:

cat_s = pd.Series(['a', 'b', 'c', 'd'] * 2, dtype='category')

在第七章也介绍过,pandas.get_dummies 函数会把一维的类型数据变为包含哑变量的 DataFrame:

pd.get_dummies(cat_s)
 abcd
01000
10100
20010
30001
41000
50100
60010
70001

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文