Datawhale组队学习 Task3-特征工程

1. 特征工程概述

1.1 相关定义及理解

(1)特征工程:最大限度从原始数据中提取特征以供算法和模型使用。呈现给算法的数据需拥有基本数据的相关结构/属性,特征工程即:将数据属性转化为数据特征,经过特征工程的预处理,能够减少噪声对算法模型的干扰。数据和特征决定机器学习的上限,而模型和算法只是逼近这个上限。
(2)特征工程是能够将数据像艺术一样展现的技术,好的特征工程很好的混合了专业领域知识、直觉和基本的数学能力。
(3)对于特征工程中引用的新特征,需要验证它的确能提高预测的准确度,而不是加入了一个无用特征,不然只会增加算法运算的复杂度。
(4)举例来讲:现在出一非常简答的二分类问题题,请你使用逻辑回归,设计一个身材分类器。输入数据X:身高和体重 ,标签为Y:身材等级(胖,不胖)。显然,不能单纯的根据体重来判断一个人胖不胖,姚明很重,他胖吗?显然不是。针对这个问题,一个非常经典的特征工程是,BMI指数,BMI=体重/(身高^2)。这样,通过BMI指数,就能非常显然地帮助我们,刻画一个人身材如何。甚至,你可以抛弃原始的体重和身高数据。
(5)被认为是一种强大的自动化特征工程工具的deep learning,能够自动学习各种低级高级的特征,我们就不需要特征工程了吗?未必如此。参考链接【3】中作者以声纹识别为例说明,前端依旧需要包含专家知识的特征工程。参考链接【4】中作者认为神经网络并非万能,依旧依赖一个基础的特征库,好的特征能够有效减少模型复杂度。
(6)总结:筛选特征(降噪)→训练模型,对提高模型精度是有意义的。

1.2 内容

摘录参考链接【1】,并结合Datewhale本期教程内容,整理如下:
在这里插入图片描述

2. 代码示例

2.1 导入数据

path = './datalab/231784/'
Train_data = pd.read_csv(path+'used_car_train_20200313.csv', sep=' ')
Test_data = pd.read_csv(path+'used_car_testA_20200313.csv', sep=' ')
print(Train_data.shape)
print(Test_data.shape)

2.2 删除异常值(本文利用箱线图剔除异常值)

2.2.1 箱线图

在这里插入图片描述

  • 粉红色箱体,中间黄线是中位数;左侧线是下四分位数(Q1);右侧线是上四分位数(Q3)
  • 四分位距 I Q R = Q 3 − Q 1 IQR=Q3-Q1 IQR=Q3Q1
  • Q 1 − 1.5 ∗ I Q R Q1-1.5*IQR Q11.5IQR得到最小值(下边缘);用 Q 3 + 1.5 ∗ I Q R Q3+1.5*IQR Q3+1.5IQR得到最大值(上边缘)
  • 在上下边缘之外的值即为异常值

2.2.2 删除

# 2.2 删除异常值
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)) # 四分位距
        val_low = data_ser.quantile(0.25)-iqr  # 最小值边界
        val_up = data_ser.quantile(0.75)+iqr  # 最大值边界
        rule_low = (data_ser < val_low)
        rule_up = (data_ser > val_up)
        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)  # (rule_low, rule_up), (val_low, val_up)
    index = np.arange(data_series.shape[0])[rule[0] | rule[1]]  # 返回上下边界外数据的索引号
    print("Delete number is: {}".format(len(index)))  # 输出删除条目数

    data_n = data_n.drop(index)  # 删除数据
    data_n.reset_index(drop=True, inplace=True)
    print("Now column number is: {}".format(data_n.shape[0]))  # 输出删除后的条目数

    # 输出超出下边界异常值的数据特征
    index_low = np.arange(data_series.shape[0])[rule[0]]  # 超出下边界的异常值索引
    outliers = data_series.iloc[index_low]
    print(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]]
    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])
    return data_n

Train_data = outliers_proc(Train_data, 'power', scale=3)

在这里插入图片描述
在这里插入图片描述

2.3 用于决策树的特征构造

2.3.1 数据集拼接

# 训练集和测试集放在一起,方便构造特征
Train_data['train']=1
Test_data['train']=0
data = pd.concat([Train_data, Test_data], ignore_index=True)  # 忽略索引,先拼接,再重构索引

2.3.2 构造特征

# (1)使用时间=data['creatDate'] - data['regDate'],反应汽车使用时间,一般与价格成反比
# 注:数据中有时间出错的格式,所以需要errors='coerce'。根据data['used_time'].isnull().sum()显示,共有15k样本为空,我们 XGBoost 之类的决策树,其本身就能处理缺失值,所以先可以不用管
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
# (2)从邮编中提取城市信息
data['city'] = data['regionCode'].apply(lambda x : str(x)[:-3])
data = data

# (3)计算train中的某品牌的销售统计量
Train_gb = Train_data.groupby("brand")
all_info = {}
for kind, kind_data in Train_gb:
    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
brand_fe = pd.DataFrame(all_info).T.reset_index().rename(columns={"index": "brand"})
data = data.merge(brand_fe, how='left', on='brand')

2.3.3 数据分桶

为什么要数据分桶:

  1. 离散后稀疏向量内积乘法运算速度更快,计算结果易于储存,容易扩展
  2. 对特征值更具有鲁棒性,如 age>30 为 1 否则为 0,对于年龄为 200 的也不会对模型造成很大的干扰
  3. LR 属于广义线性模型,表达能力有限,经过离散化后,每个变量有单独的权重,这相当于引入了非线性,能够提升模型的表达能力,加大拟合
  4. 离散后特征可以进行特征交叉,提升表达能力,由 M+N 个变量编程 M*N 个变量,进一步引入非线形,提升了表达能力
  5. 特征离散后模型更稳定,如用户年龄区间,不会因为用户年龄长了一岁就变化
  6. LightGBM 在改进 XGBoost 时就增加了数据分桶,增强了模型的泛化性

pd.cut()
pd.cut(x, bins, right=True, labels=None, retbins=False, precision=3, include_lowest=False, duplicates=‘raise’)

参数含义
x被切分的类数据
bins(1)int,表示被平均分成几份;(2)标量序列,定义被分割后的每个区间边缘;(3)pandas.IntervalIndex,定义要使用的精确区间
rightTrue,包含区间右部;反之不包含
labels给分割后的bins打标签
retbinsbool型的参数,表示是否将分割后的bins返回,当bins为一个int型的标量时比较有用,这样可以得到划分后的区间,默认为False
precision保留区间小数点的位数,默认为3
include_lowestbool型的参数,表示区间的左边是开还是闭的,默认为false,也就是不包含区间左部
duplicates是否允许重复区间。raise,不允许;drop,允许

2.3.4 删除无用数据

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

2.4 (扩展)用于LR的特征构造

不同模型对数据集的要求不同,因此需要单独构造一份特征

2.4.1 power中的异常值处理

在2.3一节中我们利用箱线图对power异常值处理完毕,但是数据可视化后能够发现,由于未对test异常值处理,使得数据分布依旧异常。在这里我们不用于2.3的处理,保留全部异常值,之后用长尾分布截断解决该问题。

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

在这里插入图片描述

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

在这里插入图片描述
在本节中,我们对其取log,再做归一化

from sklearn import preprocessing
min_max_scaler = preprocessing.MinMaxScaler()
data['power'] = np.log(data['power'] + 1) 
data['power'] = ((data['power'] - np.min(data['power'])) / (np.max(data['power']) - np.min(data['power'])))
data['power'].plot.hist()

2.4.2 归一化处理

对km和刚刚构造的统计量特征做归一化

data['kilometer'] = ((data['kilometer'] - np.min(data['kilometer'])) /
                        (np.max(data['kilometer']) - np.min(data['kilometer'])))
data['kilometer'].plot.hist()
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'])))

2.4.3 对类别特征进行OneEncoder

data = pd.get_dummies(data, columns=['model', 'brand', 'bodyType', 'fuelType',\
                                     'gearbox', 'notRepairedDamage', 'power_bin'])
data.to_csv('data_for_lr.csv', index=0)

2.5 特征筛选

2.5.1 过滤式

data_numeric = data[['power', 'kilometer', 'brand_amount', 'brand_price_average', 
                     'brand_price_max', 'brand_price_median']]
correlation = data_numeric.corr()

f , ax = plt.subplots(figsize = (7, 7))
plt.title('Correlation of Numeric Features with Price',y=1,size=16)
sns.heatmap(correlation,square = True,  vmax=0.8)

在这里插入图片描述

2.5.2 包裹式

# k_feature 太大会很难跑,没服务器,所以提前 interrupt 了
from mlxtend.feature_selection import SequentialFeatureSelector as SFS
from sklearn.linear_model import LinearRegression
sfs = SFS(LinearRegression(),
           k_features=10,
           forward=True,
           floating=False,
           scoring = 'r2',
           cv = 0)
x = data.drop(['price'], axis=1)
x = x.fillna(0)
y = data['price']
sfs.fit(x, y)
sfs.k_feature_names_ 

from mlxtend.plotting import plot_sequential_feature_selection as plot_sfs
import matplotlib.pyplot as plt
fig1 = plot_sfs(sfs.get_metric_dict(), kind='std_dev')
plt.grid()
plt.show()

2.5.3 嵌入式

(下一章介绍)

3. 收获及未来工作

  • 不能因为用深度学习的方法,就完全不考虑特征工程的工作。反思自己现在做的课题并非是图像、语言、文本类难以进行特征工程的内容,所以还是要基于现实、基于业务的把握,多加思考,组织数据。
  • 还是不能不加判断的堆砌特征,需要根据效果判断,更需要好好组织输入的形式,这方面我了解的太少了,多看多实践。

【参考文献/链接】

【1】使用sklearn做单机特征工程
【2】七种常用的特征工程
【3】特征工程到底是什么?-ID:包大人
【4】特征工程到底是什么?-ID:管他叫大靖
【5】统计基础:剔除箱线图所提示的异常值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值