在一份数据集中通常会遇见两类数据——数值型与类别型,数值型变量通常就是 int、float 类型,类别型变量就是 object 类型,也就是我们总说的字符型变量。如果更官方地讲,数值型变量被称作定量变量、类别型变量被称作定性变量。
数值型变量主要体现在连续值和离散值:
- 连续值:体温、房屋面积等
- 离散值:人数、个数等
我们都知道在大多数机器学习算法中都要与 ” 距离 ” 多多少少都会有些关系,所以只允许传入数值型变量,在不需要做其它处理的前提下,原始数据集中的数值型变量都是可以直接使用的,典型的算法代表有支持向量机、逻辑回归、KNN 算法等。
当然也有算法是可以处理字符型变量的,比如朴素贝叶斯、决策树、随机森林等,这类模型不关心变量的取值,而是注重变量间的分布状况和变量之间的条件概率,但如果要通过 sklearn 调用这些算法的 API 时,规定也必须要传入数值型变量才合法。所以这个时候通常会面对一个问题,如何将字符型变量转化成数值型变量呢?
LabelEncoder
我自己随意构建了一个数据集,共有五个特征,一个类标签,可以看到只有 Age 和 Heat 是数值型变量,其余四个皆为字符型 (类别型) 变量。如果不调用任何 API 要将一列字符型数据转化为数值型数据,可以利用自定义函数结合 apply 或 map 函数实现,先以标签变量 Label 举例。
def label(e):
if e == '猛男':
return 0
if e == '美女':
return 1
else:
return 2
Data["Label"] = Data["Label"].apply(label)
这种方式针对类别较少的特征还算可行,但是类别一多,if 语句会写吐的,并且可观性极差极差!在 sklearn 中有专门处理类别标签的 API,只需要索引出对应的标签变量并传入即可。
from sklearn.preprocessing import LabelEncoder
LabE = LabelEncoder()# 实例化
label = LabE.fit_transform(Data1.iloc[:,-1])
Data1.iloc[:,-1] = label
#一行表示
#Data1.iloc[:,-1] = LabelEncoder().fit_transform(Data1.iloc[:,-1])
一行表示是上面的化简形式,最后的效果都是相同的,实例化之后还可以通过相应的方法获取标签变量中的类别。
LabE.classes_
'''array([' 猛男 ',' 美女 ',' 靓仔 '], dtype=object)'''
最后运行的结果如下:
OrdinalEncoder
处理过标签变量之后,自然也要处理特征中的,方法呢可以选择利用上文提及的自定义函数的方法,但很明显的弊端就是每一列都需要一个自定义函数,导致效率太低了。sklearn 中也有处理特征中为类别型变量的 API,使用方法与上文大体相同:
from sklearn.preprocessing import OrdinalEncoder
Data2 = Data1.copy()
OrE = OrdinalEncoder()# 实例化
Data2.iloc[:,2:-1] = OrE.fit_transform(Data2.iloc[:,2:-1])
#Data2.iloc[:,2:-1] = OrdinalEncoder().fit_transform(Data2.iloc[:,2:-1])
与 LabelEncoder 最大不同之处就是 OrdinalEncoder 要求传入数据不能为一维,意思就是如果你只想转换一个特征 (上面代码为三个),那么你需要通过 reshape(-1,1) 将一维转换为二维再传入,而 LabelEncoder 是只针对标签变量的方法,所以自然接受一维数据。
OrdinalEncoder 是没有 class_方法的,取而替之的是 categories_,作用也是获取传入数据每个特征的类别。
OrE.categories_
'''[array([' 女 ',' 男 '], dtype=object),
array(['优', '差', '良'], dtype=object),
array(['>40kg', '>50kg', '>60kg'], dtype=object)]
'''
最后运行的结果如下:
OneHotEncoder
现在我们要考虑一个问题,就是我们这么转换变量真的合理吗?上面介绍了数值型变量,这里再来简要说一下类别型变量。
类别型变量按照变量间是否存在次序关系,可以分为无序型变量和有序型变量,而有序型变量在可计算的约束下又可以说成有距变量:
- 无序型变量:数据集中的性别
- 有序型变量:数据集中的体重
- 有距变量:数据集中的成绩
先解释一下有距变量,比如上面数据集中的体重有三个类别 60kg、50kg、40kg,很明显这是有序的,但是这三者间是存在可计算关系的,比如 60kg-10kg=50kg,类别之间可以通过一定计算相互转换。
再看有序型变量,比如上面数据集中的成绩也有三个类别优、良、差,这三个类别并不是毫无关系,因为在某种意义上 ” 优 ” 强于 ” 良 ”、” 良 ” 强于 ” 差 ”,很明显这也是有序变量,但这可不是有距变量,因为 $ 优 - 良 \neq 差 $ 是一定的。而无序变量理解起来就非常简单了,性别就是一个特别典型的例子,类别间的关系就是 $ 男 \neq 女 $,这是一种 ” 有你就没我的关系 ”。
前面在通过 OrdinalEncoder 对这三个类别型变量进行转换时,都会转换成相应的数字,从算法的角度来讲,如果你给我传入的数据为数字,那么我就认为它是可以相互计算的,但是数字是有大小之分的,2 和 0 参与计算起到的作用自然不同。
但我们已经清楚只有有距变量才有这样的相互计算的性质,而无序变量和有序变量经过编码转换后被误解成了有距变量,忽略掉了数字本身的性质,自然会影响后期的建模,而对于这样的变量可以利用独热编码将原本变量转化为哑变量。
from sklearn.preprocessing import OneHotEncoder
OneHot = OneHotEncoder()# 实例化
result = OneHot.fit_transform(Data1.iloc[:,2:4]).toarray()
OneHotnames = OneHot.get_feature_names().tolist()# 获取新特征名称
OneHotDf = pd.DataFrame(result,columns=OneHotnames)
Data3.drop(columns=['Sex','Grade'],inplace=True)
Data3 = pd.concat([OneHotDf,Data3],axis=1)
最后运行的结果如下:
这个新建的 DataFrame 中的左半部分是由许多新的特征构成,每个特征只包含 0 和 1 两个元素,其中 1 代表有、0 代表没有。比如 Sex 特征中包含两个元素 ” 男 ” 和 ” 女 ”,而这两个元素就可以通过哑变量转化形成两个新的特征。Sex 特征中为 ” 男 ” 的样本在新特征 ”x0_男 ” 中就为 1,在 ”x0_女 ” 中就为 0,反之可推出 Sex 中的 ” 女 ” 在新特征中的分布。
Dummy Coding
上面利用 OneHotEncoder 进行哑变量转换略显复杂,在 Pandas 库中也有相应的方法可以起到同样的效果。
Data4 = pd.get_dummies(Data4)
一行代码显得更加整洁,最后运行结果如下,在 get_dummies 也有很多参数可以设置,比如新特征的名字等。
这里再介绍一下虚拟变量,虚拟变量和独热编码十分相似,不同之处在于虚拟变量比独热编码少生成一个变量,下图就是虚拟变量的一种形式。
在转换为虚拟变量后,以 Grade 这一特征为例,可以看到删去了 ”Grade_良 ” 这一新特征,因为对于一个人的成绩而言,” 优 ” 和 ” 差 ” 这两个特征足以表达出所有的信息,如果一个人成绩不是 ” 优 ” 和 ” 差 ”,那一定就是 ” 良 ”,所以原数据经独热编码后是有冗余的,而虚拟变量则很好地删去冗余,相应的代价就是虚拟变量在表达上没有独热编码直观。
参考链接:
[1].https://blog.csdn.net/weixin_…
[2].https://www.bilibili.com/vide…