天天看点

特征工程-决定了结果的上限

一、异常值处理

# 箱线图(3-Sigma)删除异常值
def outliers_proc(data, col_name, scale=3):
    """
    用于清洗异常值,默认用 box_plot_outliers(scale=3)进行清洗
    :param data: 接收 pandas 数据格式
    :param col_name: pandas 列名
    :param scale: 尺度  
    正常特征取值范围[1/4分布值 - iqr, 3/4分布值 + iqr]
    举例: lst = [-20, 0, 1, 2, 3, 4, 5, 6, 7, 20, 21, 31](12个数)
    1/4 分位数是: 1
    3/4 分位数是: 7
    则 iqr = 3*(7-1) = 18
    正常值取值范围是[1-18, 7+18] = [-17, 25]
    所以lst中的异常值为[-20, 31]
    :return: 依然是DataFrame类型, 删除了异常值
    """

    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

# 我们可以删掉一些异常数据,以 power 为例。  
# 这里删不删同学可以自行判断
# 但是要注意 test 的数据不能删 = = 不能掩耳盗铃是不是

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

因为缺失值删除样本, 要看缺失值的比例(具体是多少忘记了, 以后补上)

二、特征构造

tricks1: 训练集和测试集放在一起,节省一半代码

Train_data['train']=1
Test_data['train']=0
data = pd.concat([Train_data, Test_data], ignore_index=True)# 默认行合并(axis=0)
           

注意这里的ignore_index参数一定设为True, 因为训练集和测试集的索引都是从0开始的, 合并后希望索引顺延

tricks2: 时间特征相减

# 注意,数据里有时间出错的格式,所以我们需要 errors='coerce'
data['used_time'] = (pd.to_datetime(data.cteatDate, format='%Y%m%d', error='coerce') - pd.to_datetime(data.regDate, format='%Y%m%d', error='coerce')).dt.days
           

tricks3: 分类特征统计量

# 这里要以 train 的数据计算统计量(计算每个品牌销售价格的统计量)
Train_gb = Train_data.groupby("brand")
all_info = {}
for kind, kind_data in Train_gb:# kind 是brand的种类,这里是 1-40, kind_data是每一类每条记录对应的其他特征值
    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') # 除了concat, merge方法更适合两个dataframe类型合并 data = pd.merge(df1, df2, how='', on='')
           

tricks4: 数据分桶

以 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)]  # 与qcut按频数分桶加以区分,q是数值, bins是区间
data['power_bin'] = pd.cut(data['power'], bin, labels=False)
data[['power_bin', 'power']].head()
           

tricks5: 不同的模型对特征工程的精细程度要求是不一样的, 像XGBoost或其他类树模型, 不需要对每个数据特征做归一化、标准化、oneHot等操作, 而对于LR、NN等模型,需要对每个特征进行以上操作, 这是非常值得注意的地方.

参考文章:

https://tianchi.aliyun.com/notebook-ai/detail?postId=95501