一.概述
1. 数据预处理
数据预处理是从数据中检测,修改或删除不准确或不适用于模型的记录的过程
可能面对的问题有:数据类型不同,比如有的是文字,有的是数字,有的含时间序列,有的连续,有的间断。 也可能,数据的质量不行,有噪声,有异常,有缺失,数据出错,量纲不一,有重复,数据是偏态,数据量太大或太小 。
数据预处理的目的:让数据适应模型,匹配模型的需求 。
2.sklearn中的数据预处理
sklearn中包含众多数据预处理相关的模块,包括:
模块preprocessing:几乎包含数据预处理的所有内容
模块Impute:填补缺失值专用
二 . 数据预处理
1.归一化
在机器学习算法实践中,我们往往有着将不同规格的数据转换到同一规格,或不同分布的数据转换到某个特定分布 的需求,这种需求统称为将数据“无量纲化”。
譬如梯度和矩阵为核心的算法中,譬如逻辑回归,支持向量机,神经 网络,无量纲化可以加快求解速度;而在距离类模型,譬如K近邻,K-Means聚类中,无量纲化可以帮我们提升模型精度,避免某一个取值范围特别大的特征对距离计算造成影响。
(1)preprocessing.MinMaxScaler
当数据(x)按照最小值中心化后,再按极差(最大值 - 最小值)缩放,数据移动了最小值个单位,并且会被收敛到 [0,1]之间,而这个过程,就叫做数据归一化(Normalization,又称Min-Max Scaling)。
归一化之后的数据服从正态分 布,公式如下:
\[x^{*} = \frac{x-min(x)}{max(x)-min(x)}
\]
在sklearn当中,我们使用preprocessing.MinMaxScaler来实现这个功能。
MinMaxScaler有一个重要参数, feature_range,控制我们希望把数据压缩到的范围,默认是[0,1].
from sklearn.preprocessing import MinMaxScaler
data = [[-1, 2], [-0.5, 6], [0, 10], [1, 18]]
#实现归一化
scaler = MinMaxScaler(feature_range=[5,10]) #实例化,feature_range默认是[0,1]
# scaler = scaler.fit(data) #fit,在这里本质是生成min(x)和max(x)
# result = scaler.transform(data) #通过接口导出结果 result
result_ = scaler.fit_transform(data) #训练和导出结果一步达成,可以替代上两行数据
result_
array([[ 5. , 5. ],
[ 6.25, 6.25],
[ 7.5 , 7.5 ],
[10. , 10. ]])
(2) preprocessing.StandardScale
当数据(x)按均值(μ)中心化后,再按标准差(σ)缩放,数据就会服从为均值为0,方差为1的正态分布(即标准正态分 布),而这个过程,就叫做数据标准化(Standardization,又称Z-score normalization),公式如下:
\[x^{*} = \frac{x-μ}{σ}
\]
from sklearn.preprocessing import StandardScaler
data = [[-1, 2], [-0.5, 6], [0, 10], [1, 18]]
scaler = StandardScaler() #实例化
# scaler = scaler.fit(data) #fit,本质是生成均值和方差
# result = scaler.transform(data) #通过接口导出结果
result_ = scaler.fit_transform(data) #使用fit_transform(data)一步达成结果
result_
array([[-1.18321596, -1.18321596],
[-0.50709255, -0.50709255],
[ 0.16903085, 0.16903085],
[ 1.52127766, 1.52127766]])
(3) StandardScaler和MinMaxScaler选哪个?
看情况。大多数机器学习算法中,会选择StandardScaler来进行特征缩放,因为MinMaxScaler对异常值非常敏 感。在PCA,聚类,逻辑回归,支持向量机,神经网络这些算法中,StandardScaler往往是最好的选择。
MinMaxScaler在不涉及距离度量、梯度、协方差计算以及数据需要被压缩到特定区间时使用广泛,比如数字图像 处理中量化像素强度时,都会使用MinMaxScaler将数据压缩于[0,1]区间之中。
2.缺失值
数据预处理中非常重要的一项就是处理缺失值。
对于连续值,常常使用均值或者中位数去填充缺失值;
对于离散值,常常使用众数填充,或者把缺失值当作一个单独的类别。
对于缺失率超过一定阈值的特征,可以考虑直接舍弃。
import pandas as pd
data = pd.read_csv(r"./../Narrativedata.csv",index_col=0)
data.info()
Int64Index: 891 entries, 0 to 890
Data columns (total 4 columns):
Age 714 non-null float64
Sex 891 non-null object
Embarked 889 non-null object
Survived 891 non-null object
dtypes: float64(1), object(3)
memory usage: 34.8+ KB
以Titanic数据为例,可以看到其中Age和Embarked两列有缺失值,且分别为连续值和离散值。
(1) impute.SimpleImputer
from sklearn.impute import SimpleImputer
# 对连续值Age进行填充
Age = data.loc[:,"Age"].values.reshape(-1,1) #sklearn当中特征矩阵必须是二维
# 实例化
age_median = SimpleImputer(strategy="median") #用中位数填补 (默认均值填补)
age_0 = SimpleImputer(strategy="constant",fill_value=0) #使用具体值填补,用fill_value指定
# 转化
age_median = age_median.fit_transform(Age) #fit_transform一步完成调取结果
age_0 = age_0.fit_transform(Age)
#把填补后的列赋值回原来的矩阵里
data.loc[:,"Age"] = age_median
data.info()
Int64Index: 891 entries, 0 to 890
Data columns (total 4 columns):
Age 891 non-null float64
Sex 891 non-null object
Embarked 889 non-null object
Survived 891 non-null object
dtypes: float64(1), object(3)
memory usage: 34.8+ KB
# 对离散值Embarked进行填充
Embarked = data.loc[:,"Embarked"].values.reshape(-1,1)
Embarked_most = SimpleImputer(strategy = "most_frequent") #使用众数填补
data.loc[:,"Embarked"] = Embarked_most.fit_transform(Embarked)
data.info()
Int64Index: 891 entries, 0 to 890
Data columns (total 4 columns):
Age 891 non-null float64
Sex 891 non-null object
Embarked 891 non-null object
Survived 891 non-null object
dtypes: float64(1), object(3)
memory usage: 34.8+ KB
(2) 用Pandas和Numpy进行填补
pd.fillna() 使用特定的值填充
pd.dropna(axis=0)删除所有有缺失值的行/列
import pandas as pd
data1 = pd.read_csv(r"./../Narrativedata.csv",index_col=0)
data1.loc[:,"Age"] = data1.loc[:,"Age"].fillna(data1.loc[:,"Age"].median()) #.fillna 在data1Frame里面直接进行填补
data1.dropna(axis=0,inplace=True) #.dropna(axis=0)删除所有有缺失值的行,.dropna(axis=1)删除所有有缺失值的列
data1.info()
Int64Index: 889 entries, 0 to 890
Data columns (total 4 columns):
Age 889 non-null float64
Sex 889 non-null object
Embarked 889 non-null object
Survived 889 non-null object
dtypes: float64(1), object(3)
memory usage: 34.7+ KB
3.处理离散特征:编码和哑变量
在sklearn当中,除了专用来处理文字的算法,其他算法在fit的时候全部要求输入数组或矩阵,也不能够导 入文字型数据。
为了让数据适 应算法和库,我们必须将数据进行编码,即是说,将文字型数据转换为数值型。
(1) preprocessing.LabelEncoder:标签专用,能够将分类转换为分类数值
from sklearn.preprocessing import LabelEncoder
y = data.iloc[:,-1] #要输入的是标签,不是特征矩阵,所以允许一维
print(y[:10])
le = LabelEncoder() #实例化
label = le.fit_transform(y) # 匹配加转化一步到位
# le = le.fit(y)
# label = le.transform(y)
print(label[:10])
print(le.classes_) # 查看有多少类
data.iloc[:,-1] =label
0 No
1 Yes
2 Yes
3 Yes
4 No
5 No
6 No
7 No
8 Yes
9 Yes
Name: Survived, dtype: object
[0 2 2 2 0 0 0 0 2 2]
['No' 'Unknown' 'Yes']
(2)preprocessing.OrdinalEncoder:特征专用,能够将分类特征转换为分类数 (一般用于有序变量)
data_ =data.copy()
# 观察特征的原样式
data_.iloc[:,1:-1][:10]
Sex
Embarked
0
male
S
1
female
C
2
female
S
3
female
S
4
male
S
5
male
Q
6
male
S
7
male
S
8
female
S
9
female
C
from sklearn.preprocessing import OrdinalEncoder
# 类似的,把实例化,匹配,转化和赋值写在一条语句上
data_.iloc[:,1:-1] = OrdinalEncoder().fit_transform(data_.iloc[:,1:-1])
#接口categories_对应LabelEncoder的接口classes_,一模一样的功能
print(OrdinalEncoder().fit(data_.iloc[:,1:-1]).categories_)
#查看数据,
data_.head()
[array([0., 1.]), array([0., 1., 2.])]
Age
Sex
Embarked
Survived
0
22.0
1.0
2.0
0
1
38.0
0.0
0.0
2
2
26.0
0.0
2.0
2
3
35.0
0.0
2.0
2
4
35.0
1.0
2.0
0
data_.info()
Int64Index: 891 entries, 0 to 890
Data columns (total 4 columns):
Age 891 non-null float64
Sex 891 non-null float64
Embarked 891 non-null float64
Survived 891 non-null int32
dtypes: float64(3), int32(1)
memory usage: 31.3 KB
由以上的数据情况可以看出,Age和Embarked的缺失值已被填充,特征Sex和Embarked等分类特征已转化为分类数值,标签Survived也已转化为分类数值。
(3) preprocessing.OneHotEncoder:独热编码,创建哑变量
由上面的处理可以看到OrdinalEncoder会把类别变量变成数值,比如转化为[0,1,2],这三个数字在算法看来,是连续且可以计算的,可能有大小并且有着可以相加相乘的联系。但是有的类别变量各类别是独立的,如果转化成数值会给算法传达了一些不准确的信息,而这会影响我们的建模。
因此,类别OrdinalEncoder可以用来处理有序变量,但对于名义变量,我们只有使用独热编码,以哑变量的方式来处理,才能够尽量 向算法传达最准确的信息。
# 同样的,观察下原数据样式
data.iloc[:,1:-1][:10]
Sex
Embarked
0
male
S
1
female
C
2
female
S
3
female
S
4
male
S
5
male
Q
6
male
S
7
male
S
8
female
S
9
female
C
from sklearn.preprocessing import OneHotEncoder
X = data.iloc[:,1:-1]
#可以直接一步到位
result = OneHotEncoder(categories='auto').fit_transform(X).toarray()
# # 也可以分开成两步计算
# enc = OneHotEncoder(categories='auto').fit(X)
# result = enc.transform(X).toarray()
# 把独热编码拼接到原数据上
newdata = pd.concat([data,pd.DataFrame(result)],axis=1)
newdata.drop(["Sex","Embarked"],axis=1,inplace=True)
newdata.columns = ["Age","Survived","Female","Male","Embarked_C","Embarked_Q","Embarked_S"]
newdata.head()
Age
Survived
Female
Male
Embarked_C
Embarked_Q
Embarked_S
0
22.0
0
0.0
1.0
0.0
0.0
1.0
1
38.0
2
1.0
0.0
1.0
0.0
0.0
2
26.0
2
1.0
0.0
0.0
0.0
1.0
3
35.0
2
1.0
0.0
0.0
0.0
1.0
4
35.0
0
0.0
1.0
0.0
0.0
1.0
(4) 使用pandas的.get_dummies函数 实现独热编码
dummies_Sex = pd.get_dummies(data.Sex,prefix='Sex') #ptefix 是前缀,因子化之后的字段名为 前缀_类名
dummies_Embarked = pd.get_dummies(data.Embarked,prefix='Embarked')
newdata = pd.concat([data, dummies_Sex, dummies_Embarked], axis=1) #将哑编码的内容拼接到data后
newdata.drop([ 'Sex', 'Embarked'], axis=1, inplace=True) # 把编码前的字段删除
newdata.head()
Age
Survived
Sex_female
Sex_male
Embarked_C
Embarked_Q
Embarked_S
0
22.0
0
0
1
0
0
1
1
38.0
2
1
0
1
0
0
2
26.0
2
1
0
0
0
1
3
35.0
2
1
0
0
0
1
4
35.0
0
0
1
0
0
1
4.处理连续特征:二值化和分段
(1) sklearn.preprocessing.Binarize 用于处理连续变量的二值化
根据阈值将数据二值化(将特征值设置为0或1),用于处理连续型变量。大于阈值的值映射为1,而小于或等于阈值的值映射为0。默认阈值为0。
# 将年龄二值化
data_2 = data.copy()
from sklearn.preprocessing import Binarizer
X = data_2.iloc[:,0].values.reshape(-1,1) #类为特征专用,所以不能使用一维数组
Age = Binarizer(threshold=30).fit_transform(X) # 以30为阈值,>30为1,<30为0
Age[:10]
array([[0.],
[1.],
[0.],
[1.],
[1.],
[0.],
[1.],
[0.],
[0.],
[0.]])
(2) sklearn.preprocessing.KBinsDiscretizer 用于给连续变量分箱
这是将连续型变量划分为分类变量的类,能够将连续型变量排序后按顺序分箱后编码。总共包含三个重要参数:
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)[:5]
array([[0.],
[1.],
[0.],
[1.],
[1.]])
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.]])