零基础入门数据分析 task3

Datawhale 零基础入门数据挖掘-Task3 特征工程¶

三、 特征工程目标

Tip:此部分为零基础入门数据挖掘的 Task3 特征工程 部分,带你来了解各种特征工程以及分析方法,欢迎大家后续多多交流。

赛题:零基础入门数据挖掘 - 二手车交易价格预测

地址:https://tianchi.aliyun.com/competition/entrance/231784/introduction?spm=5176.12281957.1004.1.38b02448ausjSX

3.1 特征工程目标

对于特征进行进一步分析,并对于数据进行处理

完成对于特征工程的分析,并对于数据进行一些图表或者文字总结并打卡。

3.2 内容介绍

常见的特征工程包括:

  1. 异常处理:
    • 通过箱线图(或 3-Sigma)分析删除异常值;
    • BOX-COX 转换(处理有偏分布);
    • 长尾截断;
  2. 特征归一化/标准化:
    • 标准化(转换为标准正态分布);
    • 归一化(抓换到 [0,1] 区间);
    • 针对幂律分布,可以采用公式: log(1+x1+median)log(1+x1+median)
  3. 数据分桶:
    • 等频分桶;
    • 等距分桶;
    • Best-KS 分桶(类似利用基尼指数进行二分类);
    • 卡方分桶;
  4. 缺失值处理:
    • 不处理(针对类似 XGBoost 等树模型);
    • 删除(缺失数据太多);
    • 插值补全,包括均值/中位数/众数/建模预测/多重插补/压缩感知补全/矩阵补全等;
    • 分箱,缺失值一个箱;
  5. 特征构造:
    • 构造统计量特征,报告计数、求和、比例、标准差等;
    • 时间特征,包括相对时间和绝对时间,节假日,双休日等;
    • 地理信息,包括分箱,分布编码等方法;
    • 非线性变换,包括 log/ 平方/ 根号等;
    • 特征组合,特征交叉;
    • 仁者见仁,智者见智。
  6. 特征筛选
    • 过滤式(filter):先对数据进行特征选择,然后在训练学习器,常见的方法有 Relief/方差选择发/相关系数法/卡方检验法/互信息法;
    • 包裹式(wrapper):直接把最终将要使用的学习器的性能作为特征子集的评价准则,常见方法有 LVM(Las Vegas Wrapper) ;
    • 嵌入式(embedding):结合过滤式和包裹式,学习器训练过程中自动进行了特征选择,常见的有 lasso 回归;
  7. 降维
    • PCA/ LDA/ ICA;
    • 特征选择也是一种降维。

3.3 代码示例

3.3.0 导入数据

 

1

import pandas as pd

2

import numpy as np

3

import matplotlib

4

import matplotlib.pyplot as plt

5

import seaborn as sns

6

from operator import itemgetter

7

8

%matplotlib inline

 

1

path = './datalab/231784/'

2

Train_data = pd.read_csv(path+'used_car_train_20200313.csv', sep=' ')

3

Test_data = pd.read_csv(path+'used_car_testA_20200313.csv', sep=' ')

4

print(Train_data.shape)

5

print(Test_data.shape)
(150000, 31)
(50000, 30)

 

1

Train_data.head()

[33]:

 SaleIDnameregDatemodelbrandbodyTypefuelTypegearboxpowerkilometer...v_5v_6v_7v_8v_9v_10v_11v_12v_13v_14
007362004040230.061.00.00.06012.5...0.2356760.1019880.1295490.0228160.097462-2.8818032.804097-2.4208210.7952920.914762
1122622003030140.012.00.00.0015.0...0.2647770.1210040.1357310.0265970.020582-4.9004822.096338-1.030483-1.7226740.245522
221487420040403115.0151.00.00.016312.5...0.2514100.1149120.1651470.0621730.027075-4.8467491.8035591.565330-0.832687-0.229963
337186519960908109.0100.00.01.019315.0...0.2742930.1103000.1219640.0333950.000000-4.5095991.285940-0.501868-2.438353-0.478699
4411108020120103110.051.00.00.0685.0...0.2280360.0732050.0918800.0788190.121534-1.8962400.9107830.9311102.8345181.923482

5 rows × 31 columns

 

1

Train_data.columns

[34]:

Index(['SaleID', 'name', 'regDate', 'model', 'brand', 'bodyType', 'fuelType',
       'gearbox', 'power', 'kilometer', 'notRepairedDamage', 'regionCode',
       'seller', 'offerType', 'creatDate', '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'],
      dtype='object')

 

1

Test_data.columns

[35]:

Index(['SaleID', 'name', 'regDate', 'model', 'brand', 'bodyType', 'fuelType',
       'gearbox', 'power', 'kilometer', 'notRepairedDamage', 'regionCode',
       'seller', 'offerType', 'creatDate', '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'],
      dtype='object')

3.3.1 删除异常值

 

1

# 这里我包装了一个异常值处理的代码,可以随便调用。

2

def outliers_proc(data, col_name, scale=3):

3

    """

4

    用于清洗异常值,默认用 box_plot(scale=3)进行清洗

5

    :param data: 接收 pandas 数据格式

6

    :param col_name: pandas 列名

7

    :param scale: 尺度

8

    :return:

9

    """

10

11

    def box_plot_outliers(data_ser, box_scale):

12

        """

13

        利用箱线图去除异常值

14

        :param data_ser: 接收 pandas.Series 数据格式

15

        :param box_scale: 箱线图尺度,

16

        :return:

17

        """

18

        iqr = box_scale * (data_ser.quantile(0.75) - data_ser.quantile(0.25))

19

        val_low = data_ser.quantile(0.25) - iqr

20

        val_up = data_ser.quantile(0.75) + iqr

21

        rule_low = (data_ser < val_low)

22

        rule_up = (data_ser > val_up)

23

        return (rule_low, rule_up), (val_low, val_up)

24

25

    data_n = data.copy()

26

    data_series = data_n[col_name]

27

    rule, value = box_plot_outliers(data_series, box_scale=scale)

28

    index = np.arange(data_series.shape[0])[rule[0] | rule[1]]

29

    print("Delete number is: {}".format(len(index)))

30

    data_n = data_n.drop(index)

31

    data_n.reset_index(drop=True, inplace=True)

32

    print("Now column number is: {}".format(data_n.shape[0]))

33

    index_low = np.arange(data_series.shape[0])[rule[0]]

34

    outliers = data_series.iloc[index_low]

35

    print("Description of data less than the lower bound is:")

36

    print(pd.Series(outliers).describe())

37

    index_up = np.arange(data_series.shape[0])[rule[1]]

38

    outliers = data_series.iloc[index_up]

39

    print("Description of data larger than the upper bound is:")

40

    print(pd.Series(outliers).describe())

41

    

42

    fig, ax = plt.subplots(1, 2, figsize=(10, 7))

43

    sns.boxplot(y=data[col_name], data=data, palette="Set1", ax=ax[0])

44

    sns.boxplot(y=data_n[col_name], data=data_n, palette="Set1", ax=ax[1])

45

    return data_n

 

1

# 我们可以删掉一些异常数据,以 power 为例。  

2

# 这里删不删同学可以自行判断

3

# 但是要注意 test 的数据不能删 = = 不能掩耳盗铃是不是

4

5

Train_data = outliers_proc(Train_data, 'power', scale=3)
Delete number is: 963
Now column 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

3.3.2 特征构造

 

1

# 训练集和测试集放在一起,方便构造特征

2

Train_data['train']=1

3

Test_data['train']=0

4

data = pd.concat([Train_data, Test_data], ignore_index=True)

 

1

# 使用时间:data['creatDate'] - data['regDate'],反应汽车使用时间,一般来说价格与使用时间成反比

2

# 不过要注意,数据里有时间出错的格式,所以我们需要 errors='coerce'

3

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

4

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

 

1

# 看一下空数据,有 15k 个样本的时间是有问题的,我们可以选择删除,也可以选择放着。

2

# 但是这里不建议删除,因为删除缺失数据占总样本量过大,7.5%

3

# 我们可以先放着,因为如果我们 XGBoost 之类的决策树,其本身就能处理缺失值,所以可以不用管;

4

data['used_time'].isnull().sum()

[40]:

15072

 

1

# 从邮编中提取城市信息,相当于加入了先验知识

2

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

3

data = data

 

1

# 计算某品牌的销售统计量,同学们还可以计算其他特征的统计量

2

# 这里要以 train 的数据计算统计量

3

Train_gb = Train_data.groupby("brand")

4

all_info = {}

5

for kind, kind_data in Train_gb:

6

    info = {}

7

    kind_data = kind_data[kind_data['price'] > 0]

8

    info['brand_amount'] = len(kind_data)

9

    info['brand_price_max'] = kind_data.price.max()

10

    info['brand_price_median'] = kind_data.price.median()

11

    info['brand_price_min'] = kind_data.price.min()

12

    info['brand_price_sum'] = kind_data.price.sum()

13

    info['brand_price_std'] = kind_data.price.std()

14

    info['brand_price_average'] = round(kind_data.price.sum() / (len(kind_data) + 1), 2)

15

    all_info[kind] = info

16

brand_fe = pd.DataFrame(all_info).T.reset_index().rename(columns={"index": "brand"})

17

data = data.merge(brand_fe, how='left', on='brand')

 

1

# 数据分桶 以 power 为例

2

# 这时候我们的缺失值也进桶了,

3

# 为什么要做数据分桶呢,原因有很多,= =

4

# 1. 离散后稀疏向量内积乘法运算速度更快,计算结果也方便存储,容易扩展;

5

# 2. 离散后的特征对异常值更具鲁棒性,如 age>30 为 1 否则为 0,对于年龄为 200 的也不会对模型造成很大的干扰;

6

# 3. LR 属于广义线性模型,表达能力有限,经过离散化后,每个变量有单独的权重,这相当于引入了非线性,能够提升模型的表达能力,加大拟合;

7

# 4. 离散后特征可以进行特征交叉,提升表达能力,由 M+N 个变量编程 M*N 个变量,进一步引入非线形,提升了表达能力;

8

# 5. 特征离散后模型更稳定,如用户年龄区间,不会因为用户年龄长了一岁就变化

9

10

# 当然还有很多原因,LightGBM 在改进 XGBoost 时就增加了数据分桶,增强了模型的泛化性

11

12

bin = [i*10 for i in range(31)]

13

data['power_bin'] = pd.cut(data['power'], bin, labels=False)

14

data[['power_bin', 'power']].head()

[43]:

 power_binpower
05.060
1NaN0
216.0163
319.0193
46.068

 

1

# 删除不需要的数据

2

data = data.drop(['creatDate', 'regDate', 'regionCode'], axis=1)

 

1

print(data.shape)

2

data.columns
(199037, 39)

[45]:

Index(['SaleID', 'bodyType', 'brand', 'fuelType', 'gearbox', 'kilometer',
       'model', 'name', 'notRepairedDamage', 'offerType', 'power', 'price',
       'seller', 'train', 'v_0', 'v_1', 'v_10', 'v_11', 'v_12', 'v_13', 'v_14',
       'v_2', 'v_3', 'v_4', 'v_5', 'v_6', 'v_7', 'v_8', 'v_9', 'used_time',
       'city', 'brand_amount', 'brand_price_average', 'brand_price_max',
       'brand_price_median', 'brand_price_min', 'brand_price_std',
       'brand_price_sum', 'power_bin'],
      dtype='object')

 

1

# 目前的数据其实已经可以给树模型使用了,所以我们导出一下

2

data.to_csv('data_for_tree.csv', index=0)

 

1

# 我们可以再构造一份特征给 LR NN 之类的模型用

2

# 之所以分开构造是因为,不同模型对数据集的要求不同

3

# 我们看下数据分布:

4

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

[47]:

<matplotlib.axes._subplots.AxesSubplot at 0x7fe026cf0c18>

 

1

# 我们刚刚已经对 train 进行异常值处理了,但是现在还有这么奇怪的分布是因为 test 中的 power 异常值,

2

# 所以我们其实刚刚 train 中的 power 异常值不删为好,可以用长尾分布截断来代替

3

Train_data['power'].plot.hist()

[48]:

<matplotlib.axes._subplots.AxesSubplot at 0x7fe02d7a8eb8>

 

1

# 我们对其取 log,在做归一化

2

from sklearn import preprocessing

3

min_max_scaler = preprocessing.MinMaxScaler()

4

data['power'] = np.log(data['power'] + 1) 

5

data['power'] = ((data['power'] - np.min(data['power'])) / (np.max(data['power']) - np.min(data['power'])))

6

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

[49]:

<matplotlib.axes._subplots.AxesSubplot at 0x7fe026c342b0>

 

1

# km 的比较正常,应该是已经做过分桶了

2

data['kilometer'].plot.hist()

[50]:

<matplotlib.axes._subplots.AxesSubplot at 0x7fe029ad2080>

 

1

# 所以我们可以直接做归一化

2

data['kilometer'] = ((data['kilometer'] - np.min(data['kilometer'])) / 

3

                        (np.max(data['kilometer']) - np.min(data['kilometer'])))

4

data['kilometer'].plot.hist()

[51]:

<matplotlib.axes._subplots.AxesSubplot at 0x7fe029b1d668>

 

1

# 除此之外 还有我们刚刚构造的统计量特征:

2

# 'brand_amount', 'brand_price_average', 'brand_price_max',

3

# 'brand_price_median', 'brand_price_min', 'brand_price_std',

4

# 'brand_price_sum'

5

# 这里不再一一举例分析了,直接做变换,

6

def max_min(x):

7

    return (x - np.min(x)) / (np.max(x) - np.min(x))

8

9

data['brand_amount'] = ((data['brand_amount'] - np.min(data['brand_amount'])) / 

10

                        (np.max(data['brand_amount']) - np.min(data['brand_amount'])))

11

data['brand_price_average'] = ((data['brand_price_average'] - np.min(data['brand_price_average'])) / 

12

                               (np.max(data['brand_price_average']) - np.min(data['brand_price_average'])))

13

data['brand_price_max'] = ((data['brand_price_max'] - np.min(data['brand_price_max'])) / 

14

                           (np.max(data['brand_price_max']) - np.min(data['brand_price_max'])))

15

data['brand_price_median'] = ((data['brand_price_median'] - np.min(data['brand_price_median'])) /

16

                              (np.max(data['brand_price_median']) - np.min(data['brand_price_median'])))

17

data['brand_price_min'] = ((data['brand_price_min'] - np.min(data['brand_price_min'])) / 

18

                           (np.max(data['brand_price_min']) - np.min(data['brand_price_min'])))

19

data['brand_price_std'] = ((data['brand_price_std'] - np.min(data['brand_price_std'])) / 

20

                           (np.max(data['brand_price_std']) - np.min(data['brand_price_std'])))

21

data['brand_price_sum'] = ((data['brand_price_sum'] - np.min(data['brand_price_sum'])) / 

22

                           (np.max(data['brand_price_sum']) - np.min(data['brand_price_sum'])))

 

1

# 对类别特征进行 OneEncoder

2

data = pd.get_dummies(data, columns=['model', 'brand', 'bodyType', 'fuelType',

3

                                     'gearbox', 'notRepairedDamage', 'power_bin'])

 

1

print(data.shape)

2

data.columns
(199037, 370)

[54]:

Index(['SaleID', 'kilometer', 'name', 'offerType', 'power', 'price', 'seller',
       'train', 'v_0', 'v_1',
       ...
       'power_bin_20.0', 'power_bin_21.0', 'power_bin_22.0', 'power_bin_23.0',
       'power_bin_24.0', 'power_bin_25.0', 'power_bin_26.0', 'power_bin_27.0',
       'power_bin_28.0', 'power_bin_29.0'],
      dtype='object', length=370)

 

1

# 这份数据可以给 LR 用

2

data.to_csv('data_for_lr.csv', index=0)

3.3.3 特征筛选

1) 过滤式

 

1

# 相关性分析

2

print(data['power'].corr(data['price'], method='spearman'))

3

print(data['kilometer'].corr(data['price'], method='spearman'))

4

print(data['brand_amount'].corr(data['price'], method='spearman'))

5

print(data['brand_price_average'].corr(data['price'], method='spearman'))

6

print(data['brand_price_max'].corr(data['price'], method='spearman'))

7

print(data['brand_price_median'].corr(data['price'], method='spearman'))
0.572828519605
-0.408256970162
0.0581566100256
0.383490957606
0.259066833881
0.386910423934

 

1

# 当然也可以直接看图

2

data_numeric = data[['power', 'kilometer', 'brand_amount', 'brand_price_average', 

3

                     'brand_price_max', 'brand_price_median']]

4

correlation = data_numeric.corr()

5

6

f , ax = plt.subplots(figsize = (7, 7))

7

plt.title('Correlation of Numeric Features with Price',y=1,size=16)

8

sns.heatmap(correlation,square = True,  vmax=0.8)

[57]:

<matplotlib.axes._subplots.AxesSubplot at 0x7fe0283a6dd8>

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值