美文网首页
特征工程详解及实战项目(2)

特征工程详解及实战项目(2)

作者: 茶小美 | 来源:发表于2020-04-23 12:15 被阅读0次

本文是在做项目过程中对特征工程部分的整理,特征工程更依赖经验知识,所以对于我这种入门小白来说需要一步步探索,本文写的浅显易懂,欢迎拍砖~!

特征工程入门

传送门:特征工程入门

特征工程项目

目录

EDA EDA数据探索分析入门及实战项目(1)

基于EDA探索分析部分我们了解训练集的大致情况:

  • 训练集train有15w行,31列;测试集test有5w行,30列(没有预测值price);
  • model车型编码、bodytype车身类型、fueltype燃油类型、gearbox变速箱均有缺失值,所有字段都是数值型,只有notRepairedDamage是object类型。
  • notRepairedDamage缺失较严重,offerType、seller两个数据严重倾斜,对预测无帮助,power也有异常值
  • 预测值是长尾分布,离群较严重
  • 数字特征numeric_feature = ['power','kilometer', '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']
  • 分类特征categorical_features = ['name','model','brand','bodyType','fuelType','gearbox','notRepairedDamage','regionCode']
  • 数字特征与预测值相关性、数字特征本身分布、数字特征之间的相关性
  • 分类特征的箱型图、小提琴图、柱形图(均值)、频数可视化

1. 数据浏览

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns
import os
import warnings
warnings.filterwarnings('ignore')
os.chdir('/Users/xy/Desktop/专业知识/阿里天池/天池-二手车交易价格预测')
train = pd.read_csv('used_car_train_20200313.csv',sep = ' ')
test = pd.read_csv('used_car_testA_20200313.csv',sep = ' ')
plt.rcParams['font.sans-serif'] = ['SimHei']#避免中文乱码
plt.rcParams['axes.unicode_minus'] = False#避免中文乱码
pd.set_option('display.max_columns', None)#展示全部列
train.head().append(train.tail())

2. 数据预处理&3. 特征构造

2.1 将数据分为日期特征,类别特征,数值特征
categories_features = ['name', 'model', 'brand', 'bodyType', 'fuelType', 'gearbox', 'notRepairedDamage', 'regionCode', 'seller', 'offerType']
numerical_features = ['power', 'kilometer'] + ['v_{}'.format(i) for i in range(15)]
date_features = ['regDate', 'creatDate']
2.2 单一数值特征处理
2.21 power
  • 统计量
sta(train,'power')
sta(test,'power')
train.png
test.png
  • 分布
plt.figure(1),plt.title('train中power分布')
sns.distplot(train['power'],kde=False)
plt.figure(2),plt.title('test中power分布')
sns.distplot(test['power'],kde=False)
image.png

可见train和test中power分布一致
方法1:可以利用箱型图剔除异常值
方法2:已知功率[0,600],按照600截断
方法3:按照2500截断
方法4:符合长尾分布,先取log再归一化

#方法1:可以利用箱型图剔除异常值
def outliers_proc(data,col_name,scale):
    def box_plot_outliers(data_ser,box_scale):
        iqr = box_scale*(data_ser.quantile(0.75)-data_ser.quantile(0.25))
        val_up = data_ser.quantile(0.75)+iqr
        val_low = data_ser.quantile(0.25)-iqr
        rule_up = data_ser>val_up
        rule_low = data_ser<val_low
        return (rule_up,rule_low),(val_up,val_low)
    data_n = data.copy()
    data_ser = data_n[col_name]
    rule,val = box_plot_outliers(data_ser,box_scale=scale)
    index = np.arange(data_ser.shape[0])[rule[0] | rule[1]]
    print('删除列数:',len(index))
    data_n = data_n.drop(index)
    data_n.reset_index(drop=True, inplace=True)
    print('现在列数:',len(data_n))
    index_up = np.arange(data_ser.shape[0])[rule[0]]
    outliers = data_ser.iloc[index_up]
    print('上异常值描述统计量是:')
    print(pd.Series(outliers).describe())
    index_low = np.arange(data_ser.shape[0])[rule[1]]
    outliers = data_ser.iloc[index_low]
    print('下异常值描述统计量是:')
    print(pd.Series(outliers).describe())
    plt.figure(1),plt.title('train的power异常值处理前箱线图')
    sns.boxplot(data[col_name],data=data,orient='v')
    plt.figure(2),plt.title('train的power异常值处理后箱线图')
    sns.boxplot(data_n[col_name],data=data,orient='v')
image.png
image.png

但是剔除异常值后仍是长尾分布

#方法2:已知功率[0,600],按照600截断
train_600 = train
train_600.loc[train_600['power']>600,'power'] = 600
train_600['power'].hist()
columns = ['power','price']
sns.pairplot(data=train_600[columns], kind='scatter',diag_kind='kde')
image.png
image.png

price与power无明显规律。
按照600截断350以后可以忽略,所以可以试一下按照350截断。

#方法3:按照350截断
train_350.loc[train_350['power']>350,'power'] = 350
image.png
image.png

按照350截断,随着power增加price最大值也逐渐增大。***这里power=0是需要处理的点。

#方法4:符合长尾分布,先取log再归一化
train_log = train
train_log['power'] = np.log(train_log['power']+1)
train_log['power'] = (train_log['power']-np.min(train_log['power']))/(np.max(train_log['power'])-np.min(train_log['power']))
train_log['power'].hist()
columns = ['power','price']
sns.pairplot(data=train_log[columns], kind='scatter',diag_kind='kde')
image.png
image.png
***得到的数值比较符合正态分布,与price的关系也符合正态分布。
同理,test与train的power运行结果相似
2.22 kilometer
  • 统计量&分布
sta(train,'kilometer')
sta(test,'kilometer')
print(train['kilometer'].value_counts())
print(test['kilometer'].value_counts())
plt.figure(1),plt.title('train的kilometer直方图')
train['kilometer'].hist()
plt.figure(2),plt.title('test的kilometer直方图')
test['kilometer'].hist()
image.png
image.png

train与test的kilometer分布相似,
可见kilometer已经分过桶,且kilometer越大销量越多,与常识相符毕竟开的时间越长卖车的人越多

  • 与price相关性
columns = ['kilometer','price']
sns.pairplot(data=train[columns], kind='scatter',diag_kind='kde')
image.png

***kilometer与price无分布规律,所以kilometer不需要做太多处理

2.3 日期特征处理
2.31 regDate
  • 统计量
print(train['regDate'].value_counts())
print(test['regDate'].value_counts())
sta(train,'regDate')
sta(test,'regDate')
image.png

发现train和test的注册日期中存在异常数据,需要处理。统计量均相似

  • 分布
plt.figure(1),plt.title('train的regDate直方图')
train['regDate'].hist()
plt.figure(2),plt.title('test的regDate直方图')
test['regDate'].hist()
image.png

train和test的注册日期分布规律相同

#与price相关性
columns = ['regDate','price']
sns.pairplot(data=train[columns], kind='scatter',diag_kind='kde')
image.png

***模糊的看出注册日期与price成正相关性,但是由于存在异常值原因,不明显,可以处理后再看下这个结论是否正确。

  • 日期处理
#处理成年月日
from tqdm import tqdm

date_cols = ['regDate'] #, 'creatDate']

def date_proc(x):
    m = int(x[4:6])
    if m == 0:
        m = 1
    return x[:4] + '-' + str(m) + '-' + x[6:]


for f in tqdm(date_cols):
    train[f] = pd.to_datetime(train[f].astype('str').apply(date_proc))
    train[f + '_year'] = train[f].dt.year
    train[f + '_month'] = train[f].dt.month
    train[f + '_day'] = train[f].dt.day
    train[f + '_dayofweek'] = train[f].dt.dayofweek
    
plt.figure()
plt.figure(figsize=(16, 6))
i = 1
for f in date_cols:
    for col in ['year', 'month', 'day', 'dayofweek']:
        plt.subplot(2, 4, i)
        i += 1
        v = train[f + '_' + col].value_counts()
        fig = sns.barplot(x=v.index, y=v.values)
        for item in fig.get_xticklabels():
            item.set_rotation(90)
        plt.title(f + '_' + col)
plt.tight_layout()
plt.show()
image.png
columns = ['regDate_year','price']
sns.pairplot(data=train[columns], kind='scatter',diag_kind='kde')
image.png
columns = ['regDate_month','price']
sns.pairplot(data=train[columns], kind='scatter',diag_kind='kde')

image.png
columns = ['regDate_day','price']
sns.pairplot(data=train[columns], kind='scatter',diag_kind='kde')

image.png

columns = ['regDate_dayofweek','price']
sns.pairplot(data=train[columns], kind='scatter',diag_kind='kde')

image.png

其中regdate只有1-12日
***由上可知,车辆注册年份成正态分布,与price有些正相关性;注册月份1月较多,与price无分布规律;注册天数和周几与price无分布规律,自身也没有规律可言。

2.32 creatDate
  • 统计量
sta(train,'creatDate')
sta(test,'creatDate')
  • 分布
plt.figure(1),plt.title('train的creatDate直方图')
train['creatDate'].hist()
plt.figure(2),plt.title('test的creatDate直方图')
test['creatDate'].hist()
image.png

train与test的creatDate分布相似

  • 处理年月日
date_cols = ['creatDate'] #, 'creatDate']

def date_proc(x):
    m = int(x[4:6])
    if m == 0:
        m = 1
    return x[:4] + '-' + str(m) + '-' + x[6:]


for f in tqdm(date_cols):
    train[f] = pd.to_datetime(train[f].astype('str').apply(date_proc))
    train[f + '_year'] = train[f].dt.year
    train[f + '_month'] = train[f].dt.month
    train[f + '_day'] = train[f].dt.day
    train[f + '_dayofweek'] = train[f].dt.dayofweek
    
plt.figure()
plt.figure(figsize=(16, 6))
i = 1
for f in date_cols:
    for col in ['year', 'month', 'day', 'dayofweek']:
        plt.subplot(2, 4, i)
        i += 1
        v = train[f + '_' + col].value_counts()
        fig = sns.barplot(x=v.index, y=v.values)
        for item in fig.get_xticklabels():
            item.set_rotation(90)
        plt.title(f + '_' + col)
plt.tight_layout()
plt.show()
image.png
columns = ['creatDate_year','price']
sns.pairplot(data=train[columns], kind='scatter')
image.png
columns = ['creatDate_month','price']
sns.pairplot(data=train[columns], kind='scatter')
image.png
columns = ['creatDate_day','price']
sns.pairplot(data=train[columns], kind='scatter',diag_kind='kde')
image.png
columns = ['creatDate_dayofweek','price']
sns.pairplot(data=train[columns], kind='scatter',diag_kind='kde')
image.png

***可见2016年销量远高于2015年,且16年price明显偏高;上线月份主要在3、4月且与price高度相关,因此构建上线年份和月份很有意义;而上线天数和周几与price没有什么相关性,但是周5至周日的销量比其他时间略高些。

2.33 汽车使用年限

将creatDate和regDate合起来得到汽车使用年限

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

train['used_time_month'] = round(train['used_time_day']/30)
train['used_time_year'] = round(train['used_time_day']/365)
train['used_time_year'].hist()
columns = ['used_time_year','price']
sns.pairplot(data=train[columns], kind='scatter',diag_kind='kde')
image.png
image.png
train['used_time_month'].hist()
columns = ['used_time_month','price']
sns.pairplot(data=train[columns], kind='scatter',diag_kind='kde')
image.png
image.png
train['used_time_day'].hist()
columns = ['used_time_day','price']
sns.pairplot(data=train[columns], kind='scatter',diag_kind='kde')
image.png
image.png

***由上可知,使用年限与price成反比。而且这三个字段应该相关性较高,在后续特征选择时可以只保留一个。

2.3 分类特征处理
2.31 name
  • 统计量
print(train['name'].value_counts())
print(test['name'].value_counts())
sta(train,'name')
sta(test,'name')
  • 分布
plt.figure(1),plt.title('train的name直方图')
train['name'].hist()
plt.figure(2),plt.title('test的name直方图')
test['name'].hist()
columns = ['name','price']
sns.pairplot(data=train[columns], kind='scatter',diag_kind='kde')
image.png

***train与test的name统计量相似,与price没有分布规律,不用处理扔进模型如果有效果就留下。

2.32 model

****构造新特征统计量,评价与price之间的相关性,保留相关性较高的新特征。

# 构造新特征销售统计量
# 构造新特征销售统计量
def new_features(data,col_name):
    data_gb = data.groupby(col_name)
    all_info={}
    for kind,kind_data in data_gb:
        info = {}
        kind_data = kind_data[kind_data['price']>0]
        info[col_name+'_amount'] = len(kind_data)
        info[col_name+'_price_max'] = kind_data.price.max()
        info[col_name+'_price_min'] = kind_data.price.min()
        info[col_name+'_price_median'] = kind_data.price.median()
        info[col_name+'_price_sum'] = kind_data.price.sum()
        info[col_name+'_price_std'] = kind_data.price.std()
        info[col_name+'_price_average'] = round((kind_data.price.sum()/len(kind_data)+1),2)
        all_info[kind] = info
    brand_fe = pd.DataFrame(all_info).T.reset_index().rename(columns={"index": col_name})
    data = data.merge(brand_fe, how='left', on=col_name)
    return data
  • 统计量
print(train['model'].value_counts())
print(test['model'].value_counts())
sta(train,'model')
sta(test,'model')
  • 分布
plt.figure(1),plt.title('train的model直方图')
train['model'].hist()
plt.figure(2),plt.title('test的model直方图')
test['model'].hist()
columns = ['model','price']
sns.pairplot(data=train[columns], kind='scatter',diag_kind='kde')
image.png
image.png
image.png

根据常识,车型编码model对于价格来说十分重要,必须保留。train中有一个缺失值,可以用众数填充,树模型不用处理。

2.33 brand

方法与model相同,这里不展示代码了,只展示结果截图。
为了方便后续分析,这里构建一个分类特征的处理函数:

def sta_category(data,col_name,predict_col='price'):
    print(data[col_name].value_counts())
    print(sta(data,col_name))
    plt.figure(1),plt.title(col_name+'直方图')
    data[col_name].hist()
    if 'price' in data.columns:
        columns = [col_name,'price']
        sns.pairplot(data=train[columns], kind='scatter',diag_kind='kde')
    else:
        None
        
train.png
test.png

***汽车品牌毫无疑问也是重要特征;直接可以考虑将车型和品牌结合起来处理。
train和test的分布也一致。一共有40个品牌,也不存在缺失值,所以暂时不做处理,在之后的特征交互阶段在处理。
由相关性可视化可以看出,brand有个很明显的规律,就是中间部分的brand 12-20的样子,price明显偏低。之后看情况可以尝试聚类一下。

2.34 bodyType
image.png
image.png

***bodyType缺失值需要处理,因为要用他创建新特征,选择向上填充法

由分析得知,编码可能按照销量顺序,因此缺失值按照向下填充
train['bodyType'].fillna(method='ffill',inplace=True)
train['bodyType'].isnull().sum()

***train与test的bodyType分布相似,其中1、2、3、7价格偏低
model、brand、bodyType都有点按照销量编码的意思

2.35 fuelType
train[train['fuelType'].isnull()]
image.png
image.png

***train和test中的fuelType分不相同,fuelType中2之后数据量较少而且对应的price较低,因此可以合并为一类。

2.36 gearbox
image.png

gearbox与price无相关性,且trainhetest的gearbox分布相似,无需处理直接放进模型中。

2.37 notRepairedDamage
image.png

***根据常识,无损坏价格高于有损坏,但图中表示无损坏反倒价格较低,推测是已知信息0与1的意义给反了

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

***缺失值直接填补为空值,树模型会处理。

2.38 regionCode

根据常识,不同地区二手车价位略微不同。
regionCode1-4位的情况。


image.png
image.png

***可见邮编也有种根据销量编号的意思,price与邮编无分布规律。
继续,可以根据邮编拿到省份信息。(德国邮政编码由5个数字组成,其中前两个数字代表省份或州别,后三个数字代表城市地区。)

  • 多变量构建特征
    下面看下city和brand与价格之间的关系
#不同地区的品牌不同价格有所差异
brand_fe =  train.groupby(['city','brand'])['price'].median()
df = pd.DataFrame(brand_fe).rename(columns={'price':'citybrand_price'})
train = train.merge(df, how='left', on=['city','brand'])

***但是不知道怎么分析,哭...等我查查资料

2.39 seller和offerType

seller和offerType只是0和1的信息,对于预测值无价值,删除。

2.40 匿名特征
v_features = ['price']+['v_{}'.format(i) for i in range(15)]
sns.pairplot(train[v_features])
image.png
  • 找异常值
    ***从图中其实是可以很明显的看出一些异常值的。
    比如,v_14中那突出的一个点;v_5里面那个孤立的一个点。
    将这些点找出来,我们可以直接删掉。
    注意:删之前,是需要先去test集中验证一下,是否也存在类似的异常,如果不存在,果断删除;如果存在类似的分布,那就不能删除。
  • 找关系
    这些图中其实也可以看出两个变量是否相关,比如v1和v6
  • 找规律
    主要找聚类模式。
    一是看数据是否被分成明显的几组数据;
    二是看各组的数据量是否足够。
    比如v5和v14,虽然数据很明显被分成两组,但是左边那个明显数据量过少,很难说聚类后能带来多大的帮助。
    一个好的例子是v12和v0/v8和v2,界限清晰,数据量较为平衡

3. 特征构造

二手车估值模型有:
根据使用年限折旧:

  1. 将二手车分为10年计算,前三年每年折旧15%,中间四年折旧10%,最后三年折旧5%。
  2. 15%残值不动,前三年每年11%,后四年10%,最后三年9%
    根据行驶公里数折旧:
    假定一辆车有效寿命为30万公里,将其分为5段,每段6万公里,每段价值依序为新车价的5/15、4/15、3/15、2/15、1/15。
    6万公里以内 每公里折旧1/9
    6-12万公里 每公里折旧1/15
    12-18万公里 每公里折旧1/30
    18-24万公里 每公里折旧1/(15*15)
    24-30万公里 每公里折旧
# 使用年限折旧
def depreciation_year1(year):
    if year <= 3:
        return 1 - year * 0.15
    elif year > 3 and  year <= 7:
        return 0.55 - (year-3) * 0.1
    elif year > 7 and  year <= 10:
        return 0.25 - (year-7) * 0.05
    else:
        return 0

train['depreciation_year1'] = train['used_time_year'].apply(lambda x: depreciation_year1(x))

def depreciation_year2(year):
    if year <= 3:
        return 1 - 0.85 * year * 0.11
    elif year > 3 and  year <= 7:
        return 0.7195 - 0.85 * (year-3) * 0.1
    elif year > 7 and  year <= 10:
        return 0.3795 - 0.85 * (year-7) * 0.09
    else:
        return 0.15
    
train['depreciation_year2'] = train['used_time_year'].apply(lambda x: depreciation_year2(x))
image.png
image.png
#行驶里程
def depreciation_kilometer(kilo):
    if kilo <= 6:
        return 1 - kilo * 5 / 90
    elif kilo > 6 and  kilo <= 12:
        return 0.66667 - (kilo-6) * 4 / 90
    elif kilo > 12 and  kilo <= 18:
        return 0.4 - (kilo-12) * 3 / 90
    elif kilo > 18 and  kilo <= 24:
        return 0.2 - (kilo-18) * 2 / 90
    elif kilo > 24 and  kilo <= 30:
        return 0.06667 - (kilo-24) * 1 / 90
train['depreciation_kilo'] = train['used_time_year'].apply(lambda x: depreciation_year1(x))

4. 特征选择

思路:自变量和目标变量之间的关联。
相关性分析: 连续变量 VS 连续变量
卡方检验: 分类变量 VS 分类变量
单因素方差分析: 分类变量 VS 连续变量

需要注意的一点是:
不同的模型,对于相关分析结果的处理是不一样的;
LR模型的话,应当把自变量之间相关性过高的特征,做一个筛选排除;
树模型的话,可以保留;

pd.set_option('display.max_rows', None)#展示全部列
corrs[corrs>0.5]

depreciation_year1,price-0.658
depreciation_year2,price-0.652
depreciation_kilo,price-0.658
model_price_average,price-0.58
model_price_median,price-0.56
regDate_year,0.61
v0,price-0.628
v8,price-0.685
v12,price-0.69
v10,name-0.57
v0,v5-0.72
v0,v8-0.51
v0,regDate_year-0.52
v0,model_price_average-0.54
v1,v6-0.99
v2,v7-0.97
v2,v11-0.8
v2,v12-0.5
v3, used_time_day-0.798
v3, used_time_month-0.798
v3, used_time_year-0.796
v4,v9-0.96
v4,v13-0.93
(1)独立高度相关的,可以只保留一个, V4 V9 V13,可以只保留V4;v3,used_time_day,used_time_month,used_time_year只保留V3;v2,v7,v11只保留v2;
(2)跟price高度相关的特征,比如depreciation_kilo或v8, 我们可以把这个特征跟其他自变量结合起来构造新的特征。

5. 降维

未完待续

总结

终于写完了,总觉得特征工程虽然入门简单但是想要精准get到有效特征很难!可能很多时候都是无用功~😿

参考:

https://tianchi.aliyun.com/notebook-ai/detail?spm=5176.12586969.1002.27.1cd8593anJSF4X&postId=95276
https://tianchi.aliyun.com/notebook-ai/detail?postId=100091

相关文章

  • 特征工程详解及实战项目(2)

    本文是在做项目过程中对特征工程部分的整理,特征工程更依赖经验知识,所以对于我这种入门小白来说需要一步步探索,本文写...

  • 特征工程之入门总结

    最近在做天池项目过程中,涉及到最重要的一步骤就是特征工程。本文旨在总结特征工程知识点,项目实战请移步:特征工程详解...

  • 杀毒与免杀技术详解之三:特征码免杀实战

    杀毒与免杀技术详解之三:特征码免杀实战杀毒与免杀技术详解之二:特征码定位-工具及原理

  • Linux Shell命令行及脚本编程实例详解

    《Linux Shell命令行及脚本编程实例详解》Shell学习实战秘笈,CU论坛力荐,300个实例,2个项目案例...

  • 4.15

    机电工程项目及建设程序 机电工程特点及组成,建设程序 机电工程特点包括项目建设的特征,工程实体的特点以及工程施工安...

  • haproxy参考

    1、安装配置2、配置项目3、配置SSL4、配置参数详解5、烂泥的haproxy6、实战

  • 项目措施费2023-03-09

    1、分部分项工程量清单,应载明项目编码、项目名称、项目特征、计量单位和工程量等内容。 2、项目措施费包括一般项目措...

  • #Python3组数据挖掘实战总结 6、7章#

    数据挖掘实战 特征工程 数据处理 Data vs Feature 列:特征 从原始数据中提取特征供算法和模型使用 ...

  • 2018-04-03-机器学习相关

    No.1 特征工程 (1)使用sklearn做单机特征工程 - jasonfreak - 博客园 (2)特征工程实...

  • java项目动态配置常量和peiconfig.propertie

    java项目动态配置常量和peiconfig.properties配置使用详解 点击:2 Java工程中配置信息一...

网友评论

      本文标题:特征工程详解及实战项目(2)

      本文链接:https://www.haomeiwen.com/subject/ecioihtx.html