二手车交易价格预测代码全解析(三)特征工程与缺失值处理

路漫漫其修远兮,吾将上下而求索。

缺失值处理思路

先回顾一下第二节的内容。第二节我们讲到特征构造,不但分析了特征之间的相关性、删除了没有用的特征,还构造了一些新的特征。比如used_time(使用时长)、brand_and_price_mean(品牌与价格)等。

我们在构造新的特征的时候,就将新特征中的缺失值用fillna()函数补充上了。但也要注意到,我们原有的特征列还存在很多缺失值,需要进行处理。这里的思路是:

(1)缺失的数量很少:直接用中位数或者平均值代替。
(2)缺失的数量很多:用机器学习模型预测出缺失值。

另外,关于缺失值,我们也要考虑如下问题:
(1)训练集和测试集里面可能都有缺失值,所以我们都要填补
(2)要用已有数值预测缺失值,毕竟不能用NaN预测NaN,对吧

在这一步,我们首先删除一些和预测不相关的列:

data_all=data_all.drop(['SaleID','regDate','creatDate','type'],axis=1)

SaleID是交易ID,regDatecreatDate的新特征used_time我们已经构造好了;type是我们在代码开头定义的区分train训练集和test测试集的标签,对预测肯定也没用,所以先去掉这些列。

接下来,让我们检查一下都有哪些列有缺失值、缺失了多少:

print(data_all.isnull().sum())   #检查每一列有多少缺失值

打印出来的结果如下:
在这里插入图片描述

在这里插入图片描述
好家伙,从这个结果我们可以看出,model列只有一个缺失值,而bodyType,fuelType,gearbox,notRepairedDamage,price,used_time这几列缺失的值都特别多,还是上万级别的。

极少量缺失值的填充

所以对于缺失值最少的model列,我们直接拿中位数填充一下:

data_all['model']=data_all['model'].fillna(data_all['model'].median())

而对于bodyType等缺失值特别多的几个属性,就需要我们拿机器学习模型来预测补充缺失值了,相当于对于每个含有缺失值的列,单独建模预测其缺失,比如要建立bodyType预测模型、fuelType预测模型…

大量缺失值:用机器学习模型预测填充

首先,我们先预处理一下构建bodyType预测模型所需的数据(train训练集和test测试集),把含有大量缺失值的列删掉。因为NaN不能用来预测NaN,另外训练集里面也不能用NaN训练。预处理代码如下:

# 处理bodyType
X_bT=data_all.loc[(data_all['bodyType'].notnull()),:]   
#先找出所有bodyType行,按True和False(空和不空)分类
X_bT=X_bT.drop(['bodyType','fuelType','gearbox','notRepairedDamage','price','used_time'],axis=1)#XbT删去了空值较多的列
ybT_train=data_all.loc[data_all['bodyType'].notnull()==True,'bodyType']   #只选择bodyType不为空的列
XbT_test=data_all.loc[data_all['bodyType'].isnull()==True,:]
XbT_test=XbT_test.drop(['bodyType','fuelType','gearbox','notRepairedDamage','price','used_time'],axis=1)

上述代码中:

notnull()==True的写法是为了遴选出不为空的行;isnull()==True是为了遴选出为空的行。原因第二节我们也提到过,是因为notnull()isnull()函数返回的都是一个由TrueFalse构成的矩阵嘛。

X_bT:它是我们用来训练bodyType预测模型的训练集。即使用其它几个无缺失值的特征来预测bodyType列的值。所以我们在第二行代码中删除了含有大量缺失值、同样亟待预测的特征。

ybT_train:它当然就是我们要预测的目标值啦!当然,预测值肯定必须是一个实数而不能是NaN,所以这里我们调用notnull()函数,只取出原bodyType这一列中不含缺失值的行。

XbT_test:这个很好理解,就是我们自己处理的测试集。这里把bodyType里面为空的行数据全都放到XbT_test里面,并用drop()去掉其他几个缺失待预测列['bodyType','fuelType','gearbox','notRepairedDamage','price','used_time'],只留下必要的用于预测bodyType的特征。

同理,fuelTypegearbox等其他几个特征的数据预处理也是完全如法炮制,代码如下:

# 处理fuelType
X_fT=data_all.loc[(data_all['fuelType'].notnull()),:]
X_fT=X_fT.drop(['bodyType','fuelType','gearbox','notRepairedDamage','price','used_time'],axis=1)
yfT_train=data_all.loc[data_all['fuelType'].notnull()==True,'fuelType']
XfT_test=data_all.loc[data_all['fuelType'].isnull()==True,:]
XfT_test=XfT_test.drop(['bodyType','fuelType','gearbox','notRepairedDamage','price','used_time'],axis=1)

#处理gearbox
X_gb=data_all.loc[(data_all['gearbox'].notnull()),:]
X_gb=X_gb.drop(['bodyType','fuelType','gearbox','notRepairedDamage','price','used_time'],axis=1)
ygb_train=data_all.loc[data_all['gearbox'].notnull()==True,'gearbox']
Xgb_test=data_all.loc[data_all['gearbox'].isnull()==True,:]
Xgb_test=Xgb_test.drop(['bodyType','fuelType','gearbox','notRepairedDamage','price','used_time'],axis=1)

#处理notRepairedDamage
X_nRD=data_all.loc[(data_all['notRepairedDamage'].notnull()),:]
X_nRD=X_nRD.drop(['bodyType','fuelType','gearbox','notRepairedDamage','price','used_time'],axis=1)
ynRD_train=pd.DataFrame(data_all.loc[data_all['notRepairedDamage'].notnull()==True,'notRepairedDamage']).astype('float64')
XnRD_test=data_all.loc[data_all['notRepairedDamage'].isnull()==True,:]
XnRD_test=XnRD_test.drop(['bodyType','fuelType','gearbox','notRepairedDamage','price','used_time'],axis=1)

有意思的是,used_time时间特征的数据处理与上面几个特征略有不同:

#处理used_time
scaler=preprocessing.StandardScaler()
uesed_time=scaler.fit(np.array(data_all['used_time']).reshape(-1, 1))
data_all['used_time']=scaler.fit_transform(np.array(data_all['used_time']).reshape(-1, 1),uesed_time)

X_ut=data_all.loc[(data_all['used_time'].notnull()),:]
X_ut=X_ut.drop(['bodyType','fuelType','gearbox','notRepairedDamage','price','used_time'],axis=1)
yut_train=data_all.loc[data_all['used_time'].notnull()==True,'used_time']
Xut_test=data_all.loc[data_all['used_time'].isnull()==True,:]
Xut_test=Xut_test.drop(['bodyType','fuelType','gearbox','notRepairedDamage','price','used_time'],axis=1)

上述代码中,前三行简而言之就是对used_time这个时间类型的数据做了个标准化、归一化操作。方便后续的计算。

另外代码中使用了numpy的reshape()函数,reshape第一个参数为-1,表示行数不确定(因为列数是固定的,所以具体行数让计算机自己计算),第二个参数为1,表示新矩阵只有一列。因此这里就是把归一化后的时间数据转为一列。

这里用到了scaler.fit()scaler.fit_transform(),它们都是sklearn库封装好的统计学工具,具体解释如下:

fit(): 简单来说,就是求得训练集的均值、方差、最大值、最小值等训练集固有的属性。

transform(): 在fit的基础上,进行标准化,降维,归一化等操作(看具体用的是哪个工具,如PCA,StandardScaler等)。

fit_transform():fit_transform是fit和transform的组合,它会对数据先拟合 fit,找到数据的整体指标,如均值、方差、最大值最小值等,然后对数据集进行转换transform,从而实现数据的标准化、归一化操作。

transform()fit_transform()二者的功能都是对数据进行某种统一处理(比如标准化~N(0,1),将数据缩放(映射)到某个固定区间,归一化,正则化等)。

用Xgboost填充缺失值

处理好了数据,现在当然要开始搭建我们的机器学习预测模型了!
我们先来定义一个预测函数,这里的xgb.XGBRegressorxgboost库导入过来的,顾名思义,就是将xgb算法应用到回归问题上。可以直接加参数使用,是不是很方便?
具体预测代码如下:

def RFmodel(X_train,y_train,X_test):
    model_xgb= xgb.XGBRegressor(max_depth=4, colsample_btree=0.1, learning_rate=0.1, n_estimators=32, min_child_weight=2)#模型定义
    model_xgb.fit(X_train,y_train)#拟合
    y_pre=model_xgb.predict(X_test)#预测
    return y_pre

函数里的第一行代码,意思是声明了一个XGBRegressor模型对象。第二行则是将我们处理好的训练数据输入到模型中做拟合。

这样一来,函数返回值y_pre就是我们用来填充缺失值的目标了!

预测出各特征缺失值

定义好我们的预测函数,就可以实际预测一下缺失值了。这里输入的数据就是我们刚才针对各个特征,处理好的数据。再将预测值赋值给矩阵里原来缺失值的位置,就完成了缺失值的补充。

y_pred=RFmodel(X_bT,ybT_train,XbT_test)
data_all.loc[data_all['bodyType'].isnull(),'bodyType']=y_pred

y_pred0=RFmodel(X_fT,yfT_train,XfT_test)
data_all.loc[data_all['fuelType'].isnull(),'fuelType']=y_pred0

y_pred1=RFmodel(X_gb,ygb_train,Xgb_test)
data_all.loc[data_all['gearbox'].isnull(),'gearbox']=y_pred1

y_pred2=RFmodel(X_nRD,ynRD_train,XnRD_test)
data_all.loc[data_all['notRepairedDamage'].isnull(),'notRepairedDamage']=y_pred2

y_pred3=RFmodel(X_ut,yut_train,Xut_test)
data_all.loc[data_all['used_time'].isnull(),'used_time']=y_pred3

特征工程部分到这里就结束了,对于我们大部分初学者来说,这一部分的代码实现对pandas和sklearn等库的掌握程度要求很高,所以常常会出现一段代码看好久看不懂、或者不理解背后的运算原理的情况。

优秀的特征工程对于模型的性能有很好的辅助作用,虽然在现在的深度学习中都更关注模型结构,但或许特征工程方面的优化,在深度学习上也大有可为呢?

猜你喜欢

转载自blog.csdn.net/zoubaihan/article/details/115342851
今日推荐