[比赛]二手车交易价格预测-特征工程

今天直接步入正题!也是借鉴了datawhale的baseline以及大佬的博客,在此表示感谢!


1.数据准备

# 准备数据
train_y = train['price']
del train['price']
data = pd.concat([train, test], axis=0)
data.set_index('SaleID', inplace=True)

# 按类型划分数据
numeric_features = ['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 = ['model','brand','bodyType','fuelType','gearbox','notRepairedDamage','regionCode']
time_features = ['regDate', 'creatDate']
num_data = data[numeric_features]
cat_data = data[categorical_features]
time_data = data[time_features]

注意点:首先将训练集和测试集合并,随后按类别将其划分开,为以后的处理做准备。

2.异常值处理

异常值处理有很多方法,这里我们运用到boxplot然后对异常值进行删除或截尾,datawhale包装出一个直接可以使用的代码用于删除异常值,测试集不可用,也看到大佬进行了改正,对异常值进行截尾。

# 删除异常值
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)
    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("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
# 异常值截尾处理
def outliers_proc(data, col_name, scale=3):
    """
        用于截尾异常值,默认用box_plot(scale=3)进行清洗
        param:
            data:接收pandas数据格式
            col_name: pandas列名
            scale: 尺度
    """
    data_col = data[col_name]
    Q1 = data_col.quantile(0.25) # 0.25分位数
    Q3 = data_col.quantile(0.75)  # 0.75分位数
    IQR = Q3 - Q1
    
    data_col[data_col < Q1 - (scale * IQR)] = Q1 - (scale * IQR)
    data_col[data_col > Q3 + (scale * IQR)] = Q3 + (scale * IQR)

    return data[col_name]

3.缺失值处理

我们考虑几种处理方式:

  • 不处理(针对xgboost等树模型)
  • 考虑删除该列(如果缺失的太多)
  • 缺失值填补(均值,中位数,众数,建模预测,多重插补等)
  • 分箱处理(将缺失值构造成一个箱)
# 删除重复值
data.drop_duplicates()
# 直接删除缺失样本
dropna() 

# 填充值
train_data.fillna(0, inplace=True) # 填充 0
data.fillna({0:1000, 1:100, 2:0, 4:5})   # 可以使用字典的形式为不用列设定不同的填充值

train_data.fillna(train_data.mean(), inplace=True) # 填充均值
train_data.fillna(train_data.median(), inplace=True) # 填充中位数
train_data.fillna(train_data.mode(), inplace=True) # 填充众数

train_data.fillna(method='pad', inplace=True) # 填充前一条数据的值,但是前一条也不一定有值
train_data.fillna(method='bfill', inplace=True) # 填充后一条数据的值,但是后一条也不一定有值

在这里首先不处理缺失值,因为后期可能会直接用树模型,或者是其他方法。

4.数据转换

对数值特征进行归一化

from sklearn.preprocessing import MinMaxScaler
minmax = MinMaxScaler()
num_data_minmax = minmax.fit_transform(num_data)
num_data_minmax = pd.DataFrame(num_data_minmax, columns=num_data.columns, index=num_data.index)

对类别特征进行独热编码

hot_features = ['bodyType', 'fuelType', 'gearbox', 'notRepairedDamage']
cat_data_hot = pd.get_dummies(cat_data, columns=hot_features)

5.数据分桶

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

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

# 数据分桶
bin = [i*10 for i in range(31)]
num_data['power_bin'] = pd.cut(num_data['power'], bin, labels=False)

6.特征构造

下面一部分内容较多,且主要是大佬的思路,我就不赘述了。

7.特征筛选

选择特征的角度:

  • 特征是否发散:如果一个特征不发散,例如方差接近于0,也就是说样本在这个特征上基本上没有差异,这个特征对于样本的区分并没有什么用。
  • 特征与目标的相关性:这点比较显见,与目标相关性高的特征,应当优选选择。

选择特征的方法:

  • Filter:过滤法,按照发散性或者相关性对各个特征进行评分,设定阈值或者待选择阈值的个数,选择特征。
  • Wrapper:包装法,根据目标函数(通常是预测效果评分),每次选择若干特征,或者排除若干特征。
  • Embedded:嵌入法,先使用某些机器学习的算法和模型进行训练,得到各个特征的权值系数,根据系数从大到小选择特征。类似于Filter方法,但是是通过训练来确定特征的优劣。
(1)过滤式

对每一维特征“打分”,即给每一维的特征赋予权重,这样的权重就代表着该特征的重要性,然后依据权重排序。先进行特征选择,然后去训练学习器,所以特征选择的过程与学习器无关。相当于先对特征进行过滤操作,然后用特征子集来训练分类器。

(2)包装式

包裹式从初始特征集合中不断的选择特征子集,训练学习器,根据学习器的性能来对子集进行评价,直到选择出最佳的子集。包裹式特征选择直接针对给定学习器进行优化。

(3)嵌入式

在模型既定的情况下学习出对提高模型准确性最好的特征。也就是在确定模型的过程中,挑选出那些对模型的训练有重要意义的特征。

8.PCA降维


自己有些地方也不是太懂,日后有时间再补充

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值