datawhale 學習筆記——特徵工程入門

拖延症又犯了,最後一天才寫博客,。,不想給自己找啥理由了000。

特徵工程介紹

我理解的特徵工程是一種更深層的數據分析,爲特定的數據做處理,深挖其中的信息,以便後續模型得到更好的效果。

本文是參考天池二手車預測比賽的教程——特徵工程做的筆記,task3 中提到了很多特徵處理的方法,主要介紹了其中的五種方法:

  • 異常數據處理
  • 特徵構造
  • 數據分桶
  • 特徵歸一化
  • 特徵篩選

首先,導入需要的包,並載入數據:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from operator import itemgetter

path = './data/'
Train_data = pd.read_csv(path+'used_car_train_20200313.csv', sep=' ')
Test_data = pd.read_csv(path+'used_car_testA_20200313.csv', sep=' ')

接下來就是各種特徵處理的方法了。

異常數據處理

這部分教程中使用的是箱線圖來處理,箱線圖的理解可以參考 箱線圖的理解

總的來講,就是根據四分位線,設置一個上邊界線和下邊界線。上下邊界線分別是由上四分位數和下四分位數加減“箱子”的高度得到的。這裏的箱子高度是指特定倍數的四分位數差,教程裏設置的是三倍四分位數差。

數值超出上下邊界線的數據被劃分爲異常數據。對於異常數據,我們可以選擇截斷或是直接刪除的方式來處理,教程中採用的是後者。

對 power 特徵進行數據處理,由於其中得異常值共九百多個,數量較多,因此,截斷的方式更好。我對代碼做了點更改,改成了截斷的方式。代碼如下(默認已經導入需要的包):

def outliers_proc(data, col_name, scale=3):
    """
    用於清洗異常值,默認用 box_plot(scale=3)進行清洗
    :param data: 接收 pandas.DataFrame 數據格式
    :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  # type = <class 'pandas.core.series.Series'>
        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]]  # 取出所有爲 True 的元素下標,即異常值的元素下標(第一次遇到這種操作,神奇)
    print(f"abnormal data number is: {len(index)}")
    # data_n = data_n.drop(index)  # 這裏不做刪除操作,做截斷操作
    # data_n.reset_index(drop=True, inplace=True)  # 重置索引,刪除異常值時需要將索引重置,drop=True 表示不保留以前的索引

    for i in range(2):
        row = np.arange(data_series.shape[0])[rule[i]]  # 取出異常值的下標
        data_n.loc[row, [col_name]] = value[i]
        # print(data_n.loc[row, [col_name]], value[i])
        print(f"change {'low' if i == 0 else 'high'} data number is : {len(row)}")

    ##########---------- 到這裏數據就已經截斷完了,下面是數據的展示  ----------##########

    print(f"Now column number is: {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])
    plt.show()
    return data_n

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

運行結果如下:

abnormal data number is: 963
change low data number is : 0
change high data number is : 963
Now column number is: 150000
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

power特徵箱線圖的圖像

可以看到,一共 963 個異常數據,而且異常類型全都是超出上邊界線。

特徵構造

教程裏在特徵構造方面,主要是新構造了一些特徵,構造一些認爲對模型有幫助的特徵。

第一個新特徵是使用時間:data[‘creatDate’] - data[‘regDate’],反應汽車使用時間,一般來說價格與使用時間成反比。這裏的 creatDate 是廣告發布時間;regDate 是汽車註冊時間。二者相減,就是汽車的使用時間。由於時間數據有的格式解析有問題,在前天晚上的直播中,作者也解釋了這個問題,因此,會有部分非法數據。一共有 15101 個非法數據,佔總樣本量過大,7.5%,教程裏不建議刪除。

第二個新特徵是城市信息,作者根據郵編信息,提取出郵編內的城市信息。(直播中,作者解釋了爲什麼知道這是德國的郵編格式)

最後就是計算某品牌的銷售統計量,教程作者根據訓練集中的數據信息,進行了一系列的統計計算。

特徵如果使用完了,認爲沒有什麼更多的信息了,就可以將其刪除,降低一下維度。

數據分桶

數據分桶可以簡單地理解成將數據離散化,其優點可以總結成如下三點:

  1. 稀疏向量,計算速度更快。
  2. 離散後的特徵更穩定,魯棒性更強,增強了對異常數據的抗干擾性。
  3. 離散化可以引入非線性,類似於神經網的激活函數。

教程中對 power 特徵進行了分桶操作:

bin = [i*10 for i in range(31)]
data['power_bin'] = pd.cut(data['power'], bin, labels=False)
print(data[['power_bin', 'power']].head())

運行結果:

power_bin power
0 5.0 60
1 NaN 0
2 16.0 163
3 19.0 193
4 6.0 68

特徵歸一化

特徵歸一化還是對剛纔的 power 特徵做的操作,可以先不對其進行異常值截斷,先查看其特徵分佈:

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

運行結果:

power特徵可視化

可以看到,特徵很符合長尾分佈的特點。長尾分佈就是尾巴很長的分佈,這種分佈的尾巴處數據很少,如果數據出現不準的情況,會造成較大影響,畢竟尾巴處可參考的數據很少。

可以先對特徵做 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()
plt.show()

注意這裏的 log+1 的操作,目的是使計算後的數據值都大於 0。運行結果如下:

power特徵歸一化

特徵篩選

特徵篩選有三種方法:(1)過濾式(2)包裹式(3)嵌入式

詳細介紹請參考:特徵選擇/篩選方法總結

過濾式(Filter)

過濾式的思想就是給每個特徵“打分”,相當於給特徵賦予權值,權值越大,特徵越重要。主要的“打分”方法有三種:

  • 信息增益
  • 卡方檢驗
  • 相關係數

教程中使用的是相關係數進行打分,計算每個特徵與 price 的相關係數。由於負相關也是一種強相關,因此打分時,需要將相關係數取絕對值。

教程中畫了相關係數的熱力圖,可以很直觀地感受到各特徵之間的相關性強弱。

包裹式(Wrapper)

包裹式的主要思想是選擇不同的特徵子集,對不同的特徵子集組合進行打分。將特徵選擇的問題轉換成優化問題,尋找分值較高的特徵子集。使用要使用的分類器作爲一個評價函數,對特徵子集進行打分。

教程裏使用的 mlxtend.feature_selection 下的 SequentialFeatureSelector 方法進行特徵選擇,其中評價函數使用 LinearRegression

嵌入式(Embedded)

在模型既定的情況下學習出對提高模型準確性最好的特徵。也就是在確定模型的過程中,挑選出那些對模型的訓練有重要意義的特徵。

這個教程中沒有給出案例,以後會有,留待下次記錄。

最後

整個教程讓人對特徵有了一個更深的認識,通過教程,直接和間接地瞭解到了很多知識,很贊。有點遺憾的是,最近沒有花太多時間在這上面,僅僅只是達到了一個入門的狀態。不過每天都在進步的感覺還是很有成就感的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章