博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[scikit-learn] 特征二值化编码函数的一些坑
阅读量:4286 次
发布时间:2019-05-27

本文共 4328 字,大约阅读时间需要 14 分钟。

1. 前言

这几天埋头撰写『优雅高效地数据挖掘——基于Python的sklearn_pandas库』 一文,其中有一部分涉及如何批量并行地进行特征二值化,在此过程中发现了 scikit-learn (以下简称 sklearn)中,二值化函数存在一些坑,跟 sklearn_pandas 的作者在 github 上交流过,在此总结一下,做个记录

所涉及到的几种 sklearn 的二值化编码函数:OneHotEncoder()LabelEncoder()LabelBinarizer()MultiLabelBinarizer()

2. 问题起源

首先造一个测试数据

import pandas as pd from sklearn.preprocessing import OneHotEncoder from sklearn.preprocessing import LabelEncoder from sklearn.preprocessing import LabelBinarizer from sklearn.preprocessing import MultiLabelBinarizer testdata = pd.DataFrame({
'pet': ['cat', 'dog', 'dog', 'fish'],                         'age': [4 , 6, 3, 3],                         'salary':[4, 5, 1, 1]})

这里我们把 petagesalary 都看做类别特征,所不同的是 age 和 salary 都是数值型,而 pet 是字符串型。我们的目的很简单: 把他们全都二值化,进行 one-hot 编码

2.1. 对付数值型类别变量

对 age 进行二值化很简单,直接调用 OneHotEncoder

OneHotEncoder(sparse = False).fit_transform( testdata.age ) # testdata.age 这里与 testdata[['age']]等价

然而运行结果是 array([[ 1.,  1.,  1.,  1.]]),这个结果是错的,从 Warning 信息中得知,原因是 sklearn 的新版本中,OneHotEncoder 的输入必须是 2-D array,而 testdata.age 返回的 Series 本质上是 1-D array,所以要改成

OneHotEncoder(sparse = False).fit_transform( testdata[['age']] )

我们得到了我们想要的:

array([[ 0.,  1.,  0.],        [ 0.,  0.,  1.],        [ 1.,  0.,  0.],        [ 1.,  0.,  0.]])

可以用同样的方法对 salary 进行 OneHotEncoder, 然后将结果用 numpy.hstack() 把两者拼接起来得到变换后的结果

a1 = OneHotEncoder(sparse = False).fit_transform( testdata[['age']] ) a2 = OneHotEncoder(sparse = False).fit_transform( testdata[['salary']]) final_output = numpy.hstack((a1,a2))

不过这样的代码略显冗余,既然 OneHotEncoder() 可以接受 2-D array 输入,那我们可以写成这样

OneHotEncoder(sparse = False).fit_transform( testdata['age', 'salary'])

结果为

array([[ 0.,  1.,  0.,  0.,  1.,  0.],        [ 0.,  0.,  1.,  0.,  0.,  1.],        [ 1.,  0.,  0.,  1.,  0.,  0.],        [ 1.,  0.,  0.,  1.,  0.,  0.]])

有时候我们除了得到最终编码结果,还想知道结果中哪几列属于 age 的二值化编码,哪几列属于 salary 的,这时候我们可以通过 OneHotEncoder() 自带的 feature_indices_ 来实现这一要求,比如这里 feature_indices_ 的值是[0, 3, 6],表明 第[0:3]列是age的二值化编码,[3:6]是salary的。更多细节请参考 sklearn 文档,

2.2. 对付字符串型类别变量

遗憾的是OneHotEncoder无法直接对字符串型的类别变量编码,也就是说OneHotEncoder().fit_transform(testdata[['pet']])这句话会报错(不信你试试)。已经有很多人在 stackoverflow 和 sklearn 的 github issue 上讨论过这个问题,但目前为止的 sklearn 版本仍没有增加OneHotEncoder对字符串型类别变量的支持,所以一般都采用曲线救国的方式:

  • 方法一 先用 LabelEncoder() 转换成连续的数值型变量,再用 OneHotEncoder() 二值化

  • 方法二 直接用 LabelBinarizer() 进行二值化

然而要注意的是,无论 LabelEncoder() 还是 LabelBinarizer(),他们在 sklearn 中的设计初衷,都是为了解决标签 y 的离散化,而非输入 X, 所以他们的输入被限定为 1-D array,这恰恰跟 OneHotEncoder() 要求输入 2-D array 相左。所以我们使用的时候要格外小心,否则就会出现上面array([[ 1.,  1.,  1.,  1.]])那样的错误

# 方法一: LabelEncoder() + OneHotEncoder() a = LabelEncoder().fit_transform(testdata['pet']) OneHotEncoder( sparse=False ).fit_transform(a.reshape(-1,1)) # 注意: 这里把 a 用 reshape 转换成 2-D array # 方法二: 直接用 LabelBinarizer() LabelBinarizer().fit_transform(testdata['pet'])

这两种方法得到的结果一致,都是

array([[ 1.,  0.,  0.],        [ 0.,  1.,  0.],        [ 0.,  1.,  0.],        [ 0.,  0.,  1.]])

正因为LabelEncoderLabelBinarizer设计为只支持 1-D array,也使得它无法像上面 OneHotEncoder 那样批量接受多列输入,也就是说LabelEncoder().fit_transform(testdata[['pet', 'age']])会报错。

2.3. 无用的尝试

然而执着如我怎会就此放弃,我又仔细翻了翻 sklearn 的 API 接口,果然发现有个叫 MultiLabelBinarizer() 的,看着似乎可以解决这个问题,于是尝试了一下

MultiLabelBinarizer().fit_transform(testdata[['age','salary']].values)

输出结果如下

array([[0, 0, 1, 0, 0],        [0, 0, 0, 1, 1],        [1, 1, 0, 0, 0],        [1, 1, 0, 0, 0]])

结果咋一看毫无问题,再仔细一看,被打脸!MultiLabelBinarizer并没有分别对每列进行 one-hot 编码,而是将这几列的取值看做一个整体,每行样本都被去重了,所以结果中第一行只有一个 1 ,因为 age 和 salary 第一行取值都是 4, MultiLabelBinarizer 默认这行样本只有一个类别 4 。。。。。。

3. 另一种解决方案

其实如果我们跳出 scikit-learn, 在 pandas 中可以很好地解决这个问题,用 pandas 自带的get_dummies函数即可

pd.get_dummies(testdata,columns=testdata.columns)

结果正是我们想要的

age_3   age_4   age_6   pet_cat pet_dog pet_fish    salary_1    salary_4    salary_5 0   0.0 1.0 0.0 1.0 0.0 0.0 0.0 1.0 0.0 1   0.0 0.0 1.0 0.0 1.0 0.0 0.0 0.0 1.0 2   1.0 0.0 0.0 0.0 1.0 0.0 1.0 0.0 0.0 3   1.0 0.0 0.0 0.0 0.0 1.0 1.0 0.0 0.0

get_dummies的优势在于:

  1. 本身就是 pandas 的模块,所以对 DataFrame 类型兼容很好

  2. 不管你列是数值型还是字符串型,都可以进行二值化编码

  3. 能够根据指令,自动生成二值化编码后的变量名

这么看来,我们找到最完美的解决方案了? No!get_dummies千般好,万般好,但毕竟不是 sklearn 里的transformer类型,所以得到的结果得手动输入到 sklearn 里的相应模块,也无法像 sklearn 的transformer一样可以输入到pipeline中 进行流程化地机器学习过程。更重要的一点

get_dummies 不像 sklearn 的 transformer一样,有 transform方法,所以一旦测试集中出现了训练集未曾出现过的特征取值,简单地对测试集、训练集都用 get_dummies 方法将导致数据错误

所以,若有高人有更好的解决方案,欢迎提出,非常感谢!!

4. 参考资料

  1. sklearn 官方文档

  2. pandas 官方文档

  3. StackOverFlow

转载地址:http://uqxgi.baihongyu.com/

你可能感兴趣的文章
iOS 网络请求判断连接和状态码
查看>>
iOS之ARC内存管理及强弱指针(二)
查看>>
iOS. Xcode7.1中在请求HTTP时报错的解决方法
查看>>
iOS 网络请求数据工具封装
查看>>
iOS之电商项目中的注意事项
查看>>
iOS文字上面划线的几中方式
查看>>
iOS MJRefresh的用法
查看>>
iOS 字典转模型的几中方法、KVC、runtime、YYModel、MJExtention
查看>>
iOS之coreData的原理和使用以及coreData中的多线程问题(二)
查看>>
iOS之支付宝集成(二)
查看>>
java基础(一)Java学习路线
查看>>
iOS之苹果自带的json解析NSJSONSerialization(序列化)
查看>>
iOS中坐标转换
查看>>
java 基础二
查看>>
java基础(三)方法/数组/堆栈/
查看>>
java基础(四)二维数组/
查看>>
java基础(五)面向对象/类/对象/形式参数/局部和成员变量
查看>>
java基础(六)关键字/private/this/static/构造方法/
查看>>
java基础(七)/面向对像
查看>>
java基础(八)Math/代码块/继承成员方法指南的关系/继承中成员变量之间的关系/方法的重写/继承中构造方法之间的关系/this和super的区别
查看>>