特征工程 数据预处理-分类变量处理

往往我们需要进行训练时,读取的特征是多种多样的,数值类型的变量可能只是其中的一部分(比如年龄),但是还会有一些其他类型的分类变量(比如性别,比如月份)。
而我们这里讨论的分类变量/特征是指任何特征类型,可分为两大类:

  • 无序变量: 是指有两个或两个以上类别的变量,这些类别没有任何相关顺序。例如,如果将性别分为两组,即男性和女性,则可将其视为名义变量。
  • 有序变量: 则有 “等级 “或类别,并有特定的顺序。例如,一个顺序分类变量可以是一个具有低、中、高三个不同等级的特征。顺序很重要。

计算机无法理解文本数据,因此我们需要将这些类别转换为数字。为了便于理解这里使用kaggle项目的一个数据

index id bin_0 bin_1 bin_2 bin_3 bin_4 nom_0 nom_1 nom_2 nom_3 nom_9 ord_0 ord_1 ord_2 ord_3 ord_4 ord_5 day month target
0 0 0 0 0 T Y Green Triangle Snake Finland 2f4cb3d51 2 Grandmaster Cold h D kr 2 2 0
1 1 0 1 0 T Y Green Trapezoid Hamster Russia f83c56c21 1 Grandmaster Hot a A bF 7 8 0
2 2 0 0 0 F Y Blue Trapezoid Lion Russia ae6800dd0 1 Expert Lava Hot h R Jc 7 2 0
3 3 0 1 0 F Y Red Trapezoid Snake Canada 8270f0d71 1 Grandmaster Boiling Hot i D kW 2 1 1
4 4 0 0 0 F N Red Trapezoid Lion Canada b164b72a7 1 Grandmaster Freezing a R qP 7 8 0

这份输入数据中有

  • 5个二元变量
  • 10个无序变量
  • 6个有序变量
  • 2个循环变量
  • 1个目标变量

    编码

    标签编码

    让我们来看看数据集中的 ord_2 特征。它包括6个不同的类别: Cold、Hot、Lava Hot、Boiling Hot、Freezing 等。
    计算机无法理解文本数据,因此我们需要将这些类别转换为数字。一个简单的方法是创建一个字典,将这些值映射为从 0 到 N-1 的数字,其中 N 是给定特征中类别的总数。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 映射字典
    mapping = {
    "Freezing": 0,
    "Warm": 1,
    "Cold": 2,
    "Boiling Hot": 3,
    "Hot": 4,
    "Lava Hot": 5
    }

然后我们可以读取数据,并将这些类别转换为数字。

1
2
3
4
5
import pandas as pd
# 读取数据
df = pd.read_csv("../input/cat_train.csv")
# 取*ord_2*列,并使用映射将类别转换为数字
df.loc[:, "*ord_2*"] = df.*ord_2*.map(mapping)

这种分类变量的编码方式被称为标签编码(Label Encoding)我们将每个类别编码为一个数字标签。

我们也可以使用 scikit-learn 中的 LabelEncoder 进行编码。

1
2
3
4
5
6
7
8
9
10
import pandas as pd
from sklearn import preprocessing
# 读取数据
df = pd.read_csv("../input/cat_train.csv")
# 将缺失值填充为"NONE" scikit-learn 的 LabelEncoder 无法处理 NaN 值,所以需要提前处理。
df.loc[:, "*ord_2*"] = df.*ord_2*.fillna("NONE")
# LabelEncoder编码
lbl_enc = preprocessing.LabelEncoder()
# 转换数据
df.loc[:, "*ord_2*"] = lbl_enc.fit_transform(df.*ord_2*.values)

我们可以在许多基于树的模型中直接使用它:决策树、随机森林、XGBoost 、GBM、LightGBM等。
但是这种编码方式不能用于线性模型、支持向量机或神经网络,因为它们希望数据是标准化的。

二值化的稀疏矩阵

为了对分类数据的处理能适用于线性模型、支持向量机或神经网络。对于这些类型的模型,我们可以对数据进行二值化(binarize)处理。二值化不是二分类,我们可以通过特征拆解,将一个特征用多个特征表示,实现多分类。

1
2
3
4
5
6
Freezing    --> 0 --> 0 0 0
Warm --> 1 --> 0 0 1
Cold --> 2 --> 0 1 0
Boiling Hot --> 3 --> 0 1 1
Hot --> 4 --> 1 0 0
Lava Hot --> 5 --> 1 0 1

这只是将类别转换为数字,然后再转换为二值化表示。这样,我们就把一个特征分成了三个(在本例中)特征(或列)。如果我们有更多的类别,最终可能会分成更多的列。对应的特征 Feature 就可以由 Feature_0、Feature_1、Feature_2 表示。
| Feature | Feature_0 | Feature_1 | Feature_2 |
| ——– | ——— | ——— | ——— |
| Warm | 0 | 0 | 1 |
| Hot | 1 | 0 | 0 |
| Lava Hot | 1 | 0 | 1 |

由于我们只关注其中的非0值(对应特征为1 的值)所以将上述特征格式转换成稀疏矩阵会进一步减少内存使用。用 1 表示矩阵的一种方法是某种字典方法,其中键是行和列的索引,值是 1。

1
2
3
4
5
(0, 2)      1
(1, 0) 1
(2, 0) 1
(2, 2) 1
# 占用32字节

在大数据矩阵中,稀疏矩阵可以减少内存的效果会更加明显。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import numpy as np
from scipy import sparse
n_rows = 10000
n_cols = 100000

# 生成符合伯努利分布的随机数组,维度为[10000, 100000]
example = np.random.binomial(1, p=0.05, size=(n_rows, n_cols))
print(f"Size of dense array: {example.nbytes}")
# Size of dense array: 8000000000

# 将随机矩阵转换为稀疏矩阵
sparse_example = sparse.csr_matrix(example)
print(f"Size of sparse array: {sparse_example.data.nbytes}")
# Size of sparse array: 399932496

full_size = (
sparse_example.data.nbytes +
sparse_example.indptr.nbytes +
sparse_example.indices.nbytes
)

print(f"Full size of sparse array: {full_size}")
# Full size of sparse array: 599938748

因此,密集阵列需要 ~8000MB 或大约 8GB 内存。而稀疏阵列只占用 399MB 内存。当我们的特征中有越多零时,稀疏阵列对内存的降低效果越显著。

独热编码

二值化特征的稀疏表示比其密集表示所占用的内存要少得多,但对于分类变量来说,还有一种转换所占用的内存更少。这就是所谓的 “独热编码”。
独热编码也是一种二值编码,因为只有 0 和 1 两个值。但必须注意的是,它并不是二值表示法。我们可以通过下面的例子来理解它的表示法。
假设我们用一个向量来表示 ord_2 变量的每个类别。这个向量的大小与 ord_2 变量的类别数相同。在这种特定情况下,每个向量的大小都是 6,并且除了一个位置外,其他位置都是 0。让我们来看看这个特殊的向量表。
| Category | Fea_0 | Fea_1 | Fea_2 | Fea_3 | Fea_4 | Fea_5 |
| ———– | :—: | :—: | :—: | :—: | :—: | :—: |
| Freezing | 0 | 0 | 0 | 0 | 0 | 1 |
| Warm | 0 | 0 | 0 | 0 | 1 | 0 |
| Cold | 0 | 0 | 0 | 1 | 0 | 0 |
| Boiling Hot | 0 | 0 | 1 | 0 | 0 | 0 |
| Hot | 0 | 1 | 0 | 0 | 0 | 0 |
| Lava Hot | 1 | 0 | 0 | 0 | 0 | 0 |

使用热独编码相比二值化编码,在进行稀疏化后,可以更加节省内存,还是以之前的例子,我们表示 Warm、Hot、Lava Hot 这三个类别的稀疏表示如下:

1
2
3
4
(1, 4)      1
(4, 1) 1
(5, 0) 1
# 直接少了一个非零值,仅占用24字节

二值化中示例的更大的数据,我们来看看变化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import numpy as np
from sklearn import preprocessing

# 生成符合均匀分布的随机整数,维度为[1000000, 10000000]
example = np.random.randint(1000, size=1000000)
# 独热编码,非稀疏矩阵
ohe = preprocessing.OneHotEncoder(sparse=False)
# 将随机数组展平
ohe_example = ohe.fit_transform(example.reshape(-1, 1))
print(f"Size of dense array: {ohe_example.nbytes}")
# Size of dense array: 8000000000

# 独热编码,稀疏矩阵
ohe = preprocessing.OneHotEncoder(sparse=True)
# 将随机数组展平
ohe_example = ohe.fit_transform(example.reshape(-1, 1))
print(f"Size of sparse array: {ohe_example.data.nbytes}")
# Size of sparse array: 8000000

full_size = (
ohe_example.data.nbytes +
ohe_example.indptr.nbytes +
ohe_example.indices.nbytes
)
print(f"Full size of sparse array: {full_size}")
# Full size of sparse array: 16000004

这里的密集阵列大小约为 8GB,稀疏阵列在二值化时是300MB,使用热独编码的稀疏矩阵只有 8MB。

罕见类

有时候
罕见类是指在数据集中出现次数少于一定阈值的类别。

-------------本文结束感谢您的阅读-------------