二手车交易价格预测代码全解析(二)数据分析与特征工程

面对失败,不彷徨;面对抉择,不犹豫;面对挑战,不惧怕。

查看缺失值和重复值

在这里说一下哪些算缺失值。比如某一列明明该有一个数,但是却压根就没有数。这会导致程序运行的时候报错,提示无法转换NaN(Not a Number)。

天池给的这数据集里缺失值特别多,刚开始跑程序的时候到处都报错NaN。所以查看一下缺失值情况是必要的,下面代码用来查看缺失值和重复值

missing=data_all.isnull().sum()
missing=missing[missing>0]
print(missing)
print(data_all['bodyType'].value_counts())
print(data_all['fuelType'].value_counts())
print(data_all['gearbox'].value_counts())

解释一下这里的isnull().sum()和value_counts()。
isnull():该函数会返回一个布尔类型(即TrueFalse)的矩阵,表示了原矩阵对应位置是否为NaN缺失值。

isnull().sum():sum()会在isnull()返回的布尔矩阵的基础上做累加。我们都知道在语言底层的实现中,True=1False=0
因此isnull().sum()输出的是矩阵的每一列有多少个缺失值(因为只要有一个缺失值就为True。假设某一列有Q个缺失值,那sum输出的这一列的数值就是Q*1=Q)。它直接告诉了我们每列缺失值的数量。

value_counts():该函数用于查看表格某列中有多少个不同的值,并计算每个不同的值在该列中有多少个重复值。返回的内容相当于一个列表。

数值特征的数据分析

数值特征,指的是该特征的表现形式必须是具体的数值大小,与分类特征相对应(即分类特征的数值表现的是类别,比如0表示优质,1表示劣质)。除此之外,特征工程还有时间特征、文本特征等。

下面代码中,num_features是我们选出的数值特征,包括功率(power)、价格(price)以及一些匿名数值特征。对应地,categorical_features则是分类特征,包括名称(name)、商标(brand)等。

这段代码的作用是打印出每一列的不同值以及唯一值的数量等信息,实现的是对原数据集的观察,这就节省了我们自己打开Excel一行行看的时间,方便我们后续操作:

num_features=[ 'kilometer','power','price', 'v_0',
       'v_1', 'v_2', 'v_3', 'v_4', 'v_5', 'v_6', 'v_7', 'v_8', 'v_9', 'v_10',
       'v_11', 'v_12', 'v_13', 'v_14']

for num in num_features:
    print('{}特征有{}个不同值'.format(num,data_all[num].nunique()))
    temp=data_all[num].value_counts()
    print(temp)
    print(pd.DataFrame(data_all[num]).describe())

上述代码中,describe()函数计算 数据列的各种数据统计量,在这里可以理解为我们简要观察一下矩阵的基本内容。关于describe()的详细参数可以参考:

https://blog.csdn.net/m0_45210226/article/details/108942526

扫描二维码关注公众号,回复: 16564341 查看本文章

另外,这部分代码中用到一个新的函数nunique()

在英语中,unique是“单独”、“唯一”的意思。因此这里的nunique就是n个unique,即“有n个唯一的值”。

补充一点pandas的知识吧。在pandas中有unique()nunique()两个方法:
(1)unique()是以数组形式(numpy.ndarray)返回所选列的所有唯一值(特征的所有唯一值)。
(2)nunique()返回的是唯一值的个数。

分类特征的数据分析

讲完了数值特征,当然接着就是分类特征啦!

这里的categorical_features是分类特征,包括名称(name)、商标(brand)等。

这段代码和上面数值特征部分的代码差不多,作用是要观察一下分类特征数据的基本情况:

categorical_features=['model','name', 'brand', 'notRepairedDamage','bodyType', 'fuelType', 'gearbox','regionCode']
for cat in categorical_features:
    print('{}特征有{}个不同值'.format(cat,data_all[cat].nunique()))
    temp=data_all[cat].value_counts()
    print(temp)
    print(pd.DataFrame(data_all[cat]).describe())

清理异常数值

天池给的数据集实在是太坑,里面不但有NaN,还有“-”这种特殊字符。好端端的数值型矩阵,多了“-”之后就成了字符串型矩阵,这得多闹心啊,数据都分析不了。

我和Q同学一起运行代码的时候在这上面头秃了好久。期间还用Excel暴力清理“-”符号,但Excel的替换效率实在是太慢Orz

这里我们可以先输入一段代码,查看一下数据类型:

print(data_all.info())

输出的结果如下:
在这里插入图片描述
大家看到notRepairedDamage这一列数据有多坑了吧!别人都是float或者int数值类型只有它是object对象类型!

机器学习模型运算的时候是以数值型矩阵运算的,所以object对象类型是解析不了的,要把它换掉。

这里我们的办法是,先把“-”全都替换成NaN,后续再按填充NaN的办法全都补上数值就好了。

因此清理异常数值‘-’的代码如下:

data_all['notRepairedDamage'].replace('-', np.nan, inplace=True)

replace():把对象中的第一个参数替换为第二个参数,在这里是把’-'替换为NaN。这个函数有一个参数inplace,英语的意思是“就地更改”。如果设为false,就是replace的时候不更改原来的对象,而是返回一个修改后的新对象。如果设为True,就直接在原对象上修改了,所以我们要设为True。

剔除数据不明显的特征

根据天池Datawhale上的教程,sellerofferType这两列的类别特征严重倾斜,一般不会对预测有什么帮助,所以完全可以删掉这两列。

drop()方法可以对DataFrame对象删除指定行和列,方法是在第一个参数里罗列出要删掉的列名。另外,axis=1时删除列,axis=0时删除行。
具体的数据删除代码如下:

#删掉没用的两列
data_all=data_all.drop(['seller','offerType'],axis=1)

for cat in categorical_features:
    data_all[(data_all['type']=='train')][cat].value_counts().plot(kind='box')
    plt.show()

for num in num_features:
   sns.distplot(data_train[num],kde=False,fit=stats.norm)
   plt.show()

这部分代码里,写了两个用matplotlib画图的循环,其作用是针对数值特征或者分类特征的每一列作箱线图,观察数据分布。

sns.distplot()集合了matplotlib的hist()sns.kdeplot()功能,作用是绘制直方图 + 核密度曲线,这里咱们了解它的应用就行。
详细内容可以查看:

https://blog.csdn.net/pythonxiaopeng/article/details/109642444

贴两张画出来的图像:
在这里插入图片描述
上面这张是brand商标的数据分布图看起来差距还蛮大的哈。下面这张是我们刚才数据清理过的notRepairedDamage,看起来还挺友好是不是。
在这里插入图片描述

查看特征分布情况

这一部分就是调用sns.kdeplot查看一下我们主要特征的分布情况。
sns.kdeplot()函数用来画图比较两个变量的关系,是否符合线性回归。一般用来比较特征变量和标签变量上。他有个专业名词叫核密度估计(Kernel density estimaton)。

所谓sns,其实是我们import seaborn as sns的结果。seaborn是一个类似于matplotlib的Python图形库,也是用来画图。它其实是在matplotlib的基础上进行了更高级的API封装,从而使得作图更加容易。
绘制出特征分布的代码如下:

feature=[ 'name',  'model', 'brand', 'bodyType', 'fuelType',
       'gearbox', 'power', 'kilometer', 'notRepairedDamage', 'regionCode',
        'v_0', 'v_1', 'v_2', 'v_3', 'v_4', 'v_5', 'v_6','v_7', 'v_8', 'v_9',
        'v_10', 'v_11', 'v_12', 'v_13', 'v_14']
for i in feature:
    g=sns.kdeplot(data=data_all[i][(data_all['type']=='train')],color='Red',shade=True)
    g = sns.kdeplot(data=data_all[i][(data_all['type'] == 'test')],ax=g, color='Blue', shade=True)
    g.set_xlabel(i)
    g.set_ylabel("Frequency")  #纵轴即特征变量,具体数值与核密度估计数学实现有关
    g = g.legend(["train", "test"])
    plt.show()

这里展示一下我画出来的结果,其实就是绘制出训练集和测试集数据的基本分布,看起来一目了然。
在这里插入图片描述
上面这张是name(名称)的特征分布,下面这张是brand(商标)的特征分布。这个图像作为辅助观察,大概看一看就好了。
在这里插入图片描述

分析特征的相关性,并筛除相关性低的

这一部分代码对应特征筛选,用来计算相关性。
corr()函数用于检查两个变量之间变化趋势的方向以及程度,值范围-1到+1。0表示两个变量不相关,正值表示正相关,负值表示负相关,值越大相关性越强。

method参数可选pearson、spearman、kendall,pointbiserialr等相关系数,这里用的是斯皮尔曼相关性系数。如果要优化,至于具体选哪个,这是数学角度要考虑的啦!我们就先做应用。
相关性计算代码如下:

feature=['price','name',  'model', 'brand', 'bodyType', 'fuelType',
       'gearbox', 'power', 'kilometer', 'notRepairedDamage', 'regionCode',
        'v_0', 'v_1', 'v_2', 'v_3', 'v_4', 'v_5', 'v_6','v_7', 'v_8', 'v_9',
        'v_10', 'v_11', 'v_12', 'v_13', 'v_14']
corr=data_all[feature].corr(method='spearman')
corr=pd.DataFrame(corr)   #用pandas的DataFrame封装一下相关性矩阵
#这是因为seaborn库是基于pandas的,所以必须用pandas的数据类型嘛

sns.heatmap(corr,fmt='0.2f')  #fmt就是数据格式format,代表输出的数据类型
plt.show()
# 删掉相关性比较低的特征
data_all=data_all.drop(['regionCode'],axis=1)

这里程序使用sns绘制了一张heatmap(热度图)。
至于什么是heatmap,我把画出来的结果放上大家就明白了:
在这里插入图片描述
可以看到一目了然,regionCode(地区编码)和其它因子的相关度都不够高(所有相关性几乎为0),所以本段代码中,我们用drop把这一列删掉

查看时间特征

时间特征也是一个很重要的特征,因为我们后续要用两个时间特征的变量相减,计算出汽车的使用寿命。这里我们调用pd.to_datetime(),该函数能够将Str和Unicode转化为时间格式,因为原数据集里面的时间全都是字符串,比如20210330,而不是我们想要的2021-03-30。pd.to_datetime()能帮我们实现这种从字符到日期的自动转换!

具体日期转换代码如下:

data_all['regDate']= pd.to_datetime(data_all['regDate'], format='%Y%m%d', errors='coerce')
data_all['creatDate']=pd.to_datetime(data_all['creatDate'], format='%Y%m%d', errors='coerce')
print(data_all['regDate'].isnull().sum()) 
# 在regDate里,有一些数据的格式出错,如20070009,可是我们没有0月
#所以我们需要 errors='coerce'。errors遇到错误会将该值设置为NaN,方便我们后续补全空值
print(data_all['creatDate'].isnull().sum())  #查看有多少空行

接下来到了比较关键的一步了,这也是特征工程数据处理中比较典型的一种做法:

data_all['used_time']=(data_all['creatDate']-data_all['regDate']).dt.days
data_all=data_all.drop(['SaleID','regDate','creatDate','type'],axis=1)#删掉不需要的列
# 使用时间:data['creatDate'] - data['regDate'],反应汽车使用时间,一般来说价格与使用时间成反比
# 不过要注意,数据里有时间出错的格式,

这里我们新构造了一个used_time列,顾名思义,它代表汽车的使用时长,它相当于提炼了原creatDate(上线时间)和regDate(注册时间)的信息。

查看品牌和价格特征

这一部分内容本代码比天池Datawhale上面写的要简洁,针对brand和model这两列做聚类然后求平均。解释一下代码的意思:

groupby()函数会将在指定列中值相同的元素合并为一个组,因此它返回的结果是一个按组别分配的矩阵。代码相当于对同一商标(brand)的汽车的价格求平均,并对同一车型(model)的汽车的价格也求平均和中位数。

mean()函数用来求平均值,默认按竖列求平均,如果想对行求平均,则用mean(1)

median()求的是中位数,用法与mean()类似。

brand_and_price_mean=data_all.groupby('brand')['price'].mean()
model_and_price_mean=data_all.groupby('model')['price'].mean()
brand_and_price_median=data_all.groupby('brand')['price'].median()
model_and_price_median=data_all.groupby('model')['price'].median()
data_all['brand_and_price_mean']=data_all.loc[:,'brand'].map(brand_and_price_mean)
data_all['model_and_price_mean']=data_all.loc[:,'model'].map(model_and_price_mean).fillna(model_and_price_mean.mean())
data_all['brand_and_price_median']=data_all.loc[:,'brand'].map(brand_and_price_mean)
data_all['model_and_price_median']=data_all.loc[:,'model'].map(model_and_price_mean).fillna(model_and_price_median.mean())

后四行代码使用了pandas一个比较巧妙的函数map(),其作用是将调用对象里面的值,根据map里面的键值对(键值对,即key:value形式,类似于字典)重新赋值(专业一点叫映射)。关于map的基本应用,可以借用一张图来表现:
在这里插入图片描述
可以看到,map里面实际上相当于提供了一个字典,调用者根据自己内部的数据按图索骥,就可以得到对应的新值。

因此不难理解,这段代码中,实际上是将相同汽车的brand_and_price_meanmodel_and_price_meanbrand_and_price_medianmodel_and_price_median这几个新增的属性列都按照汽车的品牌价格赋为同一个值。

我个人觉得这更像是对特征做了一次整合,构造一些新的特征。这也符合特征工程之特征构造的几个构造方向:

  • 构造统计量特征,报告计数、求和、比例、标准差等;
  • 时间特征,包括相对时间和绝对时间,节假日,双休日等;
  • 地理信息,包括分箱,分布编码等方法;
  • 非线性变换,包括 log/ 平方/ 根号等;
  • 特征组合,特征交叉;
  • 仁者见仁,智者见智,根据实际情况构造。

猜你喜欢

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