在机器学习中,处理离散属性(也称为分类变量或类别特征)是一个重要的任务,需要将离散属性转换为可供模型使用的数值表示。以下是几种常见的处理离散属性的方法:

  1. 标签编码(Label Encoding):标签编码将每个类别映射到整数值,从0开始递增。这种方法对于具有有序关系的类别特征很有用,但它不适用于没有明显顺序的类别。
  2. 序号编码(Ordinal Encoding):在机器学习中,序号编码(Ordinal Encoding)是一种将离散特征的各个类别映射为整数序号的方法。序号编码适用于有序特征,其中类别之间存在一定的顺序关系,但没有明确的意义。
  3. 独热编码(One-Hot Encoding):这是最常用的方法之一,它将每个离散属性的每个类别创建一个新的二进制特征。对于每个样本,只有一个二进制特征为1,表示它属于对应的类别,其他特征为0。这种方法适用于具有有限数量的类别。
  4. 频数编码(Count Encoding):频数编码将每个类别替换为该类别在数据集中的频数或出现次数。这种编码方法可以提供关于类别的频率信息,但可能引入一定的信息泄漏。
  5. 目标编码(Target Encoding):目标编码将离散属性的每个类别编码为其在目标变量上的平均值或其他统计信息。目标编码能够捕获类别与目标变量之间的关联性,但需要注意信息泄漏和过拟合的问题。


1 标签编码(Label Encoding)

标签编码是将不同离散量使用一个唯一的数字来表示。

例如:['beijing','shanghai','shengzheng']编码为[1,2,3]

注意:

  1. Label Encoding只是将文本转化为数值,并没有解决文本特征的问题.
  2. Label Encoding将文本转化的数值是随机的,无法指定文本内在的顺序,如学历中的本科<硕士<博士。
  3. Label Encoding将文本转化的数值,计算不同样本间距离没有意义。
  4. 若要指定'本科 硕士 博士'分别为'1 2 3',可以使用序列编码。

所有的标签都变成了数字,算法模型直接将根据其距离来考虑相似的数字,而不考虑标签的具体含义。使用该方法处理后的数据适合支持类别性质的算法模型,如LightGBM。

1.1 使用sklearn.preprocessing.LabelEncoder实现标签编码

from sklearn.preprocessing import LabelEncoderle = LabelEncoder()city_list = ["paris", "paris", "tokyo", "amsterdam"]le.fit(city_list)city_list_le = le.transform(city_list)  # 进行Encodecity_list_new = le.inverse_transform(city_list_le)  # 进行decodeprint(f'原始数据为:{city_list}')print(f'list中含有的类别有:{le.classes_}')  # 输出为:['amsterdam' 'paris' 'tokyo']print(f'编码后:{city_list_le}')  # 输出为:[1 1 2 0]print(f'解码后:{city_list_new}') # 输出为:['paris' 'paris' 'tokyo' 'amsterdam']

1.2 pandas + sklearn.preprocessing.LabelEncoder 实现标签编码

在使用LabelEncoder之前要对数据进行缺失值处理,比如fillna("None),不然编码的时候会报错

import pandas as pdfrom sklearn.preprocessing import LabelEncodercity_list = ["paris", "paris", "tokyo", "amsterdam"]df = pd.DataFrame(city_list)le = LabelEncoder()encoder = le.fit(df.iloc[:,0])df.iloc[:,0] = encoder.transform(df.iloc[:,0])print(df)

对DataFrame中多个列批量编码

import pandas as pdfrom sklearn.preprocessing import LabelEncoderdf = pd.DataFrame({

    'pets': ['cat', 'dog', 'cat', 'monkey', 'dog', 'dog'],

    'owner': ['Champ', 'Ron', 'Brick', 'Champ', 'Veronica', 'Ron'],

    'location': ['San_Diego', 'New_York', 'New_York', 'San_Diego', 'San_Diego',

                 'New_York']})print(df)

df_train = dfd = {}le = LabelEncoder()cols_to_encode = ['pets', 'owner', 'location'] # 需要编码的列for col in cols_to_encode:

    df_train[col] = le.fit_transform(df_train[col])

    d[col] = le.classes_print(df_train)print(d)

1.3 Pandas.factorize()实现标签编码

Pandas的factorize()可以将Series中的标称型数据映射称为一组数字,相同的标称型映射为相同的数字。factorize函数的返回值是一个tuple(元组),元组中包含两个元素。第一个元素是一个array,其中的元素是标称型元素映射为的数字;第二个元素是Index类型,其中的元素是所有标称型元素,没有重复。

import numpy as npimport pandas as pddf = pd.DataFrame(['green','bule','red','bule','green'],columns=['color'])print(pd.factorize(df['color']))print(pd.factorize(df['color'])[0])print(pd.factorize(df['color'])[1])

2 序列编码(Ordinal Encoding)

将指定的文本映射到指定的数值上。

注意:

1. 适用于各个特征有内在的顺序,如学历中'本科 硕士 博士'分别为'1 2 3'

2. 计算不同样本之间的距离有一定的意义,若想要得到更加精确的距离值,需要给定精确的映射表,如实际需要本科、硕士之间的距离<硕士、博士之间的距离,但是按上述的映射表来计算,两者距离相等。

2.1 DataFrame.map实现序列编码

import pandas as pd

degree_list = ["硕士", "博士", "本科", "硕士"]df = pd.DataFrame(degree_list,columns=['学历'])print(df)ord_map = {'本科':1,'硕士':2,'博士':3}df['学历'] = df['学历'].map(ord_map)print(df)

3 独热编码(One Hot Encoding)

独热编码,即 One-Hot 编码,又称一位有效编码,其方法是使用N位状态寄存器来对N个状态进行编码,每个状态都有它独立的寄存器位,并且在任意时候,其中只有一位有效。

比如颜色特征有3种:红色、绿色和黄色,转换成独热编码分别表示为:001, 010, 100。

优缺点:

  • 优点:独热编码解决了分类器不好处理属性数据的问题,在一定程度上也起到了扩充特征的作用。它的值只有0和1,不同的类型存储在垂直的空间。
  • 缺点:当类别的数量很多时,特征空间会变得非常大。在这种情况下,一般可以用PCA来减少维度。而且one hot encoding+PCA这种组合在实际中也非常有用。

什么时候用one Hot Encoding?

  • 用:独热编码用来解决类别型数据的离散值问题
  • 不用:将离散型特征进行one-hot编码的作用,是为了让距离计算更合理,但如果特征是离散的,并且不用one-hot编码就可以很合理的计算出距离,那么就没必要进行one-hot编码。有些基于树的算法在处理变量时,并不是基于向量空间度量,数值只是个类别符号,即没有偏序关系,所以不用进行独热编码。 Tree Model不太需要one-hot编码: 对于决策树来说,one-hot的本质是增加树的深度。

3.1 LabelBinarizer实现独热编码

  • LabelBinarizer接受一维数组。

from sklearn.preprocessing import LabelBinarizer

 lb = LabelBinarizer()

 city_list = ["suzhou", "suzhou", "wuxi", "shanghai", 'beijing']

 lb.fit(city_list)print(lb.classes_)  # 输出为:['amsterdam' 'beijing' 'paris' 'tokyo']

 city_list_le = lb.transform(city_list)  # 进行Encodeprint(city_list_le)# [[0 0 1 0]#  [0 0 1 0]#  [0 0 0 1]#  [1 0 0 0]#  [0 1 0 0]]

city_list_new = lb.inverse_transform(city_list_le)  # 进行decodeprint(city_list_new)  # 输出为:["paris", "paris", "tokyo", "amsterdam", 'beijing']

3.2 sklearn.preprocessing.OneHotEncoder实现独热编码

  • OneHotEncoder仅支持二维数组,每一行表示一个样本。
  • 若输入的数组为n*m,其中m>1,则会分别对每一列属性单独编码,最后横向拼接后返回结果

from sklearn.preprocessing import OneHotEncoderimport numpy as np

enc = OneHotEncoder()city_arr = np.array(["suzhou", "suzhou", "wuxi", "shanghai", 'beijing'])city_arr = city_arr.reshape(-1,1)print(city_arr)city_arr_enc = enc.fit_transform(city_arr)    # fit来学习编码,返回稀疏矩阵print(city_arr_enc.toarray())print(enc.categories_)print(enc.get_feature_names())

3.3 pd.get_dummies实现独热编码

  • pandas 自带独热编码方式

import pandas as pd

df = pd.DataFrame([

    ['green', 'Chevrolet', 2017],

    ['blue', 'BMW', 2015],

    ['yellow', 'Lexus', 2018],])df.columns = ['color', 'make', 'year']df_processed = pd.get_dummies(df, prefix_sep="_", columns=df.columns[:-1])print(df_processed)

4 频数编码

将类别特征替换为训练集中的计数(一般是根据训练集来进行计数,属于统计编码的一种,统计编码,就是用类别的统计特征来代替原始类别,比如类别A在训练集中出现了100次则编码为100)。

优点:

  1. 信息保留:频数编码将分类特征转换为数值特征,可以帮助模型更好地理解分类特征之间的关系,同时保留了部分类别信息。
  2. 提供有用的统计信息:频数编码利用了每个类别在数据集中的频次信息,不仅提供了类别本身的信息,还提供了关于类别分布的统计信息。这有助于模型更好地理解数据。
  3. 处理缺失数据和噪声鲁棒性:相对于独热编码等编码方法,频数编码更具有噪声鲁棒性,因为它将类别信息转换为连续的数值特征,并减少了维度空间的需求。这可能有助于减少过拟合和处理缺失数据的影响。
  4. 离群值敏感:对离群值很敏感,所以结果可以归一化或者转换一下(例如使用对数变换)。未知类别可以替换为1。

缺点:

  1. 引入偏见:频数编码会将分类特征的不同类别替换为它们在数据集中出现的频次,如果某个类别在数据集中的频次很高,可能会导致该类别的编码过大,从而引入偏见。
  2. 处理高基数特征的挑战:当分类特征具有高基数(即类别数目非常多)时,频数编码可能引入过多的维度,从而增加模型的复杂度和计算成本。
  3. 丢失部分信息:频数编码通过将类别信息转换为数值特征来进行编码,可能会丢失一部分类别本身的信息。这可能会影响模型对某些类别的学习能力。

4.1 category_encoders库实现频数编码

import category_encoders as cefeatures = ['Peking', 'Peking', 'Shanghai', 'Peking', 'Guangzhou', 'Shanghai']count_enc = ce.CountEncoder()df_count = count_enc.fit_transform(features)print(df_count)

4.2 DataFrame.groupby实现频数编码

import pandas as pd

data = pd.DataFrame([

    ['green', 'Chevrolet', 2017],

    ['blue', 'BMW', 2015],

    ['yellow', 'Lexus', 2018],

    ['yellow', 'audi', 2023]])data.columns = ['color', 'make', 'year']data_count = data.groupby(by='color',as_index=False)['color'].agg(['count'])print(data_count)

5 目标编码

目标编码的基本思想是使用目标变量(或称为标签或类别)的统计信息来编码分类变量。具体而言,目标编码将每个类别特征值替换为与该特征值相关的目标变量的统计指标,如平均值、中位数、频率等。这样,原始的分类变量就被转换为数值型特征,包含了与目标变量的相关信息。

例如:

x1

y

目标编码

beijing

9

9

beijing

8

9

beijing

10

9

shanghai

9

8

shanghai

7

8

beijing的目标编码值为 9+8+103=9 ;

shanghai的目标编码值为 9+72=8

优点:

  1. 信息丰富性:目标编码能够捕获分类变量与目标变量之间的关联性,将目标变量的统计信息编码为数值型特征。这样,模型可以利用更多的相关信息进行训练,提高模型的性能。
  2. 保留类别信息:相比于使用独热编码或标签编码等方法,目标编码能够保留原始的类别信息。这对于一些具有有序关系的分类变量,如年龄组、评级等,能够提供更具有解释性的特征表示。
  3. 处理高基数分类变量:对于具有大量类别的分类变量(高基数),独热编码会导致高维度的稀疏特征表示,而目标编码可以使用目标变量的统计信息来代替,从而减少特征的维度。

缺点:

  1. 信息泄漏:在目标编码中,使用了目标变量的统计信息作为编码依据,因此可能会导致信息泄漏。即使在交叉验证中也有可能出现该问题。在进行目标编码时,需要小心处理,确保不将测试集中的信息泄漏到训练集中。
  2. 过拟合风险:目标编码过于依赖于目标变量的统计信息,可能导致在训练集上过拟合。特别是当类别数量较少或数据量较少时,过拟合风险更高。为了解决这个问题,可以使用平滑技术(如平均值编码、贝叶斯平滑等)来减轻过拟合风险。
  3. 对异常值敏感:如果分类变量中存在异常值,目标编码可能会受到影响。异常值的存在可能导致目标变量的统计指标产生偏移,从而影响编码结果。因此,在应用目标编码之前,需要先进行异常值处理。

from category_encoders import TargetEncoderimport pandas as pdfrom sklearn.datasets import load_boston# prepare some databunch = load_boston()y_train = bunch.target[0:250]y_test = bunch.target[250:506]X_train = pd.DataFrame(bunch.data[0:250], columns=bunch.feature_names)X_test = pd.DataFrame(bunch.data[250:506], columns=bunch.feature_names)# use target encoding to encode two categorical featuresenc = TargetEncoder(cols=['CHAS', 'RAD'])# transform the datasetstraining_numeric_dataset = enc.fit_transform(X_train, y_train)testing_numeric_dataset = enc.transform(X_test)

6 其他notes

各类编码的适用场景:

  • 若分类属性中各个特征值之间存在顺序关系,则可以使用序列编码。如学历的本科、硕士、博士。
  • 若分类属性中的各个特征值彼此间无序、无关联,且特征值的取值数量较少,可以使用独热编码。如性别。
  • 若只是想用一个数组代表文本,且适用独热编码产生的维度过高,可以使用标签编码。如使用树模型中`喜爱颜色`属性可以使用标签属性。
  • 若类别的频数可能与目标变量之间存在相关性,则适合使用频数编码。如不同城市的人爱吃辣的程度:四川地区选择爱吃辣的频数>江苏地区选择爱吃辣的频数。
  • 若分类编码与目标遍历间具有一定关联性,则适合使用目标编码,如一个城市的房价与其所处的区域有很大关系,使用目标编码计算同一个区域的平均房价来代替区域属性上的离散值:上海黄埔区的目标编码>上海嘉定区的目标编码

关于频数编码和目标编码

频数编码和目标编码可能会引入一定的信息泄漏,导致过拟合问题。

为了解决过拟合,可以1.加入平滑;2.beta target encoding;3.交叉验证;

选择目标编码,而不是独热编码

  • 高维数据特征:具有大量类别的可能很难编码:One-hot会生成太多维度,而替代方案(如标签编码)可能不适合该类型。Target encoding在此处就很好的解决了这个问题;
  • 领域经验特征:根据之前的经验,即使某项数据它在特征度量方面得分很低,你也可能会觉得一个分类特征应该很重要。Target encoding有助于揭示特征的真实信息。

关于独热编码:

将离散型特征进行one-hot编码的作用,是为了让距离计算更合理,但如果特征是离散的,并且不用one-hot编码就可以很合理的计算出距离,那么就没必要进行one-hot编码。

有些基于树的算法在处理变量时,并不是基于向量空间度量,数值只是个类别符号,即没有偏序关系,所以不用进行独热编码。

如Tree Model不太需要one-hot编码: 对于决策树来说,one-hot的本质是增加树的深度。

其他notes.....挖坑,待填。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐