4.2 处理类别数据

到目前为止只研究了数值特征。然而,现实世界中含一个或多个类别特征列的数据集并不少见。我们将在本节以简单而有效的示例来讨论如何用数值计算库处理这类数据。

在讨论类别数据时,我们必须进一步区分序数(ordinal)特征和标称(nominal)特征。序数特征可以理解为可以排序的类别值。例如,T恤尺寸是一个有序特征,因为我们可以定义XL>L>M。相反,标称特征并不蕴涵任何顺序,例如T恤的颜色是标称特征,因为说红色比蓝色大没有什么意义。

4.2.1 用pandas实现类别数据的编码

在探讨处理这样的类别数据的不同技术之前,让我们创建一个新的DataFrame来说明这个问题:

089-01

从前面的输出可以看到,新创建的DataFrame包含一个标称特征(color)列、一个序数特征(size)列和一个数值特征(price)列。分类标签存储在最后一列。本书所讨论的分类学习算法并均不使用有序信息作为分类标签。

4.2.2 映射序数特征

为了确保机器学习算法能够正确地解读序数特征,我们需要将类别字符串值转换为整数。不幸的是,没有方便函数可以自动导出size特征标签的正确顺序,因此需要人工定义映射关系。在下面的简单例子中,假设我们知道特征之间的数值差异,例如XL=L+1=M+2:

089-02

如果想在以后再把整数值转换回原来字符串的形式,我们可以简单地定义一个反向映射字典inv_size_mapping={v: k for k, v in size_mapping.items()}(类似之前使用过的size-mapping字典),然后可以通过pandas的map方法用在变换后的特征列上,如下所示:

089-03

4.2.3 为分类标签编码

许多机器学习库都要求分类标签的编码为整数值。虽然大多数scikit-learn的分类估计器可以在内部实现整数分类标签的转换,但是通过将分类标签作为整数数组能够从技术角度避免某些问题的产生,在实践中这被认为是一个很好的做法。我们可以采用与之前讨论过的序数特征映射相似的方法为分类标签编码。需要记住的是,分类标签并是有序的,具体哪个整数匹配特定的字符串标签无关紧要。因此,可以从0开始简单地枚举:

090-01

接下来,可以用映射字典将分类标签转换为整数:

090-02

可以在字典中反向映射键值对,将转换后的分类标签匹配到原来的字符串,如下所示:

090-03

另外,也可以在scikit-learn中非常方便地直接调用LabelEncoder类来实现:

090-04

请注意,fit_transform方法只是分别调用fittransform的一种快捷方式,可以使用inverse_transform方法将分类的整数型分类标签转换回原来的字符串形式:

090-05

4.2.4 为名义特征做独热编码

4.2.2节我们用简单的字典映射方法将序数size特征转换为整数。由于scikit-learn分类估计器把分类标签当成无序的标称特征数据进行分类,我们可以用方便的LabelEncoder把字符串标签编码为整数。我们可以用类似的方法转换数据集中的标称特征color列,所实现的代码如下:

090-06

执行上述代码之后,NumPy数组x的第一列现在就有了新的color值,其编码格式为:

090-07

如果就此打住,并把数组提供给分类器,那么我们就会犯处理类别数据中最常见的错误。你知道问题所在吗?虽然颜色值并没有任何的特定顺序,但机器学习算法会假设green大于bluered大于green。尽管该假设并不正确,但是算法仍然可以产生有用的结果。然而这并不是最优的结果。

解决这个问题的常见方案是采用一种被称为独热编码(one-hot encoding)的技巧。该方法背后的逻辑是为标称特征列的每个唯一值创建一个新的虚拟特征。于是将把color特征转换为bluegreenred三个新特征。然后用二进制值表示样本的特定color;例如,一个blue样本可以编码为blue=1green=0red=0。我们可以使用scikit-learn的preprocessing模块中的OneHotEncoder来实现这种转换:

091-01

请注意,我们仅将OneHotEncoder应用于单列(X[:,0].reshape(-1, 1)),以避免再修改数组中的其他两列。如果想要选择性地转换多特征数组中的列,则可以使用ColumnTransformer,它接受(name, transformer, column(s))元组的列表,如下所示:

091-02

在前面的代码示例中,我们通过定义'passthrough'参数指定只修改第一列,而保持其他两列不变。

通过独热编码创建虚拟特征有一个更方便的方法,即使用pandas中实现的get_dummies方法。把get_dummies方法应用到DataFrame,只转换字符串列,而保持其他所有列不变:

091-03

当用独热编码技术为数据集编码时,必须小心它会带来多重共线性,对某些方法来说这可能是个问题(例如那些需要矩阵求逆的方法)。如果特征高度相关那么矩阵求逆是很难计算的,这可能会导致数值估计不稳定。为了减少变量之间的相关性,可以直接从独热编码数组中删除一个特征列。请注意,尽管我们删除了一个特征列,但并没失去任何重要的信息。例如,如果删除color_blue列,那么特征信息仍然可以得到保留,因为如果我们观察到color_green=0color_red=0,这意味着余下的观察结果一定是blue

在调用get_dummies函数时,可以通过给drop_first传递True参数来删除第一列,如下面的代码示例所示:

092-01

如果要用独热编码方法删除冗余列,我们就需要定义drop='first',并设置categories='auto',如下所示:

092-02

008-01

可选项:序数特征编码

如果我们不确定序数特征类别之间的数值差异,或者未定义两个序数值之间的差异,我们可以使用0/1阈值对它们进行编码。例如我们可以将具有M、L和XL值的特征size拆分为两个新特征,即“x>M”和“x>L”。让我们考虑原始DataFrame:

092-03

我们可以用pandas DataFrame的apply方法编写自定义的lambda表达式,以便调用value-threshold方法对这些变量进行编码:

092-04