在半研墨:数据分析思考笔记(1):用特征选择方法优化模型|python数据挖掘一文中有说到在数据建模过程中,关于特征工程中特征选择的一些思考。
今天,我们再来聊聊关于特征处理中数据编码问题的一些思考。
某些机器学习算法和模型只能接受定量特征的输入,需要将定性特征转换为定量特征。 sklearn中的数据预处理preproccessing库提供了相关的处理方法:
OneHotEncoder(),
LabelEncoder(),
LabelBinarizer(),
MultiLabelBinarizer()
Binarizer
OneHotEncoder(独热编码)
为什么能使用one hot?
1.使用one-hot编码,将离散特征的取值扩展到了欧式空间,离散特征的某个取值就对应欧式空间的某个点。
2.将离散特征通过one-hot编码映射到欧式空间,是因为,在回归,分类,聚类等机器学习算法中,特征之间距离的计算或相似度的计算是非常重要的,而我们常用的距离或相似度的计算都是在欧式空间的相似度计算,计算余弦相似性,基于的就是欧式空间。
3.将离散型特征使用one-hot编码,可以会让特征之间的距离计算更加合理。
比如,有一个离散型特征,代表工作类型,该离散型特征,共有三个取值,不使用one-hot编码,其表示分别是x_1 = (1), x_2 = (2), x_3 = (3)。两个工作之间的距离是,d(x_1, x_2) = 1, d(x_2, x_3) = 1, d(x_1, x_3) = 2。那么x_1和x_3工作之间就越不相似吗?显然这样的表示,计算出来的特征的距离是不合理。
那如果使用one-hot编码,则得到x_1 = (1, 0, 0), x_2 = (0, 1, 0), x_3 = (0, 0, 1),那么两个工作之间的距离就都是sqrt(2).即每两个工作之间的距离是一样的,显得更合理。
one hot 编码的优点:
1.能够处理非连续型数值特征。
2.在一定程度上也扩充了特征。比如性别本身是一个特征,经过one hot编码以后,就变成了男或女两个特征。
#使用onehot编码
import pandas as pd
df2 = pd.DataFrame({'id': [3566841, 6541227, 3512441],
'sex': [1, 2, 2],
'level': [3, 1, 2]})
#输出df2
id level sex
0 3566841 3 1
1 6541227 1 2
2 3512441 2 2
from sklearn.preprocessing import OneHotEncoder #导入OneHotEncoder
id_data = df2.values[:, :1] # 获得ID列
transform_data = df2.values[:, 1:] # 指定要转换的列
enc = OneHotEncoder() # 建立模型对象
df2_new = enc.fit_transform(transform_data).toarray() # 转换
# 查看df2_new
array([[ 0., 0., 1., 1., 0.],
[ 1., 0., 0., 0., 1.],
[ 0., 1., 0., 0., 1.]])
df2_all = pd.concat((pd.DataFrame(id_data),pd.DataFrame(df2_new)),axis=1)#组合数据帧
# 输出df2_all,可以看到特征由原来的3个变成了现在的6个
0 0 1 2 3 4
0 3566841 0.0 0.0 1.0 1.0 0.0
1 6541227 1.0 0.0 0.0 0.0 1.0
2 3512441 0.0 1.0 0.0 0.0 1.0
# 再来看下不使用onehot编码,使用自定义转
import pandas as pd
df = pd.DataFrame({'id': [3566841, 6541227, 3512441],
'sex': ['male', 'Female', 'Female'],
'level': ['high', 'low', 'middle']})
# 输出df
id level sex
0 3566841 high male
1 6541227 low Female
2 3512441 middle Female
df_new = df.copy() #复制新的一份数据框用来存储转换结构
for col_num, col_name in enumerate(df): # 循环读出每个列的索引值和列名
col_data = df[col_name] # 获得每列数据
col_dtype = col_data.dtype # 获得每列dtype类型
if col_dtype == 'object': # 如果dtype类型是object(非数值型),执行条件
df_new = df_new.drop(col_name, axis=1) # 删除df数据框中要进行标志转换的列
value_sets = col_data.unique() # 获取分类和顺序变量的唯一值域
for value_unique in value_sets: # 读取分类和顺序变量中的每个值
col_name_new = col_name + '_' + value_unique # 创建新的列名,使用原标题+值的方式命名
col_tmp = df.iloc[:, col_num] # 获取原始数据列
new_col = (col_tmp == value_unique) # 将原始数据列与每个值进行比较,相同为True,否则为False
df_new[col_name_new] = new_col # 为最终结果集增加新列值
# 输出df_new,可以看到level,sex分别都由1个特征变成了3个特征
id level_high level_low level_middle sex_male sex_Female
0 3566841 True False False True False
1 6541227 False True False False True
2 3512441 False False True False True
使用one hot 编码注意点:
a,OneHotEncoder 的输入必须是 2-D array
from sklearn.preprocessing import OneHotEncoder
#data.cloumn1 返回的 Series 本质上是 1-D array
#所以要改成data[['cloumn1']] 2-D array再传入
OneHotEncoder(sparse = False).fit_transform( data[['cloumn1']])
b,OneHotEncoder无法直接对字符串型的类别变量编码。
OneHotEncoder无法直接对字符串型的类别变量编码,所以如果想利用OneHotEncoder对字符串型类别变量的支持,所以一般都采用曲线救国的方式:
from sklearn.preprocessing import OneHotEncoder
from sklearn.preprocessing import LabelEncoder
#先用 LabelEncoder() 转换成连续的数值型变量
a = LabelEncoder().fit_transform(data['cloumn'])
#再用 OneHotEncoder() 二值化
OneHotEncoder( sparse=False ).fit_transform(a.reshape(-1,1)) # 注意: 这里把 a 用 reshape 转换成 2-D array
c,离散特征如果是数值型变量,则数值变量的取值之间没有大小的意义,如果是分类变量,则是无序变量。
d,对于每一个特征,如果它有m个可能值,那么经过独热编码后,就变成了m个二元特征。并且,这些特征互斥,每次只有一个激活。因此,数据会变成稀疏的。
#示例
>>> data=np.array([[1,0,3.25],
[0,0,5.2],
[2,1,3.6]])
>>> enc=OneHotEncoder(categorical_features=np.array([0,1]),n_values=[3,2])
>>> enc.fit(data)
>>> enc.transform(data).toarray()
array([[ 0. , 1. , 0. , 1. , 0. , 3.25],
[ 1. , 0. , 0. , 1. , 0. , 5.2 ],
[ 0. , 0. , 1. , 0. , 1. , 3.6 ]])
categorical_features是需要独热编码的列索引,
n_values是对应categorical_features中各列下类别的数目,
注意这里两个值可以不指定,直接使用fit_transform函数也可以,程序将统计各列中类别的多少。但是只对整数有效,对浮点数会转换为整数之后再统计,也就是对于3.5和3.6默认都是3,也就是同一类。
如果指定了这两个参数,就要对未转换的数据提出要求,各列必须是以{0,1,2,3,4......}来编码,而不能以{1,10,100,200.........}这种随意的方式来编码。 否则会出现数组越界的错误。
LabelEncode(标签编码)
LabelEncoder是一个可以用来将标签规范化的工具类,它可以将标签的编码值范围限定在[0,n_classes-1]. 当然,它也可以用于非数值型标签的编码转换成数值标签(只要它们是可哈希并且可比较的):
#示例
>>> le = preprocessing.LabelEncoder()
>>> le.fit(["paris", "paris", "tokyo", "amsterdam"])
LabelEncoder()
>>> list(le.classes_)
['amsterdam', 'paris', 'tokyo']
>>> le.transform(["tokyo", "tokyo", "paris"])
array([2, 2, 1])
>>> list(le.inverse_transform([2, 2, 1]))
['tokyo', 'tokyo', 'paris']
注:示例来源于官方文档
LabelEncoder
设计为只支持 1-D array,也使得它无法像上面 OneHotEncoder 那样批量接受多列输入。
LabelBinarizer(标签二值化)
LabelBinarizer是一个用来从多类别列表创建标签矩阵的工具类:
>>> from sklearn import preprocessing
>>> lb = preprocessing.LabelBinarizer()
>>> lb.fit([1, 2, 6, 4, 2])
LabelBinarizer(neg_label=0, pos_label=1, sparse_output=False)
>>> lb.classes_
array([1, 2, 4, 6])
>>> lb.transform([1, 6])
array([[1, 0, 0, 0],
[0, 0, 0, 1]])
注:示例来源于官方文档
无论 LabelEncoder() 还是 LabelBinarizer(),他们在 sklearn 中的设计初衷,都是为了解决标签 y 的离散化,而非输入 X。
所以他们的输入被限定为 1-D array,这恰恰跟 OneHotEncoder() 要求输入 2-D array 相左。因此我们使用的时候要格外小心。
Binarization(特征二值化)
特征二值化 是 将数值特征用阈值过滤得到布尔值
公式表达如下:
>>> X = [[ 1., -1., 2.],
... [ 2., 0., 0.],
... [ 0., 1., -1.]]
>>> binarizer = preprocessing.Binarizer().fit(X) # fit does nothing
>>> binarizer
Binarizer(copy=True, threshold=0.0)
>>> binarizer = preprocessing.Binarizer(threshold=1.1)
>>> binarizer.transform(X)
array([[ 0., 0., 1.],
[ 1., 0., 0.],
[ 0., 0., 0.]])
注:示例来源于官方文档