数据预处理与特征工程总结 - 分类型特征和连续型特征(三)

451 阅读7分钟

根据菜菜的课程进行整理,方便记忆理解

代码位置如下:

分类型特征:编码与哑变量

在机器学习中,大多数算法,譬如逻辑回归,支持向量机SVM,k近邻算法等都只能够处理数值型数据,不能处理文字,在sklearn当中,除了专用来处理文字的算法,其他算法在fit的时候全部要求输入数组或矩阵,也不能够导入文字型数据其实手写决策树和普斯贝叶斯可以处理文字,但是sklearn中规定必须导入数值型)。

然而在现实中,许多标签和特征在数据收集完毕的时候,都不是以数字来表现的。比如说,学历的取值可以是["小学",“初中”,“高中”,"大学"],付费方式可能包含["支付宝",“现金”,“微信”]等等。在这种情况下,为了让数据适应算法和库,我们必须将数据进行编码,即是说,将文字型数据转换为数值型

preprocessing.LabelEncoder

  • 标签专用,能够将分类转换为分类数值
from sklearn.preprocessing import LabelEncoder

y = data.iloc[:,-1]
le = LabelEncoder()
le = le.fit(y)
label = le.transform(y)

# classes_属性可以查看有多少的类别
le.classes_
# array(['No', 'Unknown', 'Yes'], dtype=object)

label

# 部分数据展示
"""
array([0, 2, 2, 2, 0, 0, 0, 0, 2, 2, 1, 2, 0, 0, 0, 1, 0, 2, 0, 2, 1, 2,2, 2, 0, 1, 0, 0, 2, 0, 0, 2, 2, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 1,2, 0, 0, 2, 0, 0, 0, 0, 2, 2, 0, 2, 2, 0, 2, 0, 0, 0, 0, 0, 2, 2,0, 2, 0, 0, 0, 0, 0, 2, 1, 0, 1, 2, 2, 0, 2, 2, 0, 2, 2, 0, 0, 2,0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0, 0, 0])
"""

# 可以一步到位
label = LabelEncoder().fit_transform(label)
  • 使用inverse_transform反转数据
# 可以使用inverse_transform将转化后的数据转换回去
le.inverse_transform(label)

# 部分数据展示
"""
array(['No', 'Yes', 'Yes', 'Yes', 'No', 'No', 'No', 'No', 'Yes', 'Yes','Unknown', 'Yes', 'No', 'No', 'No', 'Unknown', 'No', 'Yes', 'No','Yes', 'Unknown', 'Yes', 'Yes', 'Yes', 'No', 'Unknown', 'No', 'No','Yes', 'No', 'No', 'Yes', 'Yes', 'No', 'No', 'No', 'Yes', 'No','No', 'Yes', 'No', 'No', 'No', 'Unknown', 'Yes', 'No', 'No', 'Yes','No', 'No', 'No', 'No', 'Yes', 'Yes', 'No', 'Yes', 'Yes', 'No'], dtype=object)
"""
  • 对转化后的数据赋值
data.iloc[:,-1] = label
data.head(10)

image.png

  • 简化上面的代码
# 最精简的完成标签的转换
from sklearn.preprocessing import LabelEncoder
data.iloc[:,-1] = LabelEncoder().fit_transform(data.iloc[:,-1])
preprocessing.OrdinalEncoder

特征专用,能够将分类特征转换为分类数值

from sklearn.preprocessing import OrdinalEncoder

data_= data.copy()
data_.head()

ord = OrdinalEncoder().fit(data_.iloc[:,1:-1])

# categories_属性可以查看有多少的类别
ord.categories_
# [array(['female', 'male'], dtype=object), array(['C', 'Q', 'S'], dtype=object)]
  • 最精简的写法
# 最精简的完成标签的转换
from sklearn.preprocessing import OrdinalEncoder
data.iloc[:,1:-1] = OrdinalEncoder().fit_transform(data.iloc[:,1:-1])
preprocessing.OneHotEncoder

独热编码,创建哑变量

我们来思考三种不同性质的分类数据:

  • 舱门(S,C,Q)
    • 三种取值S,C,Q是相互独立的,彼此之间完全没有联系,表达的是S≠C≠Q的概念。这是名义变量。
  • 学历(小学,初中,高中)
    • 三种取值不是完全独立的,我们可以明显看出,在性质上可以有高中>初中>小学这样的联系,学历有高低,但是学历取值之间却不是可以计算的,我们不能说小学 + 某个取值 = 初中。这是有序变量。
  • 体重(>45kg,>90kg,>135kg)
    • 各个取值之间有联系,且是可以互相计算的,比如120kg - 45kg = 90kg,分类之间可以通过数学计算互相转换。这是有距变量。

然而在对特征进行编码的时候,这三种分类数据都会被我们转换为[0,1,2],这三个数字在算法看来,是连续且可以计算的,这三个数字相互不等,有大小,并且有着可以相加相乘的联系。所以算法会把舱门,学历这样的分类特征,都误会成是体重这样的分类特征。这是说,我们把分类转换成数字的时候,忽略了数字中自带的数学性质,所以给算法传达了一些不准确的信息,而这会影响我们的建模。

类别OrdinalEncoder可以用来处理有序变量,但对于名义变量,我们只有使用哑变量的方式来处理,才能够尽量向算法传达最准确的信息。

image.png

这样的变化,让算法能够彻底领悟,原来三个取值是没有可计算性质的,是“有你就没有我”的不等概念。在我们的数据中,性别和舱门,都是这样的名义变量。因此我们需要使用独热编码,将两个特征都转换为哑变量。

# 可以将名义变量变成onehot编码
from sklearn.preprocessing import OneHotEncoder
X = data.iloc[:,1:-1]
enc = OneHotEncoder(categories='auto').fit(X)
result = enc.transform(X).toarray()
result

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

#依然可以直接一步到位,但为了给大家展示模型属性,所以还是写成了三步
OneHotEncoder(categories='auto').fit_transform(X).toarray()
  • 使用还原原始数据
#依然可以还原
pd.DataFrame(enc.inverse_transform(result))
  • 获取特征的名称
# 通过enc的get_feature_names接口可以获取特征的名称
enc.get_feature_names()
# array(['x0_0.0', 'x0_1.0', 'x1_0.0', 'x1_1.0', 'x1_2.0'], dtype=object)
  • 对数据进行处理后美化数据
#axis=1,表示跨行进行合并,也就是将量表左右相连,如果是axis=0,就是将量表上下相连(注意concat的使用)
newdata = pd.concat([data,pd.DataFrame(result)],axis=1)
newdata.head()

# 删除Sex和Embarked两个列,并赋予新列名
newdata.drop(["Sex","Embarked"],axis=1,inplace=True)
newdata.columns = ["Age","Survived","Female","Male","Embarked_C","Embarked_Q","Embarked_S"]
newdata.head()

image.png

image.png

数据类型以及常用的统计量

image.png

连续型特征:二值化与分段

preprocessing.Binarizer

根据阈值将数据二值化(将特征值设置为0或1),用于处理连续型变量。大于阈值的值映射为1,而小于或等于阈值的值映射为0。默认阈值为0时,特征中所有的正值都映射到1。二值化是对文本计数数据的常见操作,分析人员可以决定仅考虑某种现象的存在与否。它还可以用作考虑布尔随机变量的估计器的预处理步骤

# 将年龄二值化
data_2 = data.copy()
from sklearn.preprocessing import Binarizer
X = data_2.iloc[:,0].values.reshape(-1,1) #类为特征专用,所以不能使用一维数组
transformer = Binarizer(threshold=30).fit_transform(X)
transformer

"""
array([[0.],
       [1.],
       [0.],
       [1.],
       [1.],
       [0.],
       [1.],
       [0.],
       [0.],
       [0.],
       [0.],
       [1.]])
"""
preprocessing.KBinsDiscretizer

这是将连续型变量划分为分类变量的类,能够将连续型变量排序后按顺序分箱后编码。总共包含三个重要参数:

参数含义&输入
n_bins每个特征中分箱的个数,默认5,一次会被运用到所有导入的特征
encode编码的方式,默认“onehot” "onehot":做哑变量,之后返回一个稀疏矩阵,每一列是一个特征中的一个类别,含有该 类别的样本表示为1,不含的表示为0 “ordinal”:每个特征的每个箱都被编码为一个整数,返回每一列是一个特征,每个特征下含 有不同整数编码的箱的矩阵 "onehot-dense":做哑变量,之后返回一个密集数组。
strategy用来定义箱宽的方式,默认"quantile" "uniform":表示等宽分箱,即每个特征中的每个箱的最大值之间的差为 (特征.max() - 特征.min())/(n_bins) "quantile":表示等位分箱,即每个特征中的每个箱内的样本数量都相同 "kmeans":表示按聚类分箱,每个箱中的值到最近的一维k均值聚类的簇心得距离都相同
from sklearn.preprocessing import KBinsDiscretizer
X = data.iloc[:,0].values.reshape(-1,1) 
est = KBinsDiscretizer(n_bins=3, encode='ordinal', strategy='uniform')
est.fit_transform(X) #查看转换后分的箱:变成了一列中的三箱

"""
array([[0.],
       [1.],
       [0.],
       [1.],
       [1.],
       [1.],
       [2.],
       [0.],
       [1.],
       [0.],
       [0.],
       [2.],
       [0.],
       [1.]])
"""
  • 数据降维查看分段结果
# ravel进行降维
set(est.fit_transform(X).ravel())
# {0.0, 1.0, 2.0}
  • 分箱最终是onehot的形式展示
est = KBinsDiscretizer(n_bins=3, encode='onehot', strategy='uniform') #查看转换后分的箱:变成了哑变量
est.fit_transform(X).toarray()

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