【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呢?事实是任何数字的排序都无法表达出变量的差异性。

  1. 在回归,分类,聚类等机器学习算法中,特征之间距离的计算或相似度的计算是非常重要的,而我们常用的距离或相似度的计算都是在欧式空间的相似度计算,计算余弦相似性,基于的就是欧式空间;对离散型特征进行one-hot编码是为了让距离的计算显得更加合理。
  2. 编码后的特征会扩维(如性别编码后变成male[0,1],female[1,0]),这样数据会变得稀疏。稀疏对算法来说是有好处的:当某列的元素都为零时就意味着可以去除这个特征,则学习的难度、计算和存储开销都会降低;即便每个样本中有很多零元素,但是都不不是以整列或整行存在的,这样依旧有好处,比如支持向量机之说以能在文本数据上有很好的性能,就是由于数据有高度的稀疏性,是的大多数问题可以变得线性可分,而且稀疏矩阵有高效的存储方法,节约存储空间。

何时不需要编码

  1. 将离散型特征进行one-hot编码的作用,是为了让距离计算更合理,但如果特征是离散的,并且不用one-hot编码就可以很合理的计算出距离,那么就没必要进行one-hot编码。(如Titanic中的age特征)
  2. 有些基于树的算法在处理变量时,并不是基于向量空间度量,数值只是个类别符号,即没有偏序关系,所以不用进行独热编码。  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']

【sklearn实例】4--特征工程之离散值编码

注意,需要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)

【sklearn实例】4--特征工程之离散值编码

 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'])

【sklearn实例】4--特征工程之离散值编码

 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)) #将两个编好的特征组合起来

【sklearn实例】4--特征工程之离散值编码

1 OneHotEncoder编码方便,可以实现多个特征同时一次性编码:

【sklearn实例】4--特征工程之离散值编码

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",即如果碰到未知的类别,是返回一个错误还是忽略它。

参考:https://www.cnblogs.com/zhoukui/p/9159909.html【】