二手車交易價格預測——特徵工程(2) 生成適用於LR的數據

針對於LR,NN模型的數據處理

針對生成樹的數據處理鏈接https://editor.csdn.net/md?articleId=105156784

導入數據

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

%matplotlib inline
Train_data= pd.read_csv(r'D:\ershouche\used_car_train_20200313.csv', sep=' ')
Test_data = pd.read_csv(r'D:\ershouche\used_car_testA_20200313.csv', sep=' ')
Train_data['notRepairedDamage'].replace('-', np.nan, inplace=True)
Train_data['notRepairedDamage'].value_counts()
0.0    111361
1.0     14315
Name: notRepairedDamage, dtype: int64
#對偏斜類做刪除處理
del Train_data["seller"]
del Train_data["offerType"]
del Test_data["seller"]
del Test_data["offerType"]
Train_data.isnull().sum()
SaleID                   0
name                     0
regDate                  0
model                    1
brand                    0
bodyType              4506
fuelType              8680
gearbox               5981
power                    0
kilometer                0
notRepairedDamage    24324
regionCode               0
creatDate                0
price                    0
v_0                      0
v_1                      0
v_2                      0
v_3                      0
v_4                      0
v_5                      0
v_6                      0
v_7                      0
v_8                      0
v_9                      0
v_10                     0
v_11                     0
v_12                     0
v_13                     0
v_14                     0
dtype: int64

1.缺失值的處理

由EDA數據分析,我們可以清楚的知道在我們的缺失數據的變量爲bodyType 缺失數爲4506 ,fuelType 缺失數爲8680;gearbox 缺失數爲5981,notRepairedDamage 有24324 種,且其均爲分類變量,而gearbox,notRepairedDamage 變量爲0,1分類,所以我們可以採取虛擬變量法填補缺失值,同時,在bodytype 和fuelType 變量,其雖爲分類變量,但其類別較多,不太適合虛擬變量法,又其含有缺失值的個數分別佔全部全部訓練集的3.00%,5.787%,所以對於這兩個變量我們可以直接採取刪除缺失值的方法。

1.1虛擬變量法處理缺失值

 虛擬變量

 又稱啞變量,通常取值爲0或1。引入啞變量可以使問題描述更加簡明。

pd.get_dummies( ) 

 參數column:欲轉換爲虛擬變量的指標。

 參數prefix:定義列名稱bb
#添加虛擬變量
# 將gearbox轉化爲虛擬變量,添加在Train_data的最後一列
train = pd.get_dummies(Train_data,columns=['gearbox'],
               prefix=['gearbox1'],prefix_sep='_')
 
train['gearbox1'] = Train_data['gearbox']
 
print(train.isnull().sum())
SaleID                   0
name                     0
regDate                  0
model                    1
brand                    0
bodyType              4506
fuelType              8680
power                    0
kilometer                0
notRepairedDamage    24324
regionCode               0
creatDate                0
price                    0
v_0                      0
v_1                      0
v_2                      0
v_3                      0
v_4                      0
v_5                      0
v_6                      0
v_7                      0
v_8                      0
v_9                      0
v_10                     0
v_11                     0
v_12                     0
v_13                     0
v_14                     0
gearbox1_0.0             0
gearbox1_1.0             0
gearbox1              5981
dtype: int64
train1 = pd.get_dummies(train,columns=['notRepairedDamage'],
               prefix=['notRepairedDamage'],prefix_sep='_')
 
train1['notRepairedDamage'] = train['notRepairedDamage']
 
print(train1.isnull().sum())
SaleID                       0
name                         0
regDate                      0
model                        1
brand                        0
bodyType                  4506
fuelType                  8680
power                        0
kilometer                    0
regionCode                   0
creatDate                    0
price                        0
v_0                          0
v_1                          0
v_2                          0
v_3                          0
v_4                          0
v_5                          0
v_6                          0
v_7                          0
v_8                          0
v_9                          0
v_10                         0
v_11                         0
v_12                         0
v_13                         0
v_14                         0
gearbox1_0.0                 0
gearbox1_1.0                 0
gearbox1                  5981
notRepairedDamage_0.0        0
notRepairedDamage_1.0        0
notRepairedDamage        24324
dtype: int64
del train1['gearbox1']
del train1['notRepairedDamage']

1.2刪除缺失值

train2=train1.dropna()
train2.isnull().sum()
SaleID                   0
name                     0
regDate                  0
model                    0
brand                    0
bodyType                 0
fuelType                 0
power                    0
kilometer                0
regionCode               0
creatDate                0
price                    0
v_0                      0
v_1                      0
v_2                      0
v_3                      0
v_4                      0
v_5                      0
v_6                      0
v_7                      0
v_8                      0
v_9                      0
v_10                     0
v_11                     0
v_12                     0
v_13                     0
v_14                     0
gearbox1_0.0             0
gearbox1_1.0             0
notRepairedDamage_0.0    0
notRepairedDamage_1.0    0
dtype: int64

由此,我們可以看到我們已經解決了缺失值的問題。

2.異常值處理

對於上述的,tree數據的生成,我們對power變量做了箱線圖異常值處理,但對於箱線圖處理,但對於數據不符合正態分佈的情況下,箱線圖處理,並不完美,而多選擇box_cox將非正態分佈數據轉換爲正態分佈,或者選擇長尾截斷的方法。
1.3δ原則,箱線圖參考https://blog.csdn.net/xzfreewind/article/details/77014587?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
2. box—cox 變換參考: https://blog.csdn.net/u012735708/article/details/84755595
3.

train2['power'].plot.hist()
<matplotlib.axes._subplots.AxesSubplot at 0x1099c2c8>

在這裏插入圖片描述

2.1 box_cos 變換

 如果數據不滿足正態時該怎麼辦,答案就是將非正態數據通過Box-Cox變換進一步轉換成符合正態分佈的數據。

Box-Cox變換是多種變換的總稱,具體的公式如下:
在這裏插入圖片描述

上面公式中y(λ)表示變換後的值,根據λ的值不同,屬於不同的變換,當λ值取以下特定的幾個值時就變成了特殊的數據變換:

當λ=0時,Box-Cox變換就變成了對數變換,y(λ) = ln(y);
當λ=0.5時,Box-Cox變換就變成了平方根變換,y(λ) = y^1/2
當λ=1時,Box-Cox變換變換就是它本身,y(λ) = y
當λ=2時,Box-Cox變換就變成了平方變化,y(λ) = y^2
當λ=-1時,Box-Cox變換就變成了倒數變化,y(λ) = 1/y。

λ值取多少,我們可以利用Python中現成的函數,讓函數自動去探索,然後返回給我們最優的值是多少就可以。

我們以price爲例進行分析,並不是所有的數據都適合做boxcox分析,我有提前用power進行分析,因爲power中的數據不全爲正數,所以,其會報錯: ValueError: Data must be positive. 所以這裏我們以之前在數據分析中顯示成右偏分佈的price爲例。當然也可以用for循環對多個變量進行變換。

1.我們先看一下數據的概率密度圖

sns.distplot(Train_data["price"],color = "#D86457")

在這裏插入圖片描述

通過上面的概率密度圖,我們可以看出這是一份偏態數據,也就是非正態。接下來我們先利用boxcox_normmax函數來尋找最優λ值,代碼如下:

from scipy import stats
stats.boxcox_normmax(Train_data["price"])

在這裏插入圖片描述

在獲得最優λ值以後,我們再利用boxcox函數來進行數據轉換,具體代碼如下:

x = stats.boxcox(Train_data["price"],stats.boxcox_normmax(Train_data["price"]))
sns.distplot(x,color = "#D86457")

在這裏插入圖片描述

對轉換後的數據再次進行概率密度圖的繪製,我們可以看到,數據就很正態了。

接下來再來我們再看一下我們計算出來的λ值是不是最優的,具體代碼如下:

fig = plt.figure()
ax = fig.add_subplot(111)
stats.boxcox_normplot(Train_data["kilometer"], -20, 20,plot = ax)
plt.axvline(x = stats.boxcox_normmax(Train_data["kilometer"]),color = "#D86457")
plt.show()

在這裏插入圖片描述

中間紅色那條線的位置就是我們求出來最優的λ值,結果很吻合。

# box_cos 變換
"""from scipy import stats,special

y = train2['power']
print(y.shape)
 
lam_range = np.linspace(0,310,100)  # default nums=50
llf = np.zeros(lam_range.shape, dtype=float)
 
# lambda estimate:
for i,lam in enumerate(lam_range):
    llf[i] = stats.boxcox_llf(lam, y)		# y 必須>0
 
# find the max lgo-likelihood(llf) index and decide the lambda
lam_best = lam_range[llf.argmax()]
print('Suitable lam is: ',round(lam_best,2))
print('Max llf is: ', round(llf.max(),2))
 
plt.figure()
plt.axvline(round(lam_best,2),ls="--",color="r")
plt.plot(lam_range,llf)
plt.show()
plt.savefig('boxcox.jpg')
 
# boxcox convert:
print('before convert: ','\n', y.head())
#y_boxcox = stats.boxcox(y, lam_best)
y_boxcox = special.boxcox1p(y, lam_best)
print('after convert: ','\n',  pd.DataFrame(y_boxcox).head())
 
# inverse boxcox convert:
y_invboxcox = special.inv_boxcox1p(y_boxcox, lam_best)
print('after inverse: ', '\n', pd.DataFrame(y_invboxcox).head())"""
 


2.2 長尾截斷

    #power分桶處理
bin = [i*10 for i in range(31)]
train2['power_bin'] = pd.cut(train2['power'], bin, labels=False)
train2[['power_bin', 'power']].head()
print('power變量中31個桶中的個數:')
pd.value_counts(train2['power_bin'])
power變量中31個桶中的個數:


c:\users\administrator\appdata\local\programs\python\python37\lib\site-packages\ipykernel_launcher.py:3: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  This is separate from the ipykernel package so we can avoid doing imports until





10.0    17222
7.0     12373
13.0    11572
5.0     10565
14.0    10121
11.0     9251
8.0      8416
16.0     7851
12.0     6516
6.0      5264
17.0     3897
19.0     3323
4.0      3067
9.0      2975
23.0     2331
18.0     2296
15.0     2073
21.0     1945
20.0     1436
22.0     1327
24.0     1010
27.0      759
25.0      424
28.0      409
26.0      384
29.0      363
3.0       207
0.0        72
1.0        31
2.0        14
Name: power_bin, dtype: int64

train2['power_bin'].plot.hist()

<matplotlib.axes._subplots.AxesSubplot at 0x122dbfc8>

在這裏插入圖片描述

#對分桶之後的power進行長尾截斷
train2.ix[train2['power_bin']>25,'power_bin'] = 25
c:\users\administrator\appdata\local\programs\python\python37\lib\site-packages\ipykernel_launcher.py:1: FutureWarning: 
.ix is deprecated. Please use
.loc for label based indexing or
.iloc for positional indexing

See the documentation here:
http://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#ix-indexer-is-deprecated
  """Entry point for launching an IPython kernel.
train2['power_bin'].plot.hist()
<matplotlib.axes._subplots.AxesSubplot at 0x10c36588>

在這裏插入圖片描述

3.特徵構造

#訓練集和測試集放在一起,方便構造 _——
train2['train']=1
Test_data['train']=0
data = pd.concat([train2,Test_data],ignore_index=True)

3.1時間特徵構造

#找使用時間=creatDate-regDate,數據中出錯歌神,用error ='coerce'
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()
10500

3.2地理信息特徵構造

# 從郵編中提取城市信息,相當於加入了先驗知識
data['city']=data['regionCode'].apply(lambda x : str(x)[:-3]) #lambda函數也叫匿名函數,即沒有具體名稱的函數,它允許快速定義單行函數,可以用在任何需要函數的地方
data=data

3.3 統計量特徵構造

## 計算某品牌的銷售統計量,同學們還可以計算其他特徵的統計量
# 這裏要以 train 的數據計算統計量
Train_gb=train2.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_prince_max']=kind_data.price.max()
    info['brand_prince_min']=kind_data.price.min()
    info['brand_prince_median']=kind_data.price.median()
    info['brand_prince_sum']=kind_data.price.sum()
    info['brand_prince_std']=kind_data.price.std()
    info['brand_prince_averge']=round(kind_data.price.sum()/(len(kind_data)+1),2)  #這個地方爲什麼要加1呢
    all_info[kind] =info
brand_fe = pd.DataFrame(all_info).T.reset_index().rename(columns={'index': 'brand'})
train2= data.merge(brand_fe,how = 'left',on='brand')
data.columns
Index(['SaleID', 'bodyType', 'brand', 'fuelType', 'gearbox', 'gearbox1_0.0',
       'gearbox1_1.0', 'kilometer', 'model', 'name', 'notRepairedDamage',
       'notRepairedDamage_0.0', 'notRepairedDamage_1.0', 'power', 'power_bin',
       'price', 'train', 'v_0', 'v_1', 'v_10', 'v_11', 'v_12', 'v_13', 'v_14',
       'v_2', 'v_3', 'v_4', 'v_5', 'v_6', 'v_7', 'v_8', 'v_9', 'used_time',
       'city', 'brand_amount_x', 'brand_prince_max_x', 'brand_prince_min_x',
       'brand_prince_median_x', 'brand_prince_sum_x', 'brand_prince_std_x',
       'brand_prince_averge_x', 'brand_amount_y', 'brand_prince_max_y',
       'brand_prince_min_y', 'brand_prince_median_y', 'brand_prince_sum_y',
       'brand_prince_std_y', 'brand_prince_averge_y'],
      dtype='object')
# 刪除不需要的數據
data = data.drop(['creatDate', 'regDate', 'regionCode'], axis=1)

4.特徵歸一化

4.1.Rescaling(最小最大歸一化,不免疫outlier)

(1)將訓練集中某一列數值特徵(假設是第i列)的值縮放到0和1之間
   x'= x - np.min(x)) / (np.max(x) - np.min(x))
   
 適用場景:
 如果對輸出結果範圍有要求,用歸一化
 如果數據較爲穩定,不存在極端的最大最小值,用歸一化 

我們對數值型數據(除去匿名特徵),以及構造的brand的統計量特徵做歸一化處理。

train2['kilometer'].plot.hist()
<matplotlib.axes._subplots.AxesSubplot at 0x22f42108>

在這裏插入圖片描述

train2['kilometer']=((train2['kilometer']-np.min(train2['kilometer']))/(np.max(train2['kilometer'])-np.min(train2['kilometer'])))
train2['kilometer'].plot.hist()

在這裏插入圖片描述

dt=['power_bin', 'brand_prince_max', 'brand_prince_min', 'brand_prince_median',
       'brand_prince_sum', 'brand_prince_std', 'brand_prince_averge','brand_amount']
print('聯合歸一化直方圖')
for ct in dt:
   
    train2[ct]=((train2[ct]-np.min(train2[ct]))/(np.max(train2[ct])-np.min(train2[ct])))
    
    train2[ct].plot.hist()
    
聯合歸一化直方圖

在這裏插入圖片描述

4.2Standardization(標準化/z-score標準化,不免疫outlier)

 將訓練集中某一列數值特徵(假設是第i列)的值縮放成均值爲0,方差爲1的狀態。
 x'=(x-x均值)/ 標準差

 適用場景:
 SVM、LR、神經網絡
 如果數據存在異常值和較多噪音,用標準化,可以間接通過中心化避免異常值和極端值的影響
 
 中的來講3.2的方法更爲優秀。
# 方法1
from sklearn.proprocessing import scale
df_train['feature'] = scale(df_train['feature'])
# 方法2
# 一般會把train和test集放在一起做標準化,或者在train集上做標準化後,用同樣的標準化器去標準化test集,此時可以用scaler
from sklearn.proprocessing import StandardScaler
scaler = StandardScaler().fit(df_train)
scaler.transform(df_train)
scaler.transform(df_test)

% 如果對輸出結果範圍有要求,用歸一化
%如果數據較爲穩定,不存在極端的最大最小值,用歸一化
%如果數據存在異常值和較多噪音,用標準化,可以間接通過中心化避免異常值和極端值的影響
我比較建議先使用標準化。

5. one hot 獨熱編碼

    1.one-hot

如果類別特徵本身有順序(例:優秀、良好、合格、不合格),那麼可以保留單列自然數編碼。如果類別特徵沒有明顯的順序(例:紅、黃、藍),則可以使用one-hot編碼:
作用:將類別變量轉換爲機器學習算法容易處理的形式
爲什麼one-hot編碼可以用來處理非連續(離散)特徵?
在使用one-hot編碼中,我們可以將離散特徵的取值擴展到歐式空間,在機器學習中,我們的研究範圍就是在歐式空間中,首先這一步,保證了能夠適用於機器學習中;另外對於one-hot處理的離散的特徵的某個取值也就對應了歐式空間的某個點.

# 對類別特徵進行 OneEncoder
data = pd.get_dummies(data, columns=['model', 'brand', 'bodyType', 'fuelType',
                                     'gearbox', 'notRepairedDamage'])

data
SaleID gearbox1_0.0 gearbox1_1.0 kilometer name notRepairedDamage_0.0 notRepairedDamage_1.0 power power_bin price ... fuelType_2.0 fuelType_3.0 fuelType_4.0 fuelType_5.0 fuelType_6.0 gearbox_0.0 gearbox_1.0 notRepairedDamage_- notRepairedDamage_0.0 notRepairedDamage_1.0
0 0 1.0 0.0 0.827586 736 1.0 0.0 60 5.0 1850.0 ... 0 0 0 0 0 0 0 0 0 0
1 1 1.0 0.0 1.000000 2262 0.0 0.0 0 NaN 3600.0 ... 0 0 0 0 0 0 0 0 0 0
2 2 1.0 0.0 0.827586 14874 1.0 0.0 163 16.0 6222.0 ... 0 0 0 0 0 0 0 0 0 0
3 3 0.0 1.0 1.000000 71865 1.0 0.0 193 19.0 2400.0 ... 0 0 0 0 0 0 0 0 0 0
4 4 1.0 0.0 0.310345 111080 1.0 0.0 68 6.0 5200.0 ... 0 0 0 0 0 0 0 0 0 0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
189232 199995 NaN NaN 15.000000 20903 NaN NaN 116 NaN NaN ... 0 0 0 0 0 1 0 0 1 0
189233 199996 NaN NaN 15.000000 708 NaN NaN 75 NaN NaN ... 0 0 0 0 0 1 0 0 1 0
189234 199997 NaN NaN 15.000000 6693 NaN NaN 224 NaN NaN ... 0 0 0 0 0 0 1 0 1 0
189235 199998 NaN NaN 15.000000 96900 NaN NaN 334 NaN NaN ... 0 0 0 0 0 0 1 0 1 0
189236 199999 NaN NaN 9.000000 193384 NaN NaN 68 NaN NaN ... 0 0 0 0 0 0 1 0 1 0

189237 rows × 349 columns

我們可以看到所有的分類數劇進行了自編碼

data.isnull().sum()
SaleID                       0
gearbox1_0.0             50000
gearbox1_1.0             50000
kilometer                    0
name                         0
                         ...  
gearbox_0.0                  0
gearbox_1.0                  0
notRepairedDamage_-          0
notRepairedDamage_0.0        0
notRepairedDamage_1.0        0
Length: 349, dtype: int64
# 這份數據可以給 LR 用
data.to_csv('D:/ershouche/data_for_lr.csv', index=0)

總結

對於線性模型的構造,我們是在除去其缺失值的基礎上,進行了,忽略對匿名特徵的的處理。
首先,我們針對非正態分佈數值型數據,進行異常值刪除的過程,選擇了長尾截斷,當然box-cox轉換也是很好的處理方法,本文已經給到了相應的代碼與原理。
其次,我們對於數據中含有的數值型數據做了歸一化處理,在線性問題處理過程中歸一化後加快了梯度下降求最優解的速度且歸一化有可能提高精度。本文給到了歸一化處理,標準化的方法,當然最推薦的還是3.2裏面的z-score標準化,其不用考慮極大值極小值的影響,適用範圍廣,性能更好一些。
https://www.zhihu.com/question/20455227/answer/370658612
最後我們選擇對分類型數據進行獨熱編碼處理其將多類別數據二值化,更有利於對之後模型的建立。下面是關於自編碼的相關內容,可藉以參考。

參考文獻

1.如何將非正態分佈的數據轉爲正態分佈? —boxcox

2.獨熱編碼

3.歸一化標準化處理

4.特徵工程:歸一化處理

5.天池賽題

6.數據預處理方法

7.機器學習筆記(1)-分析框架-以Kaggle Titanic問題爲例

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