house-prices

一、探索性數據分析

  在這一部分主要是對訓練集的81個features進行了分析,以對數據有一個初步的瞭解,方便後續進行特徵工程和模型的建立。在進行數據探索之前,先了解一下訓練集和測試集的樣本量,訓練集的shape爲(1460, 81) ,測試集的shape爲(1459, 80),測試集比訓練集少了“SalePrice”目標值這一列;此外也統計了訓練數據的類別和數值特徵,代碼如下所示:

train_dty = train.dtypes
train_obj_names = list(train_dty[train_dty=='object'].index)    # 43
train_int_names = list(train_dty[train_dty=='int64'].index)    # 35   
train_flt_names = list(train_dty[train_dty=='float64'].index)   # 3

  雖然features中的“Id”與目標值“SalePrice”的type是數值型,但是“Id”是唯一標識,“SalePrice”是目標值,所以將它們從features中刪除。
  接下來正式進入數據探索性分析,瞭解數據的缺失情況、連續及類別型特徵的數據分佈情況以及各個特徵之間的相關性等。

1 查看缺失值

  查看訓練集的缺失值情況發現共有19個特徵存在缺失值,其中有5個特徵“FireplaceQu”、“Fence”、“Alley”、“MiscFeature”、“PoolQC”缺失值量大於50%,按照從小到大排序如圖1所示:

圖1 缺失值的直方圖

  針對這種情況,考慮在特徵工程中將缺失值較多的特徵刪除,其餘的含有缺失值的特徵可以採用衆數或均值等方式進行填充。此外,通過圖1還可以發現“GarageCond”、“GarageQual”、“GarageFinish”、“GarageType”這四個指標的缺失值都是81,而且都是描述車庫信息的,在特徵工程中可以考慮將這四個指標結合起來構造新的特徵。

2 查看目標值的類型及分佈
  瞭解了數據特徵的缺失值狀態之後,接下來分析目標值的類型疾風步。首先,通過head發現目標值“SalePrice”是連續型變量,說明要解決的問題是迴歸類型的問題,進一步分析其分佈狀態,如圖2、圖3、圖4所示,可以看出SalePrice的分佈與Johnsonsu分佈的擬合度最高,同時很明顯的可以看出SalePrice不服從正態分佈,在圖4中與SalePrice的對數後的正態分佈擬合較好,所以認爲取對數後的SalePrice服從正態分佈,即考慮在後面將SalePrice對數化。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-GXjTL6cY-1572442046698)(./images/02.png)]

圖2 SalePrice的分佈與Johnsonsu分佈對比

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-YBvi9aEq-1572442046700)(./images/03.png)]

圖3 SalePrice的分佈與正態分佈對比

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-iwNApf0K-1572442046701)(./images/04.png)]

圖4 SalePrice的分佈與對數正態分佈對比
3 檢驗連續型特徵分佈是否服從正態性分佈
  類似的,檢驗連續型變量是否服從正態分佈,根據之前提取出的連續型變量用如下代碼進行正態性檢驗,結果得到False表示,沒有一個連續型變量的分佈服從正態分佈;再進一步畫出這些連續型變量的分佈圖,如圖5所示,許多變量的分佈不規則,考慮在後面對其進行對數變換。   
	test_normality = lambda x: stats.shapiro(x.fillna(0))[1] < 0.01
	## shapirpo專門做正態檢驗的模塊,當樣本數大於時,不再適合用其做正態分佈,得到的p值可能不準確
	normal = pd.DataFrame(train[list(train_int_names+train_flt_names)])
	normal = normal.apply(test_normality)  # normal每列都爲true,說明每個都拒絕了正態分佈的假設
	print(not normal.any())

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-faj7I7X1-1572442985363)(./images/05.png)]

圖5 連續型特徵的分佈圖
4 對類變形變量進行初步探索
  對類別型變量進行探索使用了兩種方式,第一種,先畫箱線圖初步分析一下每一個類別型特徵類間的區分度,如下圖6所示,可以看到幾乎每個特徵下的類別間並不完全相同,初步認爲類間存在差異。第二種,通過單因素方差分析來分析類間差距,將得到的p值取倒數再取對數(這樣做可以使越小的值變得越大,方便後面畫圖),再按照大小畫圖如圖7所示,可以看出“Neighborhood” 的類間差距最大,依次類推,而且這些類別特徵用單因素方差分析,其p值均小於0.05,可以認爲這些類別特徵對“SalePrice” 的影響是顯著的。   其次,對類別型變量進行one-hot編碼,對每一個特徵的類別先計數,再根據計數的大小排序,數目最多的類別編碼爲1,第二多的編碼爲2,以此往後類推,爲了區分編碼後的特徵,在特徵名後面加上“_E”。   最後,經過上面一些列的數據探索分析,瞭解了數據的缺失值情況,目標值、連續型、離散型特徵的分佈等情況,接下來正式進入特徵工程,爲建模做準備。

二、特徵工程

1 數據預處理
01 構造訓練集、測試集的features及label
  第一步,先將train、test中的“Id”刪掉,因爲“Id”是具有唯一標識的特徵,對研究“SalePrice”來說沒有實際意義,所以將其刪除;第二步,將train數據集中的“SalePrice”進行對數變換,將其賦值給y,進行對數變換可以使其近似服從正態分佈,因爲一些模型如線性模型、SVM等要求目標變量服從正態分佈,然後將“SalePrice”這一特徵從train中刪除,此時train、test的特徵數都是79個,所以可以將其合併爲一張表格一起進行接下來的處理。
# 刪除無關的特徵列Id
train.drop(['Id'], axis=1, inplace=True)
test.drop('Id', axis=1, inplace=True)
# 將“SalePrice”對數化,並賦值給y
train.reset_index(drop=True, inplace=True)
train['SalePrice'] = np.log1p(train['SalePrice'])
y = train['SalePrice'].reset_index(drop=True)
train_features = train.drop(['SalePrice'], axis=1)
# 將train與test數據集合並
test_features = test
features = pd.concat([train_features, test_features]).reset_index(drop=True)
02 缺失值處理
  對類別型特徵的缺失值補“None”,對連續型特徵的缺失值補0。
03 連續型特徵的偏度處理
  計算出連續型特徵的偏度係數,挑選出偏度係數大於0.5的特徵,對其進行boxcox變換,從而改變數據的正態性。
numerics_dtypes = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64']
numerics2 = []
for i in features.columns:
    if features[i].dtype in numerics2:
        numerics2.append(i)
skew_features = features[numerics].apply(lambda x: skew(x)).sort_values(ascending=False)    # 得到含有各個特徵的偏度係數表
# 挑選出偏度係數大於0.5的特徵,對其進行boxcox變換
high_skew = skew_features[skew_features>0.5]
skew_index = high_skew.index
for i in skew_index:
    features[i] = boxcox1p(features[i], boxcox_normmax(features[i]+1))
2 特徵構造
  基於特徵所表達含義,可以利用連續型和離散型特徵構造一些簡單的特徵,以進一步挖掘數據的潛藏特徵。   第一步,構造數值型特徵,如將房屋的地下室面積、一層面積、二層面積加起來就可以得到房屋的總面積等,具體代碼如下所示:
 # 構造數值型變量,主要是針對數值型特徵進行了處理
features['YrBltAndRemod'] = features['YearBuilt'] + features['YearRemodAdd']    # 兩個年份加起來
features['TotalSF'] = features['TotalBsmtSF'] + features['1stFlrSF'] + features['2ndFlrSF']    # 得到房屋的總面積: 地下室+一層+二層
features['Total_sqr_footage'] = features['BsmtFinSF1'] + features['BsmtFinSF2'] + features['1stFlrSF'] + features['2ndFlrSF']
features['Total_Bathrooms'] = features['BsmtFullBath'] + features['BsmtHalfBath'] + features['FullBath'] + features['HalfBath']    # 總的浴室個數
features['Total_porch_sf'] = features['WoodDeckSF'] + features['OpenPorchSF'] + features['EnclosedPorch'] + \
            features['3SsnPorch'] + features['ScreenPorch']    # 總的門廊面積
  第二步,構造離散型特徵,在這裏主要是基於某一個離散特徵,判斷該住宅是否含有這一目標,如“PoolArea”,如果有“PoolArea”,則對“hasPool”賦值爲1,否則爲0,其餘的類似,詳見下面的代碼:
features['hasPool'] = features['PoolArea'].apply(lambda x:1 if x>0 else 0)    # 一共就只有12家有游泳池
features['has2ndFloor'] = features['2ndFlrSF'].apply(lambda x:1 if x>0 else 0)  # 有1249戶有二層
features['hasGarage'] = features['GarageArea'].apply(lambda x: 1 if x>0 else 0) # 有2759戶有車庫,158戶沒有
features['hasFireplace'] = features['Fireplaces'].apply(lambda x: 1 if x>0 else 0)# 有1497戶有壁爐,1420戶沒有
features['hasBsmt'] = features['TotalBsmtSF'].apply(lambda x: 1 if x>0 else 0)  # 有2838戶有地下室,79戶沒有
  第三步,在進行最終的建模前,刪除類別型特徵中某一類別佔比超過99.94%的特徵,代碼如下所示,通過這種方式一共刪除了5+3個對y區分佈不大的特特徵。
X = final_features.iloc[:len(y), :]           # 拆分出訓練集
X_sub = final_features.iloc[len(y):, :]       # 拆分出測試集 
# 用這種方式刪除一些特徵,如果該特徵的value_counts[0]的值佔比超過99.94%, 則刪除
overfit = []
for i in X.columns:
    counts = X[i].value_counts()
    zeros = counts.iloc[0]    # 取第0行
    if zeros/len(X)>0.9994:
        overfit.append(i)	
X.drop(overfit, axis=1, inplace=True)
X_sub.drop(overfit, axis=1, inplace=True)

三、建立模型

  在這一部分,會應用多個模型,有嶺迴歸模型、lasso模型、彈性網絡模型、支持向量迴歸模型、GBR、lightgbm、xgboost模型;然後再用xgboost作爲次級分類器以stacking的方式進行模型融合,這樣也得到一個模型。此時一共有8個模型,然後基於這8個模型分別對測試集進行預測,最後再將預測結果按照不同的權重結合起來,進而得出最終的預測結果(將8個模型預測結果結合起來的函數如下所示)。
def blend_model_predict(X):
    return (0.1*elasticnet_model.predict(X)) + (0.05*lasso_model.predict(X)) + \
           (0.1*ridge_model.predict(X)) + (0.1*svr_model.predict(X)) + \
           (0.1*gbr_model.predict(X)) + (0.15*xgboost_model.predict(X)) + \
           (0.1*lgbm_model.predict(X)) + (0.3*stack_gen_model.predict(np.array(X)))

在融合過程中,各個分類器的rmse如表1所示,可以看出融合後的模型效果要優於單個模型的rmse,表明模型融合是有效的。

表1 各模型的rmse
模型 rmse
ridge 0.1096
lasso 0.1092
elastic net 0.1092
SVR 0.1091
gbr 0.1135
lightgbm 0.1148
xgboost 0.1130
stacking 0.0597
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章