最近最近做了个阿里天池的竞赛项目,有关二手车交易价格预测。一般的数据挖掘项目包括探索性数据分析、特征工程、模型选择、模型调优和模型融合。对数据挖掘过程不熟悉的同学可以参考【绝对干货】机器学习模型训练全流程!,比较详细介绍了机器学习模型构造的全流程。
一、探索性数据分析
探索性数据分析的目的,就是先对数据有个整体的把握,熟悉数据,为接下来的特征工程和 模型构造打下基础。探索性数据分析一般会从数据类型、变量的数值分布、缺失值情况和变量之间的相关关系来分析数据。首先,我们导入数据,数据是官方给定的,分为训练集和测试集两个部分。
#导入需要的包
import pandas as pd
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns
from operator import itemgetter
import datetime
%matplotlib inline
#导入数据
df_train_data=pd.read_csv('used_car_train_20200313.csv',sep=' ')
df_test_data=pd.read_csv('used_car_testB_20200421.csv',sep=' ')
print(df_train_data.shape)
print(df_test_data.shape)
训练集包含150000条数据,31个变量;测试集包含50000条数据,30个变量(缺少交易价格一列)。值得注意的是,探索性数据分析是为接下来的建模做准备的,所以我们在进行探索数据分析时,只能针对训练集,不然会产生数据窥探偏误。测试集的作用是测试我们基于训练集构建的模型的泛化能力,我们的模型要完全独立于测试集进行构造。这就好比我们比赛,你不能既当球员,又当裁判。
1. 训练集数据类型和缺失值分析
#数据备份
df_train=df_train_data.copy()
#查看总体数据
df_train.head(5)
#查看训练集的变量数据类型
df_train.info()
存在缺失值的列有:model、 bodyType、 fuelType、 gearbox。唯一的有个字符串类型的变量为notRepairedDamage。
#查看唯一的字符串类型变量的数值分布
df_train['notRepairedDamage'].value_counts()
训练集各变量的数值分布
#绘制所有变量的柱形图,查看数据
df_train.hist(bins=50,figsize=(20,15))
plt.cla() #清除axes
从上面的柱形图来看,训练数据种分布有点异常的变量有:creatDate、offerType、seller。下面我们具体看一下这些变量数值分布:
#查看训练集中offerType变量
df_train['offerType'].value_counts()
#查看训练集seller变量
df_train['seller'].value_counts()
这些变量的数值分布可以总结为:
- creatDate为汽车开始售卖时间,训练集中的数据大致在2016-03-09至2016-04-03之间(excel打开文件查看)。这个变量可以考虑减去汽车注册日期regDate,得到汽车的使用时长。汽车使用时长可能时影响汽车销售价格的一个重要变量。
- 训练集和测试集中,offerType变量的值全为0。在构建模型时,可以考虑去掉这个变量。
- .训练集中,seller变量的值只有一个值为1,其余的都为0;而测试集中,全为0,可以考虑去掉这个变量。
3. 相关性分析
我们把字符串类型的变量、以及一些无关的变量去掉,来进行相关性分析。
#获得需要的列名
numeric_columns=df_train.select_dtypes(exclude='object').columns
columns=[col for col in numeric_columns if col not in ['SaleID', 'name', 'regDate',
'regionCode','seller', 'offerType', 'creatDate']]
#根据列名提取数据
train_set=df_train[columns]
#计算各列于交易价格的相关性
corr_matrix=train_set.corr()
corr_matrix['price'].sort_values()
二、特征工程
特征工程主要涉及数据的处理、特征的构造和特征帅选。
- 特征构造
我们对照官方给的变量的定义,发现model、 brand、 bodyType、 fuelType等为取几个值的离散变量。当我们采用基于树模型(XGBoost、LightGBM)来解决回归问题时,针对每一棵树,需要做的是确定分裂的变量,以及分裂点。一般采用启发式方法,根据分裂后的收益(误差)是否下降,并选择下降最多的分裂点,来逐步构建一棵树。由于上述的几个变量的值,只是代表类别,并不代表实际的大小,根据值大小的分裂并不能代表实际的意义。所以我们准备把每一个类别切分出来,形成非0即1的二分变量。
我们首先查看,每个变量属性值的数量:
len(df_train['model'].unique()) #结果为249
len(df_train['brand'].unique()) #结果为40
len(df_train['bodyType'].unique()) #结果为9
len(df_train['fuelType'].unique()) #结果为8
这些属性值种,还包含着NAN的类别,由于我在编程的时候,一直识别NAN来作为判断条件,所以我首先将数据种的NAN填充为-1。
#将nan填充为-1
df_train=df_train.fillna(-1)
(1)处理model
#获得model所有属性值,除nan
model_list=[model for model in list(df_train['model'].unique()) if model != -1]
#将所有的属性值切分出来,用1和0表示是否属于该属性,并保留nan值
all_info={
}
for n in range(len(model_list)):
print(n)
info=[]
for i in range(len(df_train['model'])):
if df_train['model'][i]==model_list[n]:
info.append(1)
elif df_train['model'][i]==-1:
info.append(np.nan)
else:
info.append(0)
all_info['model'+str(model_list[n])]=info
#将切分出来的属性保存为csv文件
df_info=pd.DataFrame(all_info)
df_info.to_csv('model.csv',encoding='utf_8_sig')
(2)处理brand、 bodyType和fuelType
#获得除nan外的所有属性值列表
brand_list=[brand for brand in list(df_train['brand'].unique()) if brand != -1]
body_list=[bodyType for bodyType in list(df_train[