- 章节列表
Chapter1 数据获取
Chapter2 单因子探索分析与可视化
Chapter3 多因子探索分析
Chapter4 预处理理论
第四章 预处理理论
特征工程
数据清洗
- 数据样本采集(抽样)
- 样本要具有代表性
- 样本比例要平衡以及样本不平衡时如何处理
- 考虑全量数据
- 异常值(空值)处理
空值是异常值,重复值有时也是一种异常值,四分位数上下1.5~3倍边界范围之外也有可能是异常值,或者是具体业务中不允许出现的值。
处理方式有:直接丢弃,异常值比较多时,可以考虑用一个新的值用来代替异常值,或者把这个属性是否是异常值这个判断结果来代替这个属性或者直接代替异常值。还可以考虑用集中值进行指代,这里的集中值可以是除异常值之外的均值、中位数或者众数。也可以用边界值来指代,比如在连续数据中,用四分位间距确定的上下边界来确定超过上下边界的数。连续型的值还能用插值的方法来处理。 - 例
df=pd.DataFrame({'A':['a0','a1','a1','a2','a3','a4'],'B':['b0','b1','b2','b2','b3',None],'C':[1,2,np.nan,3,4,5],'D':[0.1,10.2,11.4,8.9,9.1,12],'E':[10,19,32,25,8,np.nan],'F':['f0','f1','g2','f3','f4','f5']})
'''
df为
A B C D E F
0 a0 b0 1.0 0.1 10.0 f0
1 a1 b1 2.0 10.2 19.0 f1
2 a1 b2 NaN 11.4 32.0 g2
3 a2 b2 3.0 8.9 25.0 f3
4 a3 b3 4.0 9.1 8.0 f4
5 a4 NaN 5.0 12.0 NaN f5
'''
#D属性第一个数是0.1,其他几个数都在8~12之间。因此0.1可能是异常值,利用四分位数确定的上下界的方法来过滤
upper_q=df['D'].quantile(0.75)
lower_q=df['D'].quantile(0.25)
q_int=upper_q-lower_q
k=1.5
#假定在四分位数确定的上下界以外的数为异常值,则:数据必须大于 下四分位数-k*四分位间距,且小于上四分位数+k*四分位间距
df[df['D']>lower_q-k*q_int][df['D']<upper_q+k*q_int]
特征预处理
- 特征选择
剔除与标注不相关或者冗余的特征。
- 数据规约中的与过滤思想有关的表
- 特征选择中的包裹思想
实现算法:
- 特征选择中的嵌入思想
这里说的嵌入的主体是特征,被嵌入的实体是一个简单的模型,也就是说,根据一个简单的模型来分析特征的重要性。最常用的方式是用正则化的方式来做特征选择,如下图共有n个特征,通过回归模型对标注进行回归(线性回归,逻辑回归等)后得到一些W系数,再对系数进行正则化操作,如果发现哪些W较小则去掉。
- 三种思想在sklearn中的实现
利用sklearn中SelectKBest(过滤思想的类),RFE(包裹思想的类),SelectFromModel(嵌入思想常用的类)来进行特征选择,代码如下:
from sklearn.feature_selection import SelectKBest,RFE,SelectFromModel
skb=SelectKBest(k=2) #SelectKBest会对输入数据的特征进行打分,k=2表示保留两个分数最高的特征
skb.scores_
'''
输出
array([0.23008591, 1.41796183, 0.03364956])
可以看出特征A,B的得分靠前,因此保留A和B的特征
'''
skb.transform(X)
'''
输出
array([[ 0.71419346, 0.51843561],
[-1.52679653, -0.6889083 ],
[-1.14701688, 0.77519133],
[ 0.39123552, 1.37031157],
[-0.57143396, 0.13105099],
[ 0.11818108, -0.72551292],
[-0.72591547, 1.03552561],
[ 1.55745499, 0.01664493],
[ 0.31193864, -0.7048164 ],
[ 0.12465426, -0.09379559]])
'''
rfe=RFE(estimator=SVR(kernel="linear"),n_features_to_select=2,step=1) #RFE需要指定estimator,SVR是支持向量回归,n_features_to_select表示最终选取多少个特征,step=1表示每迭代以此减少一个特征
rfe.fit_transform(X,Y) #保留了B和C
sfm=SelectFromModel(estimator=DecisionTreeRegressor(),threshold=0.1) #threshold表示重要性因子,在低于门限时特征被去掉
sfm.fit_transform(X,Y)
特征变换
特征变换的常用方法:对指化、离散化、数据平滑、归一化(标准化)、数值化、正规化
- 指数化
经过指数化后,原来数据在大于0的范围内,数与数之间的差异很小,而通过指数化后数值的差异增大。虽然差距变大,但归一化差距减小。
- 对数化
对数化的作用是当横轴的变化很大时,纵轴能够保证数据的变化不大,这样把数据范围缩小到一个较小的范围内,就能够方便我们进行计算,生活中这样的例子有很多,比如声音强度的大小采用分贝来表示等。
- 离散化
将连续变量分成几段(bins),进行离散化的原因是:克服数据缺陷,某些算法要求(如朴素贝叶斯),非线数据映射。离散化的方式采用分箱操作。分箱前一定要进行排序
分箱的方法:等深分箱、等宽分箱。深度表示数的个数,宽度表示数的区间。
如,给定一组数据,[6 8 10 15 16 24 25 40 67].
采用等深分箱后:
分箱后,利用一个值来替代一个箱里的数,替代的方法有很多,比如可以用均值或者边界值来替代每个箱的数值。如:当采用边界值来代替每个箱的数值时,第二个箱子被映射成 15 15 24
等宽分箱指的是每个箱的数据的区间尽量一致。对数据为[6 8 10 15 16 24 25 40 67],如果要分成3个箱,则 每个箱子的宽度为(67-6)/3。python中pd.qcut为等深分箱;pd.cut为等宽分箱。
-
归一化
Min-Max : x ′ = x − x m i n x m a x − x m i n x'=\frac{x-x_{min}}{x_{max}-x_{min}} x′=xmax−xminx−xmin -
标准化(狭义)
这里的标准化是指将数据缩放到均值为0标准差1的尺度上。(又称Z-score) x ′ = x − x ‾ σ x'=\frac{x-\overline x}{\sigma} x′=σx−x -
数值化
- 定类数据之间没有大小关系
- 定序数据之间有大小关系但大小关系不好衡量
- 定距数据的属性间大小可以衡量,但是没有零点,无法乘除。
- 如果在定距数据的基础上有零点,可以乘除,那就是定比数据。
定类数据和定序数据无法进行距离间的比较和加减法的运算,定距数据虽然能够进行加减法的运算,但不能进行乘除法的运算。
定序数据的数值化可以考虑采用标签化的处理方式,编码后的数据不是直接相加减的,或者他们只需要可以保留相对大小的信息有时候就足够了。天然有去除量纲的作用。如果定序数据的相对大小的概念和标注并没有明显的相关关系,也可以采用One-Hot编码的形式。
定类数据利用One-Hot编码
from sklearn.preprocessing import LabelEncoder,OneHotEncoder
LabelEncoder().fit_transform(np.array(["Down","Up","Up","Down"]).reshape((-1,1)))
'''
输出标签:
array([0, 1, 1, 0], dtype=int64)
'''
label_encoder=LabelEncoder()
label_train_f=label_encoder.fit_transform(np.array(["Red","Yellow","Blue","Green"]))
one_hot_encoder=OneHotEncoder().fit(label_train_f.reshape((-1,1)))
one_hot_encoder.transform(label_encoder.transform(np.array(["Yellow","Blue","Green","Green","Red"])).reshape((-1,1))).toarray()
'''
array([[0., 0., 0., 1.],
[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.]])
'''
- 正规化
特征降维
PCA,奇异值分解等线性降维。
LDA降维:线性判别式分析。 (p.s. 这部分基本照抄了,回头看周志华机器学习仔细理解。。。)
(不是 隐含迪利克雷分布(Latent Dirichlet Allocation))
LDA的核心思想:投影变换后同一标注内距离尽可能小,不同标注间距离尽可能大。
将矩阵分为两个子矩阵,一个矩阵都为 0,另一个矩阵都为1.
不同标注间距离的衡量:
我们希望标注间的距离尽可能大,标注间的距离尽可能小。则有
数学描述:
作如下假设:
可得
LDA
特征衍生
通过数据的相关性分析等找出可能潜在的关系。视具体情况而定
预处理过程
代码
#sl:satisfaction_level---False:MinMaxScaler;True:StandardScaler
#le:last_evaluation---False:MinMaxScaler;True:StandardScaler
#npr:number_project---False:MinMaxScaler;True:StandardScaler
#amh:average_monthly_hours--False:MinMaxScaler;True:StandardScaler
#tsc:time_spend_company--False:MinMaxScaler;True:StandardScaler
#wa:Work_accident--False:MinMaxScaler;True:StandardScaler
#pl5:promotion_last_5years--False:MinMaxScaler;True:StandardScaler
#dp:department--False:LabelEncoding;True:OneHotEncoding
#slr:salary--False:LabelEncoding;True:OneHotEncoding
def hr_preprocessing(sl=False,le=False,npr=False,amh=False,tsc=False,wa=False,pl5=False,dp=False,slr=False,lower_d=False,ld_n=1):
df=pd.read_csv("./data/HR.csv")
#1、清洗数据
df=df.dropna(subset=["satisfaction_level","last_evaluation"])
df=df[df["satisfaction_level"]<=1][df["salary"]!="nme"]
#2、得到标注
label = df["left"]
df = df.drop("left", axis=1)
#3、特征选择(小样本不选择)
#4、特征处理
scaler_lst=[sl,le,npr,amh,tsc,wa,pl5]
column_lst=["satisfaction_level","last_evaluation","number_project",\
"average_monthly_hours","time_spend_company","Work_accident",\
"promotion_last_5years"]
for i in range(len(scaler_lst)):
if not scaler_lst[i]:
df[column_lst[i]]=\
MinMaxScaler().fit_transform(df[column_lst[i]].values.reshape(-1,1)).reshape(1,-1)[0]
else:
df[column_lst[i]]=\
StandardScaler().fit_transform(df[column_lst[i]].values.reshape(-1,1)).reshape(1,-1)[0]
scaler_lst=[slr,dp]
column_lst=["salary","department"]
for i in range(len(scaler_lst)):
if not scaler_lst[i]:
if column_lst[i]=="salary":
df[column_lst[i]]=[map_salary(s) for s in df["salary"].values]
else:
df[column_lst[i]]=LabelEncoder().fit_transform(df[column_lst[i]])
df[column_lst[i]]=MinMaxScaler().fit_transform(df[column_lst[i]].values.reshape(-1,1)).reshape(1,-1)[0]
else:
df=pd.get_dummies(df,columns=[column_lst[i]])
if lower_d:
return PCA(n_components=ld_n).fit_transform(df.values),label
return df,label
d=dict([("low",0),("medium",1),("high",2)])
def map_salary(s):
'''
:param s:
:return:
进行label_encoder时候,我们想把salary中的low映射成最小值,mid映射成中间值,high映射为最大值,
但是默认的在LabelEncoder()中会i按照字母的升序进行处理 ,因此需要进行重新映射,映射的规则为
d=dict([("low",0),("medium",1),("high",2)])
'''
return d.get(s,0)