Task3 二手車數據特徵工程

前言

在之前的工作中Task2 數據探索性分析
探索數據和了解數據
發現數據一些需要處理的地方有
1、缺失值,類別特徵(bodyType, gearbox, fullType, notRepaired)
2、異常值,可通過箱線圖分析或長尾階段
3、類別傾斜的特徵(seller, offtype),刪掉
4、類別數據需要編碼
5、數值數據歸一化和標準化
6、價格不服從正態分佈,需要進行log轉換
7、power特徵分佈有異常,需要

特徵工程是什麼

有這麼一句話在業界廣泛流傳:數據和特徵決定了機器學習的上限,而模型和算法只是逼近這個上限而已。那特徵工程到底是什麼呢?顧名思義,其本質是一項工程活動,目的是最大限度地從原始數據中提取特徵以供算法和模型使用。
引用下羣裏大佬的總結
在這裏插入圖片描述
數據清洗
目的:提高數據質量,降低算法用錯誤數據建模的風險。

  1. 特徵變換:模型無法處理或不適合處理
    a) 定性變量編碼:Label Encoder;Onehot Encoder;Distribution coding;
    b) 標準化和歸一化:z分數標準化(標準正太分佈)、min-max 歸一化;
  2. 缺失值處理:增加不確定性,可能會導致不可靠輸出
    a) 不處理:少量樣本缺失;
    b) 刪除:大量樣本缺失;
    c) 補全:(同類)均值/中位數/衆數補全;高維映射(One-hot);模型預測;最鄰近補全;
    矩陣補全(R-SVD);
  3. 異常值處理:減少髒數據
    a) 簡單統計:如 describe() 的統計描述;散點圖等;
    b) 3∂ 法則(正態分佈)/箱型圖截斷;
    c) 利用模型進行離羣點檢測:聚類、K近鄰、One Class SVM、Isolation Forest;
  4. 其他:刪除無效列/更改dtypes/刪除列中的字符串/將時間戳從字符串轉換爲日期時間格式等

特徵構造
目的:增強數據表達,添加先驗知識。

  1. 統計量特徵:
    a) 計數、求和、比例、標準差;
  2. 時間特徵:
    a) 絕對時間、相對時間、節假日、雙休日;
  3. 地理信息:
    a) 分桶;
  4. 非線性變換:
    a) 取 log/平方/根號;
  5. 數據分桶:
    a) 等頻/等距分桶、Best-KS 分桶、卡方分桶;
  6. 特徵組合

特徵選擇
目的:降低噪聲,平滑預測能力和計算複雜度,增強模型預測性能。

  1. 過濾式(Filter):先用特徵選擇方法對初識特徵進行過濾然後再訓練學習器,特徵
    選擇過程與後續學習器無關。
    a) Relief/方差選擇/相關係數/卡方檢驗/互信息法
  2. 包裹式(Wrapper):直接把最終將要使用的學習器的性能作爲衡量特徵子集的評
    價準則,其目的在於爲給定學習器選擇最有利於其性能的特徵子集。
    a) Las Vegas Wrapper(LVM)
  3. 嵌入式(Embedding):結合過濾式和包裹式方法,將特徵選擇與學習器訓練過程
    融爲一體,兩者在同一優化過程中完成,即學習器訓練過程中自動進行了特徵選擇。
    a) LR+L1或決策樹

類別不平衡
缺點:少類別提供信息太少,沒有學會如何判別少數類。

  1. 擴充數據集;
  2. 嘗試其他評價指標:AUC等;
  3. 調整θ值;
  4. 重採樣:過採樣/欠採樣;
  5. 合成樣本:SMOTE;
  6. 選擇其他模型:決策樹等;
  7. 加權少類別人樣本錯分代價;
  8. 創新:
    a) 將大類分解成多個小類;
    b) 將小類視爲異常點,並用異常檢測建模。

特徵工程常見方法

常見的特徵工程包括:
1、異常處理:
通過箱線圖(或 3-Sigma)分析刪除異常值;
BOX-COX 轉換(處理有偏分佈);
長尾截斷;
2、特徵歸一化/標準化:
標準化(轉換爲標準正態分佈);
歸一化(抓換到 [0,1] 區間);
針對冪律分佈,可以採用公式:
3、數據分桶:
等頻分桶;
等距分桶;
Best-KS 分桶(類似利用基尼指數進行二分類);
卡方分桶;
4、缺失值處理:
不處理(針對類似 XGBoost 等樹模型);
刪除(缺失數據太多);
插值補全,包括均值/中位數/衆數/建模預測/多重插補/壓縮感知補全/矩陣補全等;
分箱,缺失值一個箱;
5、特徵構造:
構造統計量特徵,報告計數、求和、比例、標準差等;
時間特徵,包括相對時間和絕對時間,節假日,雙休日等;
地理信息,包括分箱,分佈編碼等方法;
非線性變換,包括 log/ 平方/ 根號等;
特徵組合,特徵交叉;
仁者見仁,智者見智。
6、特徵篩選
過濾式(filter):先對數據進行特徵選擇,然後在訓練學習器,常見的方法有 Relief/方差選擇發/相關係數法/卡方檢驗法/互信息法;
包裹式(wrapper):直接把最終將要使用的學習器的性能作爲特徵子集的評價準則,常見方法有 LVM(Las Vegas Wrapper) ;
嵌入式(embedding):結合過濾式和包裹式,學習器訓練過程中自動進行了特徵選擇,常見的有 lasso 迴歸;
7、降維
PCA/ LDA/ ICA;
特徵選擇也是一種降維

主要工作

長尾截斷

查看power分佈情況(本應在[0,600])

print(Train_data['power'].describe())
print(Train_data['power'][Train_data['power']>600].describe())
plt.hist(Train_data['power'][Train_data['power']>600], histtype = 'bar') 
plt.show()
print(Test_data['power'].describe())
print(Test_data['power'][Test_data['power']>600].describe())
plt.hist(Test_data['power'][Test_data['power']>600], histtype = 'bar') 
plt.show()

可以看到超過600的部分
在這裏插入圖片描述
在這裏插入圖片描述
有超過最大範圍600的值,這裏採用長尾截斷,對超過600的值,取值爲600

Train_data['power'][Train_data['power']>600]=600
Test_data['power'][Test_data['power']>600]=600

箱線圖去異常值

在這裏插入圖片描述
箱型圖,是利用數據中的五個統計量:最小值、第一四分位數、中位數、第三四分位數與最大值來描述數據的一種方法,它也可以粗略地看出數據是否具有有對稱性,分佈的分散程度等信息,特別可以用於對幾個樣本的比較。
四分位距IQR=Q3-Q1
圖片紅色是k=1.5時,計算出的是中度異常的範圍。
藍色是K=3計算出的是極度異常的範圍。
1、內限
上限是非異常範圍內的最大值。上限=Q3+1.5IQR
下限是非異常範圍內的最小值。下限=Q1-1.5IQR
這個界限叫內限
2、外限
外限與內限的計算方法相同,唯一的區別就在與:上面的T形線段所延伸到的極遠處,是Q3+3IQR與剔除異常值後的極大值兩者取最小,下面的T形線段所延伸到的極遠處,是Q1-3IQR與剔除異常值後的極小值兩者取最大。

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))#IQR
        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
Train_data = outliers_proc(Train_data, 'price', scale=3)
Train_data['price'] = np.log1p(Train_data['price'])
Train_data = outliers_proc(Train_data, 'price', scale=3)

在這裏插入圖片描述
在這裏插入圖片描述
做了log(1+x)處理後明顯改善了
迴歸模型大多要將非正態分佈數據轉化爲正態分佈,這是模型假設決定的

缺失值處理

缺失值處理有幾個方案
1、不處理(xgboost等樹模型)
2、缺失的過多可以直接刪除該列
3、有(均值、中位數、衆數)補全
4、分箱處理,有缺失值的分爲一個箱

# 刪除重複值
data.drop_duplicates()
# data.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) # 填充後一條數據的值,但後一條也不一定有值

"""插值法:用插值法擬合出缺失的數據,然後進行填充。"""
for f in features: 
    train_data[f] = train_data[f].interpolate()    
train_data.dropna(inplace=True)

由上一篇文章中的結論缺失值較多的列有,bodyType、gearbox、fuelTypehe和notRepairedDamage,這些特徵都是類別特徵,所以用均值中位數衆數填充或插值法都不合理,暫時沒有一個特別好的方法,後面可以用樹模型自行處理帶缺失值帶缺失值的數據
在這裏插入圖片描述

特徵構造

1、使用時間:data[‘creatDate’] - data[‘regDate’]得到汽車使用時間,一般來說價格與使用時間成反比

# 訓練集和測試集放在一起,方便構造特徵
Train_data['train']=1
Test_data['train']=0
data = pd.concat([Train_data, Test_data], ignore_index=True)#兩份數據列方向拼接

# 要注意,數據裏有時間出錯的格式,所以我們需要 errors='coerce'
#轉爲datetime形式,可做時間運算
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#以天爲單位

# 看一下空數據,有 15k 個樣本的時間是有問題的,我們可以選擇刪除,也可以選擇放着。
# 但是這裏不建議刪除,因爲刪除缺失數據佔總樣本量過大,7.5%
# 我們可以先放着,因爲如果我們 XGBoost 之類的決策樹,其本身就能處理缺失值,所以可以不用管;
data['used_time'].isnull().sum()

2、從郵編中提取城市信息

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

3、計算某品牌的銷售統計量(構造表徵品牌的特徵)

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')
# 刪除不需要的數據
data = data.drop(['creatDate', 'regDate', 'regionCode','SaleID','seller','offerType'], axis=1)

數據分桶(特徵離散化)

這時候我們的缺失值也進桶了,
爲什麼要做數據分桶
離散後的特徵對異常值更具魯棒性,如 age>30 爲 1 否則爲 0,對於年齡爲 200 的也不會對模型造成很大的干擾;
特徵離散後模型更穩定,如用戶年齡區間,不會因爲用戶年齡長了一歲就變化
當然還有很多原因,LightGBM 在改進 XGBoost 時就增加了數據分桶,增強了模型的泛化性

bin = [i*10 for i in range(31)]#定義31個桶
data['power_bin'] = pd.cut(data['power'], bin, labels=False)#以10爲間隔分桶
data[['power_bin', 'power']].head()

#目前的數據其實已經可以給樹模型使用了,所以我們導出一下
data.to_csv('data_for_tree.csv', index=0)#不保存行索引

歸一化

我們可以再構造一份特徵給 LR NN 之類的模型用
之所以分開構造是因爲,不同模型對數據集的要求不同
我們看下數據分佈:

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

在這裏插入圖片描述
km 的比較正常,應該是已經做過分桶了

data['kilometer'].plot.hist()
# 所以我們可以直接做歸一化
data['kilometer'] = ((data['kilometer'] - np.min(data['kilometer'])) / 
                        (np.max(data['kilometer']) - np.min(data['kilometer'])))
data['kilometer'].plot.hist()

在這裏插入圖片描述
除此之外 還有我們剛剛構造的統計量特徵:
‘brand_amount’, ‘brand_price_average’, ‘brand_price_max’,
‘brand_price_median’, ‘brand_price_min’, ‘brand_price_std’,
‘brand_price_sum’

def max_min(x):
    return (x - np.min(x)) / (np.max(x) - np.min(x))
data['brand_amount'] = max_min(data['brand_amount']

對類別特徵one_hot編碼

one_hot編碼

  1. 離散後稀疏向量內積乘法運算速度更快,計算結果也方便存儲,容易擴展;
  2. LR 屬於廣義線性模型,表達能力有限,經過離散化後,每個變量有單獨的權重,這相當於引入了非線性,能夠提升模型的表達能力,加大擬合;
  3. 離散後特徵可以進行特徵交叉,提升表達能力,由 M+N 個變量編程 M*N 個變量,進一步引入非線形,提升了表達能力;
data = pd.get_dummies(data, columns=['model', 'brand', 'bodyType', 'fuelType',
                                     'gearbox', 'notRepairedDamage', 'power_bin'])
 # 這份數據可以給 LR 用
data.to_csv('data_for_lr.csv', index=0)

特徵篩選

用斯皮爾曼,不用皮爾遜相關係數,因爲要求正態分佈

# 相關性分析,各個特徵與價格的相關性
print(data['power'].corr(data['price'], method='spearman'))
print(data['kilometer'].corr(data['price'], method='spearman'))
print(data['brand_amount'].corr(data['price'], method='spearman'))
print(data['brand_price_average'].corr(data['price'], method='spearman'))
print(data['brand_price_max'].corr(data['price'], method='spearman'))
print(data['brand_price_median'].corr(data['price'], method='spearman'))

輸出
0.572828519605
-0.408256970162
0.0581566100256
0.383490957606
0.259066833881
0.386910423934

也可輸出圖

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)

在這裏插入圖片描述

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