【sklearn实例】4--特征工程之离散值编码
离散特征
离散特征变量类型可以分为有序类和无序类。
无序类,价值相等且可区分,没有等级、顺序、排序、好坏等逻辑关系,各变量相互独立:性别(男/女)、颜色(赤橙黄绿青蓝紫)、登机口(A/B/C);
有序类:各变量有级别大小等逻辑关系:尺码(L/XL/XXL)、学历(高/中/低)
为何要编码
对无序类:为何不能直接对特征进行赋值,比如male1,female0。这是不科学的,因为这样一来就存在了大小关系,在算法学习时就会利用其大小关系来训练和预测。任何两个数值如1和0,都无法表示出male与female的价值相等且可区分的平行独立的关系,但这样就可以了:male[0,1],female[1,0])
对有序类:比如学历变量是博士、硕士、本科用3-2-1来分别表示,那么问题来了:为何可以用3-2-1来表示而不能用30-20-10来表示?如果1000-900-800呢?事实是任何数字的排序都无法表达出变量的差异性。
- 在回归,分类,聚类等机器学习算法中,特征之间距离的计算或相似度的计算是非常重要的,而我们常用的距离或相似度的计算都是在欧式空间的相似度计算,计算余弦相似性,基于的就是欧式空间;对离散型特征进行one-hot编码是为了让距离的计算显得更加合理。
- 编码后的特征会扩维(如性别编码后变成male[0,1],female[1,0]),这样数据会变得稀疏。稀疏对算法来说是有好处的:当某列的元素都为零时就意味着可以去除这个特征,则学习的难度、计算和存储开销都会降低;即便每个样本中有很多零元素,但是都不不是以整列或整行存在的,这样依旧有好处,比如支持向量机之说以能在文本数据上有很好的性能,就是由于数据有高度的稀疏性,是的大多数问题可以变得线性可分,而且稀疏矩阵有高效的存储方法,节约存储空间。
何时不需要编码
- 将离散型特征进行one-hot编码的作用,是为了让距离计算更合理,但如果特征是离散的,并且不用one-hot编码就可以很合理的计算出距离,那么就没必要进行one-hot编码。(如Titanic中的age特征)
- 有些基于树的算法在处理变量时,并不是基于向量空间度量,数值只是个类别符号,即没有偏序关系,所以不用进行独热编码。 Tree Model不太需要one-hot编码: 对于决策树来说,one-hot的本质是增加树的深度。
编码方法
大致有三种:
LabelEncoder :适合处理字符型数据或label类,一般先用此方法将字符型数据转换为数值型,然后再用以下两种方法编码;
get_dummies :pandas 方法,处理DataFrame 数据更便捷
OneHotEncoder :更普遍的编码方法
需要保证测试集和训练集采用相同的编码处理,所以编码过程前,最好先将train和test数据结合起来统一编码,然后再将数据分离开。
1 LabelEncoder
将离散的数值或字符串,转化为连续的数值型数据。n个类别就用0到n-1个数表示。没有扩维,多用于标签列的编码(如果用于特征的编码,那编码后还要用get_dummies或OneHotEncoder进行再编码,才能实现扩维)
以Titanic中组合Parch 和Sibsp 特征并编码为例
#导入数据和工具
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
# 导入数据
train=pd.read_csv("train.csv")
test=pd.read_csv("test.csv")
#定义一个判断函数,将family按照人口数分类,
def family_size_category(family_size):
if family_size <= 1:
return 'Single'
elif family_size <= 4:
return 'Small_Family'
else:
return 'Large_Family'
# 编码
from sklearn.preprocessing import LabelEncoder # 导入LabelEncoder 工具
train['Family_Size'] = train['Parch'] + train['SibSp'] + 1 #添加一个新的特征,由两个特征组合而成
train['Family_Size_Category'] = train['Family_Size'].map(family_size_category) #根据新特征的人口数,用map函数,划分家庭类别:single、small、large
le_family = LabelEncoder()
#对要的编码类别进行fit,fit内必须为一个行数组1-D的array(可重复,会自动辨别)
le_family.fit(np.array(['Single', 'Small_Family', 'Large_Family']))
train['Family_Size_Category'] = le_family.transform(train['Family_Size_Category']) #对指定特征进行transform转换
print le_family.classes_ # 查看编码的所有标签类别
print le_family.inverse_transform(0) #查看0对应的原标签类别
train['Family_Size_Category']
注意,需要fit时候不能直接fit该列,需要将所有该列的所有类别提取出来组成一个一维数组来fit:fit(np.array(['Single', 'Small_Family', 'Large_Family']))
2 get_dummies
pandas编码工具,直接将数据扩维,表示变量间的独立和平行关系。
刚开始了解的时候我有个疑惑,本来就一个特征,这么一搞不是扩出来好几个吗,那还能用于训练吗?后来想想是多虑了,维数变多了又如何,扩出来的维数,算法训练的时候就把其当做一个新的特征去学习,结果没什么影响,只要没有维数灾难,那就是可以学习的。
仍以上述组合后并LabelEncoder的数据为例,对其再编码(实现扩维)
fam_size_cat_dummies_df = pd.get_dummies(train['Family_Size_Category'],
prefix=train[['Family_Size_Category']].columns[0])
train = pd.concat([train, fam_size_cat_dummies_df], axis=1) #将编码后的特征加入原数据里面
train.head(5)
1 get_dummies中,prefix:设置编码后的前缀,这里可以改为prefix='Family_Size_Category',直接给定前缀名
2 编码后的特征需要手动加入原数据集,方法有两种:
一种如上例所示:pd.concat([data,get_dummies_data], axis=1); 另一种:data=data.join(get_dummies_data)
3 自动生成编码列而不用手动加入的方法:train=pd.get_dummies(train,columns=['Pclass'])
get_dummies的特点:
优势:
1 本身就是 pandas 的模块,所以对 DataFrame 类型兼容很好.
2 无论你的列是字符型还是数字型都可以进行二值编码.
3 能根据用户指定,自动生成二值编码后的变量名.
缺点:
1 不是 sklearn 里的transformer类型,所以得到的结果得手动输入到 sklearn 里的相应模块
2 无法像 sklearn 的transformer一样可以输入到pipeline中 进行流程化地机器学习过程
3 get_dummies 不像 sklearn 的 transformer一样,有 transform方法,所以一旦测试集中出现了训练集未曾出现过的特征取值,简单地对测试集、训练集都用 get_dummies 方法将导致数据错误。
参考:https://blog.****.net/gao1440156051/article/details/55096630?utm_source=blogxgwz5
3 OneHotEncoder
最通用的编码方法,只能处理数值型数据,可以处理多列输入,多列同时编码。
但是,编码后为一个1-D的array,不适用于DataFrame格式,无法通过get_dummies的concat和join方法加入原数据中。
仍以LabelEncoder后的组合特征为例,将其再编码
from sklearn.preprocessing import OneHotEncoder
a1=OneHotEncoder(sparse=False).fit_transform(train[['Family_Size_Category']])
print 'a1\n',a1
a2=OneHotEncoder(sparse=False).fit_transform(train[['Pclass']])
print '\na2\n',a2
train.head()
final_output = numpy.hstack((a1,a2)) #将两个编好的特征组合起来
1 OneHotEncoder编码方便,可以实现多个特征同时一次性编码:
2 fit_transform(train[['Pclass']] )OneHotEncoder编码的数据需要是2-D的array,dataframe中,train.Pclass返回的Series是一个1-D的array,这里需要转化成二维的,用train[['Pclass']]
3 编码后的特征无法加入原数据中(应该会有方法能实现该功能,找到后来写)
参数详解
OneHotEncoder(n_values=’auto’, categorical_features=’all’, dtype=<class ‘numpy.float64’>, sparse=True, handle_unknown=’error’)
n_values:表示每个特征使用几维的数值由数据集自动推断,即几种类别就使用几位来表示,当然也可以自己指定,当指定后的类别个数大于数据中的类别个数时,则transform编码的时候就可以给多出来的类别进行编码.
categorical_features = 'all'
,这个参数指定了对哪些特征进行编码,默认对所有类别都进行编码。也可以自己指定选择哪些特征,通过索引或者 bool 值来指定。如:enc = OneHotEncoder(categorical_features = [0,2]),则只对第1和第3个特征进行编码,第二个特征保持原样放在最后。
sparse=True
表示编码的格式,默认为 True,即为稀疏的格式,需要加 transform(...).toarray()。指定 False 则就不用 toarray() 了
handle_unknown=’error’
,其值可以指定为 "error" 或者 "ignore",即如果碰到未知的类别,是返回一个错误还是忽略它。