一、特征工程理论:

常见的特征工程包括:

一、导入数据

import warnings
warnings.filterwarnings('ignore')
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import missingno as msno
import scipy.stats as st

path = 'D:/data/car/'
Train_data = pd.read_csv(path + 'used_car_train.csv', sep=' ')    
# 文件以空字符分隔数据,若以','分隔数据,则sep=','或省略(源文件都将数据存到了一列里面)
Test_data = pd.read_csv(path + 'used_car_testA.csv', sep=' ')

二、删除异常值(仅适用于树模型的构造)

在task2中,我们仅仅是凭借观察删除掉了两个特征和一部分特征的异常值,在本次任务中,我们给出了一个比较通用的方法来处理异常值:利用箱线图来去除异常值。我将所有的代码都做了详细注释,希望能帮助到大家理解:

def outliers_proc(data, col_name, scale=3):
    """
    用于清洗异常值,默认用 box_plot(scale=3)进行清洗
    :param data: 接收 pandas 数据格式
    :param col_name: pandas 列名
    :param scale: 尺度
    :return:
    """

def box_plot_outliers(data_ser, box_scale):
        """
        利用箱线图去除异常值
        :param data_ser: 接收 pandas.Series 数据格式
        :param box_scale: 箱线图尺度,
        :return:
        """
        iqr = box_scale * (data_ser.quantile(0.75) - data_ser.quantile(0.25))   # quantile为四分位函数,其中quantile(0.75)是从小到大排四分之三处的数据大小
        val_low = data_ser.quantile(0.25) - iqr                                 # 设置需要丢弃数据的上下阈值
        val_up = data_ser.quantile(0.75) + iqr
        rule_low = (data_ser < val_low)                                         # 比val_low更小的值要被丢弃(False和True组成的数组)
        rule_up = (data_ser > val_up)                                           # 比val_up更大的值要被丢弃(False和True组成的数组)
        return (rule_low, rule_up), (val_low, val_up)

data_n = data.copy()                                                        # 原数据的临时副本
    data_series = data_n[col_name]                                              # 原数据的某列
    rule, value = box_plot_outliers(data_series, box_scale=scale)               # 用箱线图对该列数据进行数据清洗
    index = np.arange(data_series.shape[0])[rule[0] | rule[1]]                  # 存储需要丢弃的过小过大的值的下标index
    print("Delete number is: {}".format(len(index)))
    data_n = data_n.drop(index)                                                 # 丢弃过小和过大的值
    data_n.reset_index(drop=True, inplace=True)                                 # 数据清洗过后,对数据重新设置连续行索引。
    print("Now row number is: {}".format(data_n.shape[0]))                      # 清洗后该特征剩余的取值个数(原代码的column应该是笔误了)
    index_low = np.arange(data_series.shape[0])[rule[0]]                        # 因为取值过小被清洗掉的数据的index
    outliers = data_series.iloc[index_low]                                      # 因为取值过小被清洗掉的数据集合
    print("Description of data less than the lower bound is:")
    print(pd.Series(outliers).describe())                                       # 查看清洗掉的取值过小数据的各统计量
    index_up = np.arange(data_series.shape[0])[rule[1]]                         # 因为取值过大被清洗掉的数据的index
    outliers = data_series.iloc[index_up]                                       # 因为取值过大被清洗掉的数据集合
    print("Description of data larger than the upper bound is:")
    print(pd.Series(outliers).describe())                                       # 查看清洗掉的取值过大数据的各统计量

fig, ax = plt.subplots(1, 2, figsize=(10, 7))
    sns.boxplot(y=data[col_name], data=data, palette="Set1", ax=ax[0])          # 可视化数据清洗前的箱线图
    sns.boxplot(y=data_n[col_name], data=data_n, palette="Set1", ax=ax[1])      # 可视化数据清洗后的箱线图
    plt.show()
    return data_n

其中第二个print()语句中原文写的是Now column number is,我认为应该是笔者的笔误,此处应该是行数(即该特征的有效数据个数)。
其输出如下所示:

Delete number is: 963
Now row number is: 149037
Description of data less than the lower bound is:
count    0.0
mean     NaN
std      NaN
min      NaN
25%      NaN
50%      NaN
75%      NaN
max      NaN
Name: power, dtype: float64
Description of data larger than the upper bound is:
count      963.000000
mean       846.836968
std       1929.418081
min        376.000000
25%        400.000000
50%        436.000000
75%        514.000000
max      19312.000000
Name: power, dtype: float64

有结果我们可知共删除了过大和过小的值共963个,剩余的有效取值值个数为149037。
可视化的结果如下图所示:

其中左图显示的是删除异常值之前数据的箱线图,右图显示的是删除异常值之后数据的箱线图。经过对比可见,删除前的数据中位数,第一四等分点和第三四等分点都处于图像非常靠下的位置,因此存在非常多异常大的取值,对数据的整体分布产生了较大的影响;删除后的数据中位数,第一四等分点和第三四等分点相对全部的取值取值区间已有较大的区分度,整体分布得到了优化。

三、特征构造(所有模型通用)

本部分我们将应用我们的先验知识构造有助于预测price的新特征。

1.训练集与测试集的合并

# 训练集和测试集放在一起,方便构造特征
Train_data['train']=1
Test_data['train']=0
data = pd.concat([Train_data, Test_data], ignore_index=True)

注意:不熟悉pandas的同学可能会疑问这个ignore_index的作用,下面我们通过控制变量看一下他的作用,当该值取False时,训练集与测试集合并后的序号如下图所示:

当该值取True时,合并后的序号如下图所示:

经过对比可见当该值取False时,合并后的表格将序号依然使用的原表格中的序号,而当该值取True时,序号更新为连续的序号。

2.构造特征之使用时间

因为我们知道其售卖时间和出厂时间,所以两者相减可以计算出其使用时间。之所以要计算汽车使用时间,是因为一般来说价格与使用时间成反比。

data['used_time'] = (pd.to_datetime(data['creatDate'], format='%Y%m%d', errors='coerce') - pd.to_datetime(data['regDate'], format='%Y%m%d', errors='coerce')).dt.days

3.构造特征之所属城市

从邮编中可以提取城市特征(4位邮编的第一位,不足4位的邮编认为是异常的)。

data['city'] = data['regionCode'].apply(lambda x : str(x)[:-3])
data = data

4.构造特征之品牌

Train_gb = Train_data.groupby("brand")                                # 按照brand来聚合,brand=0的聚合在一起,brand=1的聚合在一起(总共聚合了40类)
all_info = {}
for kind, kind_data in Train_gb:                                      # kind分别从0取到39,kind_data为0-39这40类的具体数据
    info = {}
    kind_data = kind_data[kind_data['price'] > 0]
    info['brand_amount'] = len(kind_data)
    info['brand_price_max'] = kind_data.price.max()
    info['brand_price_median'] = kind_data.price.median()
    info['brand_price_min'] = kind_data.price.min()
    info['brand_price_sum'] = kind_data.price.sum()
    info['brand_price_std'] = kind_data.price.std()
    info['brand_price_average'] = round(kind_data.price.sum() / (len(kind_data) + 1), 2)
    all_info[kind] = info                                              # all_info存储了这40个聚合类的统计学特征
brand_fe = pd.DataFrame(all_info).T.reset_index().rename(columns={"index": "brand"})
data = data.merge(brand_fe, how='left', on='brand')

如上面代码所示,我们根据brand的值聚合为多种类(本题为40类),也就是一共40个品牌,我们分别计算每个品牌的各种数学统计量,并保存在字典all_info中,为了将新构造的特征合并到原来的data中,我们将其转化为和data相同格式的类型,然后将其合并。如下图所示:新的特征就被加入到了原来的data中了。

5.构造特征之数据分桶

本构造以power特征为例,数据分桶实际上就是数据离散化,其优点如下:
①.离散后稀疏向量内积乘法运算速度更快,计算结果也方便存储,容易扩展;
②. 离散后的特征对异常值更具鲁棒性。如 age>30 为 1 否则为 0,所以异常年龄100也不会对模型造成很大的干扰;
③. LR (逻辑斯蒂回归)属于广义线性模型,表达能力有限,经过离散化后,每个变量有单独的权重,这相当于引入了非线性,能够提升模型的表达能力,加大拟合;
④. 离散后特征可以进行特征交叉,提升表达能力,由 M+N 个变量编程 M*N 个变量,进一步引入非线形,提升了表达能力;
⑤. 特征离散后模型更稳定。
其具体实现如下所示:

bin = [i*10 for i in range(31)]                                        # bin 是一个整数数列,标明了我们的cut标准
data['power_bin'] = pd.cut(data['power'], bin, labels=False)           # 按照bin所给的数组切割,左开右闭,因此power等于0或者大于300的值都被认为是nan

以上是将power在(0,300]区间内的值都根据bin的值做一定切分处理(除以10)。其结果如下所示:

power_bin  power
0             5.0     60
1             NaN      0
2            16.0    163
3            19.0    193
4             6.0     68
...           ...    ...
199995       11.0    116
199996        7.0     75
199997       22.0    224
199998        NaN    334
199999        6.0     68

[200000 rows x 2 columns]

细心的同学可能发现了,power=60处理后是变成了5.0,而不是6.0。这是因为cut()函数默认的切割是左开右闭的。之所以这样切割是有好处的,因为power=0也是异常值,这样可以将0处理为Nan。

6.去除无用的特征,导出到文件

我们前面利用已有特征人为构造了一部分新的特征,所以原来特征已经变成了无用特征,我们需要将其删除,然后导出到文件作为树模型的数据使用。

data = data.drop(['creatDate', 'regDate', 'regionCode','seller','offerType'], axis=1)
data.to_csv(path+'data_for_tree.csv',index = 0)

其中seller和offerType为task2中我们寻找到的出现严重倾斜的特征,我们一并删除。

四、特征的异常值处理之二+归一化处理(供LR和NN等模型使用)

1.异常值处理

因为不同模型使用的数据特征是不同的,所以对于树模型和LR等模型的数据需要分开构造。我们看下之前用箱线图去除过异常值的特征power的数据分布。

data['power'].plot.hist()
plt.show()

其分布如下图所示:

可以看到我们虽然用箱型图去除掉了部分过大过小的异常值,但是仍然有许多过大的异常值,因此我们如果用归回模型的话,不应该使用箱线图去除异常值,而应该使用如下所示的长尾分布截断。

rule = Train_data['power'] > 375
index = np.arange(Train_data.shape[0])[rule]
Train_data_n = Train_data.drop(index)
Train_data_n['power'].plot.hist()

其可视化结果如下所示:

从上图可见,power特征的数值已经十分接近正态分布了。

2.归一化处理

因为data中的power特征有过多的异常大值,所以我们之间归一化势必会出现问题,因此我们将其取log缩小其差距,然后在归一化。

min_max_scaler = preprocessing.MinMaxScaler()
data['power'] = np.log(data['power'] +1)                                # +1是为了结果都是正数
data['power'] = ((data['power'] - np.min(data['power'])) / (np.max(data['power']) - np.min(data['power'])))
data['power'].plot.hist()
plt.show()

其中取对数的时候+1是为了结果均为正值,否则我们将无法在可视化图形中表示出来。
其他差异不大的特征不用取对数,直接归一化即可,不再赘述。

data['kilometer'] = ((data['kilometer'] - np.min(data['kilometer'])) /
                        (np.max(data['kilometer']) - np.min(data['kilometer'])))
def max_min(x):
    return (x - np.min(x)) / (np.max(x) - np.min(x))

data['brand_amount'] = ((data['brand_amount'] - np.min(data['brand_amount'])) / 
                        (np.max(data['brand_amount']) - np.min(data['brand_amount'])))
data['brand_price_average'] = ((data['brand_price_average'] - np.min(data['brand_price_average'])) / 
                               (np.max(data['brand_price_average']) - np.min(data['brand_price_average'])))
data['brand_price_max'] = ((data['brand_price_max'] - np.min(data['brand_price_max'])) / 
                           (np.max(data['brand_price_max']) - np.min(data['brand_price_max'])))
data['brand_price_median'] = ((data['brand_price_median'] - np.min(data['brand_price_median'])) /
                              (np.max(data['brand_price_median']) - np.min(data['brand_price_median'])))
data['brand_price_min'] = ((data['brand_price_min'] - np.min(data['brand_price_min'])) / 
                           (np.max(data['brand_price_min']) - np.min(data['brand_price_min'])))
data['brand_price_std'] = ((data['brand_price_std'] - np.min(data['brand_price_std'])) / 
                           (np.max(data['brand_price_std']) - np.min(data['brand_price_std'])))
data['brand_price_sum'] = ((data['brand_price_sum'] - np.min(data['brand_price_sum'])) / 
                           (np.max(data['brand_price_sum']) - np.min(data['brand_price_sum'])))

最后看一下特征的形状,确认无误后导出到文件中供LR模型使用。

print(data.shape)
data = pd.get_dummies(data, columns=['model', 'brand', 'bodyType', 'fuelType',
                                     'gearbox', 'notRepairedDamage', 'power_bin'])
print(data.shape)

(199037, 37)
(199037, 368)

get_dummies()函数是将编码方式转换为One-Hot方式,因此特征数量增加了许多是正常的结果。所以我们将其保存起来备用。

数据挖掘-二手车价格预测 Task03:特征工程相关推荐

  1. 二手车价格预测task03:特征工程

    二手车价格预测task03:特征工程 1.学习了operator模块operator.itemgetter()函数 2.学习了箱线图 3.了解了特征工程的方法 (内容介绍) 4.敲代码学习,加注解 以 ...

  2. 【直播】王茂霖:二手车交易价格预测-千变万化特征工程(河北高校数据挖掘邀请赛)

    二手车交易价格预测-千变万化特征工程 目前 河北高校数据挖掘邀请赛 正在如火如荼的进行中.为了大家更好的参赛,王茂霖分享了 从0梳理1场数据挖掘赛事!,完整梳理了从环境准备.数据读取.数据分析.特征工 ...

  3. 【算法竞赛学习】二手车交易价格预测-Task3特征工程

    二手车交易价格预测-Task3 特征工程 三. 特征工程目标 Tip:此部分为零基础入门数据挖掘的 Task3 特征工程部分,带你来了解各种特征工程以及分析方法,欢迎大家后续多多交流. 赛题:零基础入 ...

  4. 数据挖掘-二手车价格预测 Task04:建模调参

    数据挖掘-二手车价格预测 Task04:建模调参 模型调参部分 利用xgb进行五折交叉验证查看模型的参数效果 ## xgb-Model xgr = xgb.XGBRegressor(n_estimat ...

  5. 数据挖掘二手车价格预测 Task05:模型融合

    模型融合是kaggle等比赛中经常使用到的一个利器,它通常可以在各种不同的机器学习任务中使结果获得提升.顾名思义,模型融合就是综合考虑不同模型的情况,并将它们的结果融合到一起.模型融合主要通过几部分来 ...

  6. 二手车交易价格预测:特征工程

    前言 文章数据基于天池零基础入门数据挖掘 - 二手车交易价格预测的比赛:https://tianchi.aliyun.com/competition/entrance/231784/informati ...

  7. 二手车交易价格预测——Task3特征工程

    二手车交易价格预测--特征工程 一.目标 二.异常值处理 (一)删除异常值 三.特征构造 (一)构造时间特征 (二)构造地区特征 四.数据清洗 (一)数据分桶 (二)删除冗余数据 (三)处理数据集中的 ...

  8. 数据挖掘-二手车价格预测 Task02:数据分析

    Task02:数据分析 1.EDA数据探索性分析 读取文件 import warnings warnings.filterwarnings('ignore') import pandas as pd ...

  9. DNN二手车价格预测完整代码

    前言 最近在学习深度学习,就用DNN试着跑了个天池赛二手车价格预测,特征还是用之前集成模型跑的特征,通过不断调试模型的学习率.隐藏层数量.神经元数量.优化器.激活函数.迭代次数.batchsize,K ...

最新文章

  1. 在Ubuntu下构建Bullet以及执行Bullet的样例程序
  2. GPT-3:人工智能的新突破
  3. 线程同步synchronized
  4. 添加three20模板的方法
  5. 李飞飞:在物体识别之后,计算机视觉还要多久才能理解这个世界?
  6. win98 老电脑 文件导出_UGNX利器:轻量化建模,很多人还傻傻的关注电脑配置
  7. Percona Toolkit工具箱的安装与使用--完成复杂的mysql操作。
  8. vbs中对excel的常用操作
  9. 最大最小距离聚类算法c语言,聚类算法-最大最小距离算法(实例+代码)
  10. 面对面的办公室——纪念艾伦•图灵百年诞辰
  11. 常见的物联网通信技术有哪些,是怎么分类的?
  12. matlab版本下载地址
  13. word一键生成ppt 分页_一键实现Word转PPT的三种方法评测
  14. 北京电影学院及中央戏剧学院老师推荐的必看影片,我真是一条一条翻的,一条一条写的,是真的。...
  15. Obsidian看板指北
  16. 手把手从0开始学会Python爬虫,从大一初学者视角,带你实现爬虫攥写
  17. 记一些Linux/centos的基础运维命令
  18. Android 项目必备(二十九)-->App 在线升级与更新
  19. 《对不队团队》第一次作业:团队亮相
  20. Mac符号对应键盘位置简解

热门文章

  1. ASP.NET 运行时详解 揭开请求过程神秘面纱
  2. C++程序设计实践题1
  3. 再学 GDI+[20]: TGPTextureBrush 与 TWrapMode
  4. Python多线程编程方式1(转)
  5. 转:FileReader详解与实例---读取并显示图像文件
  6. 湖南卫视明年不办选秀 段林希或是最后一个冠军
  7. Xlib: connection to :0.0 refused by server解决方法
  8. python下载数据集出现:Compressed file ended before the end-of-stream marker was reached
  9. Storm计算结果是怎样存放的
  10. Python for Informatics 第11章 正则表达式五(译)