Kaggle競賽入門教程案例

Kaggle房價預測全流程詳解

對於剛剛入門機器學習的童孩來講,如何快速地經過不一樣實戰演練以提升代碼能力和流程理解是一個須要關注的問題。Kaggle平臺正好提供了數據科學家的所須要的交流環境,而且爲癡迷於人工智能的狂熱的愛好者舉辦了各類類型的競賽(如,數據科學/圖像分類/圖像識別/天然語言處理/漏洞檢測)。html

Kaggle社區是一種全球性的交流社區,集中大量優秀的AI科學家和數據分析家,可以相互分享實戰經驗和代碼,而且有基礎入門教程,對新手很是友好~python

競賽連接與背景介紹

在這裏插入圖片描述

  1. Kaggle平臺官網:https://www.kaggle.com
  2. 房價預測競賽網址: https://www.kaggle.com/c/house-prices-advanced-regression-techniques

房價是一個生活中耳熟能詳的概念,在大城市買房尤爲成爲了上班族幾乎最大的苦惱(之後即將面臨····),而在美國的愛荷華州埃姆斯市有許多因素影響着房屋的最終價格,例如房屋面積、地下室、浴室和車庫等等;web

kaggle平臺收集了約80個可能影響房價的特徵變量,要求數據科學家利用機器學習等工具對房價進行預測,即該案例是一種簡單的迴歸問題。算法

官方提供的房屋特徵描述文件我已翻譯成中文,供你們參考。英文原版的能夠點擊Kaggle競賽欄目下的下載按鈕,數據集也是同樣。以下所示:app

  • SalePrice: 房產銷售價格,以美圓計價。所要預測的目標變量
  • MSSubClass: Identifies the type of dwelling involved in the sale 住所類型
  • MSZoning: The general zoning classification 區域分類
  • LotFrontage: Linear feet of street connected to property 房子同街道之間的距離
  • LotArea: Lot size in square feet 建築面積
  • Street: Type of road access 主路的路面類型
  • Alley: Type of alley access 小道的路面類型
  • LotShape: General shape of property 房屋外形
  • LandContour: Flatness of the property 平整度
  • Utilities: Type of utilities available 配套公用設施類型
  • LotConfig: Lot configuration 配置
  • LandSlope: Slope of property 土地坡度
  • Neighborhood: Physical locations within Ames city limits 房屋在埃姆斯市的位置
  • Condition1: Proximity to main road or railroad 附近交通狀況
  • Condition2: Proximity to main road or railroad (if a second is present) 附近交通狀況(若是同時知足兩種狀況)
  • BldgType: Type of dwelling 住宅類型
  • HouseStyle: Style of dwelling 房屋的層數
  • OverallQual: Overall material and finish quality 完工質量和材料
  • OverallCond: Overall condition rating 總體條件等級
  • YearBuilt: Original construction date 建造年份
  • YearRemodAdd: Remodel date 翻修年份
  • RoofStyle: Type of roof 屋頂類型
  • RoofMatl: Roof material 屋頂材料
  • Exterior1st: Exterior covering on house 外立面材料
  • Exterior2nd: Exterior covering on house (if more than one material) 外立面材料2
  • MasVnrType: Masonry veneer type 裝飾石材類型
  • MasVnrArea: Masonry veneer area in square feet 裝飾石材面積
  • ExterQual: Exterior material quality 外立面材料質量
  • ExterCond: Present condition of the material on the exterior 外立面材料外觀狀況
  • Foundation: Type of foundation 房屋結構類型
  • BsmtQual: Height of the basement 評估地下室層高狀況
  • BsmtCond: General condition of the basement 地下室整體狀況
  • BsmtExposure: Walkout or garden level basement walls 地下室出口或者花園層的牆面
  • BsmtFinType1: Quality of basement finished area 地下室區域質量
  • BsmtFinSF1: Type 1 finished square feet Type 1完工面積
  • BsmtFinType2: Quality of second finished area (if present) 二次完工面積質量(若是有)
  • BsmtFinSF2: Type 2 finished square feet Type 2完工面積
  • BsmtUnfSF: Unfinished square feet of basement area 地下室區域未完工面積
  • TotalBsmtSF: Total square feet of basement area 地下室整體面積
  • Heating: Type of heating 採暖類型
  • HeatingQC: Heating quality and condition 採暖質量和條件
  • CentralAir: Central air conditioning 中央空調系統
  • Electrical: Electrical system 電力系統
  • 1stFlrSF: First Floor square feet 第一層面積
  • 2ndFlrSF: Second floor square feet 第二層面積
  • LowQualFinSF: Low quality finished square feet (all floors) 低質量完工面積
  • GrLivArea: Above grade (ground) living area square feet 地面以上部分起居面積
  • BsmtFullBath: Basement full bathrooms 地下室全浴室數量
  • BsmtHalfBath: Basement half bathrooms 地下室半浴室數量
  • FullBath: Full bathrooms above grade 地面以上全浴室數量
  • HalfBath: Half baths above grade 地面以上半浴室數量
  • Bedroom: Number of bedrooms above basement level 地面以上臥室數量
  • KitchenAbvGr: Number of kitchens 廚房數量
  • KitchenQual: Kitchen quality 廚房質量
  • TotRmsAbvGrd: Total rooms above grade (does not include bathrooms) 總房間數(不含浴室和地下部分)
  • Functional: Home functionality rating 功能性評級
  • Fireplaces: Number of fireplaces 壁爐數量
  • FireplaceQu: Fireplace quality 壁爐質量
  • GarageType: Garage location 車庫位置
  • GarageYrBlt: Year garage was built 車庫建造時間
  • GarageFinish: Interior finish of the garage 車庫內飾
  • GarageCars: Size of garage in car capacity 車殼大小以停車數量表示
  • GarageArea: Size of garage in square feet 車庫面積
  • GarageQual: Garage quality 車庫質量
  • GarageCond: Garage condition 車庫條件
  • PavedDrive: Paved driveway 車道鋪砌狀況
  • WoodDeckSF: Wood deck area in square feet 實木地板面積
  • OpenPorchSF: Open porch area in square feet 開放式門廊面積
  • EnclosedPorch: Enclosed porch area in square feet 封閉式門廊面積
  • 3SsnPorch: Three season porch area in square feet 時令門廊面積
  • ScreenPorch: Screen porch area in square feet 屏風門廊面積
  • PoolArea: Pool area in square feet 游泳池面積
  • PoolQC: Pool quality 游泳池質量
  • Fence: Fence quality 圍欄質量
  • MiscFeature: Miscellaneous feature not covered in other categories 其它條件中未包含部分的特性
  • MiscVal: $Value of miscellaneous feature 雜項部分價值
  • MoSold: Month Sold 賣出月份
  • YrSold: Year Sold 賣出年份
  • SaleType: Type of sale 出售類型
  • SaleCondition: Condition of sale 出售條件

接下來的工做就是基於這些特徵進行數據挖掘和構建模型來預測了。總體流程的思路以下:機器學習

在這裏插入圖片描述

競賽代碼解析

導入工具包

import numpy as np    #基本矩陣計算工具
import pandas as pd   #基本數據可視化工具
import matplotlib.pyplot as plt  #繪圖工具
import seaborn as sns
from datetime import datetime   #記錄時間
from scipy.stats import skew  #偏度計算
from scipy.special import boxcox1p  #box-cox變換工具
from scipy.stats import boxcox_normmax   
from sklearn.linear_model import LinearRegression, ElasticNetCV, LassoCV, RidgeCV  #線性模型
from sklearn.ensemble import GradientBoostingRegressor   #GBDT模型
from sklearn.svm import SVR  #SVR模型
from sklearn.pipeline import make_pipeline  #構建Pipeline 
from sklearn.preprocessing import RobustScaler  #穩健標準化,用於縮放包含許多異常值的數據
from sklearn.model_selection import KFold, RepeatedKFold, cross_val_score, GridSearchCV  #K折取樣以及交叉驗證
from sklearn.metrics import mean_squared_error   #均方根指標
from mlxtend.regressor import StackingCVRegressor   #帶交叉驗證的Stacking迴歸器
from xgboost import XGBRegressor    #XGBoost模型
from lightgbm import LGBMRegressor  #LGB模型
import warnings  #系統警告提示
import os   #系統讀取工具
warnings.filterwarnings('ignore')  #忽略警告

數據加載

#文件根目錄,輸入本地下載好的文件目錄地址

DATA_ROOT = 'D:/Kaggle比賽/房價迴歸預測/'
print(os.listdir(DATA_ROOT))
['data_description.txt', 'House_price_submission.csv', 'sample_submission.csv', 'test.csv', 'test_results.csv', 'train.csv', '數據描述中文介紹.txt']
#導入訓練集、測試集和提交樣本
train = pd.read_csv(f'{DATA_ROOT}/train.csv')
test = pd.read_csv(f'{DATA_ROOT}/test.csv')
sub = pd.read_csv(f'{DATA_ROOT}/sample_submission.csv')
	
#打印數據維度
print("Train set size:", train.shape)
print("Test set size:", test.shape)
輸出結果: 
Train set size: (1460, 81) , Test set size: (1459, 80)
#查看訓練集數據摘要
print(train.info())
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1460 entries, 0 to 1459
Data columns (total 81 columns):
 # Column Non-Null Count Dtype 
---  ------         --------------  -----  
 0   Id             1460 non-null   int64  
 1   MSSubClass     1460 non-null   int64  
 2   MSZoning       1460 non-null   object 
 3   LotFrontage    1201 non-null   float64
 4   LotArea        1460 non-null   int64  
 5   Street         1460 non-null   object 
 6   Alley          91 non-null     object 
 7   LotShape       1460 non-null   object 
 8   LandContour    1460 non-null   object 
 9   Utilities      1460 non-null   object 
 10  LotConfig      1460 non-null   object 
 ......
#查看測試集數據摘要
print(test.info())
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1460 entries, 0 to 1459
Data columns (total 81 columns):
 # Column Non-Null Count Dtype 
---  ------         --------------  -----  
 0   Id             1460 non-null   int64  
 1   MSSubClass     1460 non-null   int64  
 2   MSZoning       1460 non-null   object 
 3   LotFrontage    1201 non-null   float64
 4   LotArea        1460 non-null   int64  
 5   Street         1460 non-null   object 
 6   Alley          91 non-null     object 
 7   LotShape       1460 non-null   object 
 8   LandContour    1460 non-null   object 
 9   Utilities      1460 non-null   object 
 10  LotConfig      1460 non-null   object 
 .....

經過簡單粗略看數據,咱們知道這裏有着數值型變量和非數值變量(類別型變量),除開ID和SalePrice之外共有79個特徵。ide

數據預處理

異常值初篩

#先將樣本ID賦值並刪除
train_ID = train['Id']
test_ID = test['Id']

train.drop(['Id'], axis=1, inplace=True)
test.drop(['Id'], axis=1, inplace=True)

#整理出數值型特徵和類別型特徵
all_cols = test.columns.tolist()
numerical_cols = []
categorical_cols = []

for col in all_cols:
    if (test[col].dtype != 'object') :
        numerical_cols.append(col)
    else:
        categorical_cols.append(col)

print('數值型變量數目爲:',len(numerical_cols))
print('類別型變量數目爲:',len(categorical_cols))
數值型變量數目爲: 36
類別型變量數目爲: 43
#對訓練集的連續性數值變量繪製箱型圖篩選異常值

fig = plt.figure(figsize=(80,60),dpi=120)
for i in range(len(numerical_cols)):
    plt.subplot(6, 6, i+1)
    sns.boxplot(train[numerical_cols[i]], orient='v', width=0.5)
    plt.ylabel(numerical_cols[i], fontsize=36)
plt.show()

在這裏插入圖片描述
查看具備較爲明顯異常值的特徵列:
函數

#地面上居住面積與房屋售價關係
fig = plt.figure(figsize=(6,5))
plt.axvline(x=4600, color='r', linestyle='--')
sns.scatterplot(x='GrLivArea',y='SalePrice',data=train, alpha=0.6)

在這裏插入圖片描述

#顯然對於可居住面積越大,其售價確定也越高,但圖中顯示有兩個離散點不遵循此規則,查看其具體的數值
train.GrLivArea.sort_values(ascending=False)[:4]
1298    5642
523     4676
1182    4476
691     4316
Name: GrLivArea, dtype: int64
#地皮建築面積與房屋售價關係
fig = plt.figure(figsize=(6,5))
plt.axvline(x=200000, color='r', linestyle='--')
sns.scatterplot(x='LotArea',y='SalePrice',data=train, alpha=0.6)
*強#地皮建築面積與房屋售價關係
fig = plt.figure(figsize=(6,5))
plt.axvline(x=200000, color='r', linestyle='--')
sns.scatterplot(x='LotArea',y='SalePrice',data=train, alpha=0.6)

在這裏插入圖片描述
(經過數據集中能看出,對於地皮建築面積越大,其售價卻不必定更高,兩者不成正比,所以異常值不用刪除)
工具

#地下室總面積與房屋售價關係
fig = plt.figure(figsize=(6,5))
plt.axvline(x=5900, color='r', linestyle='--')
sns.scatterplot(x='TotalBsmtSF',y='SalePrice',data=train, alpha=0.6)

在這裏插入圖片描述

#同上,查看其具體的數值
train.TotalBsmtSF.sort_values(ascending=False)[:3]
1298    6110
332     3206
496     3200
Name: TotalBsmtSF, dtype: int64
#第一層面積與房屋售價關係
fig = plt.figure(figsize=(6,5))
plt.axvline(x=4000, color='r', linestyle='--')
sns.scatterplot(x='1stFlrSF',y='SalePrice',data=train, alpha=0.6)

在這裏插入圖片描述

#同上,查看其具體的數值
train['1stFlrSF'].sort_values(ascending=False)[:3]
1298    4692
496     3228
523     3138
Name: 1stFlrSF, dtype: int64

你會發現原來這幾個特徵的離羣點都是Index=1298的這個樣本.性能

#裝飾石材面積與房屋售價關係
fig = plt.figure(figsize=(6,5))
plt.axvline(x=1500, color='r', linestyle='--')
sns.scatterplot(x='MasVnrArea',y='SalePrice',data=train, alpha=0.6)

在這裏插入圖片描述

經過數據集中能看出,對於裝飾石材面積越大,其售價卻不必定更高,還須要看石材的類型,所以異常值不用刪除。還有其他特徵變量能夠用來探索,具體方式是先看箱型圖,再細看可能會存在離羣值的一些特徵作散點圖,最最重要的就是不要過度地刪除異常值,必定要基於人爲經驗或者可觀事實判斷。好比,住房面積大房價卻很低,人的年齡超過200歲,月份數爲-1等等。

綜上,須要將部分異常值刪除。

#剔除異常值並將數據集從新排序
train = train[train.GrLivArea < 4600]
train = train[train.TotalBsmtSF < 5000]
train = train[train['1stFlrSF'] < 4000]
train.reset_index(drop=True, inplace=True)
train.shape
(1458, 80)

標籤值對數變換

先對我們的標籤(房價)作一下偏度圖,通常用直方圖和Q-Q圖來看。
不懂Q-Q圖的小夥伴能夠移步這裏~

#對'SalePrice'繪製直方圖和Q-Q圖
from scipy import stats
plt.figure(figsize=(10,5))
ax_121 = plt.subplot(1,2,1)
sns.distplot(train["SalePrice"],fit=stats.norm)
ax_122 = plt.subplot(1,2,2)
res = stats.probplot(train["SalePrice"],plot=plt)

在這裏插入圖片描述
可見,我們的房價分佈並不徹底符合正態,而是一種向左的偏態分佈。

因爲該競賽最終的評估指標是取房價對數的RMSE值,所以有必要先將房價轉化爲對數形式,方便後續用於模型的評估。(這裏能夠用numpy.log()或者numpy.log1p()將數值轉化爲對數。注意,log()是指e爲底數,而log1p表明了ln(1+x))

#使用log1p也就是log(1+x),用來對房價數據進行數據預處理,它的好處是轉化後的數據更加服從正態分佈,有利於後續的評估結果。
#但須要注意最後須要將預測出的平滑數據還原,而還原過程就是log1p的逆運算expm1
train["SalePrice"] = np.log1p(train["SalePrice"])
plt.figure(figsize=(10,5))
ax_121 = plt.subplot(1,2,1)
sns.distplot(train["SalePrice"],fit=stats.norm)
ax_122 = plt.subplot(1,2,2)
res = stats.probplot(train["SalePrice"],plot=plt)

在這裏插入圖片描述
如今,經過對數變換的偏態標籤是否是更符合正態分佈了呢~

接下來須要合併訓練和測試數據,作一些統一的預處理變化,若是分開作會顯得比較麻煩。

#分離標籤和特徵,合併訓練集和測試集便於統一預處理
y = train['SalePrice'].reset_index(drop=True)
train_features = train.drop(['SalePrice'], axis=1)
test_features = testfeatures = pd.concat([train_features, test_features],axis=0).reset_index(drop=True)
print("剔除訓練數據中的極端值後,將其特徵矩陣和測試數據中的特徵矩陣合併,維度爲:",features.shape)
剔除訓練數據中的極端值後,將其特徵矩陣和測試數據中的特徵矩陣合併,維度爲: (2917, 79)

明確變量類型

經過閱讀官方提供的說明文件(這一點很重要)可以加深對數據特徵的理解,以便更好的進行特徵處理。在這裏,咱們會發現有一些特徵自己是數值型的數據,可是卻沒有連續值,而是一些單一分佈的值,所以須要檢驗它們是否是本來就是類別型的數據,只不過用數值來表達了。

#尋找數值變量中實際應該爲類別變量的特徵(即並不連續分佈)
transform_cols = []
for col in numerical_cols:
    if len(features[col].unique()) < 20:
        transform_cols.append(col)
       
transform_cols
['MSSubClass',
 'OverallQual',
 'OverallCond',
 'BsmtFullBath',
 'BsmtHalfBath',
 'FullBath',
 'HalfBath',
 'BedroomAbvGr',
 'KitchenAbvGr',
 'TotRmsAbvGrd',
 'Fireplaces',
 'GarageCars',
 'PoolArea',
 'MoSold',
 'YrSold']

經過對比文件描述 (data_distribution) 中的特徵含義:

  • MSSubClass – 肯定銷售涉及的住宅類型(擁有16個不一樣類型,且互相無優劣關係,實質爲onehot類別型變量)
  • OverallQual – 評估房子的總體材料和裝修(擁有10個類型,且數值越低表示越差,實質爲labelcoder類別型變量)
  • OverallCond – 評估房子的總體情況(擁有10個類型,且數值越低表示越差,實質爲labelcoder類別型變量)
  • BsmtFullBath – 地下室全浴室個數(類型數未知,實質爲數值型變量)
  • BsmtHalfBath – 地下室半浴室個數(類型數未知,實質爲數值型變量)
  • FullBath – 地面上的全浴室個數(類型數未知,實質爲數值型變量)
  • HalfBath – 地面上的半浴室個數(類型數未知,實質爲數值型變量)
  • BedroomAbvGr – 地面上臥室個數(類型數未知,實質爲數值型變量)
  • KitchenAbvGr – 地面上廚房個數(類型數未知,實質爲數值型變量)
  • TotRmsAbvGrd – 地面上房間個數(類型數未知,實質爲數值型變量)
  • Fireplaces – 壁爐數量(類型數未知,實質爲數值型變量)
  • GarageCars – 車庫容量(類型數未知,實質爲數值型變量)
  • PoolArea – 游泳池面積,平方英尺(類型數未知,實質爲數值型變量)
  • MoSold – 房屋的售出月份(擁有12個月,且互相無優劣關係,實質爲onehot類別型變量)
  • YrSold – 房屋的售出年份(擁有5個月,且互相無優劣關係,實質爲onehot類別型變量)

故此,數值型變量中存在列名爲’MSSubClass’、‘YrSold’、'MoSold’的特徵列,實際爲one-hot類別型變量須要更正爲string形式。 (不懂one-hot和label_encoder區別的夥伴點這裏

#對於列名爲'MSSubClass'、'YrSold'、'MoSold'的特徵列,將列中的數據類型轉化爲string格式。
features['MSSubClass'] = features['MSSubClass'].apply(str)
features['YrSold'] = features['YrSold'].astype(str)
features['MoSold'] = features['MoSold'].astype(str)

#將其加入對應的組別
numerical_cols.remove('MSSubClass')
numerical_cols.remove('YrSold')
numerical_cols.remove('MoSold')
categorical_cols.append('MSSubClass')
categorical_cols.append('YrSold')
categorical_cols.append('MoSold')

缺失值處理

由dataframe.info()能看出對於訓練和測試數據都有不一樣程度的缺失狀況,而缺失值的存在會致使模型沒法工做,所以須要題前將這部分數據處理好。

#數據總缺失狀況查閱
(features.isna().sum()/features.shape[0]).sort_values(ascending=False)[:35]
PoolQC          0.996915
MiscFeature     0.964004
Alley           0.932122
Fence           0.804251
FireplaceQu     0.486802
LotFrontage     0.166610
GarageCond      0.054508
GarageQual      0.054508
GarageYrBlt     0.054508
GarageFinish    0.054508
GarageType      0.053822
BsmtCond        0.028111
......
GarageArea      0.000343
GarageCars      0.000343
OverallQual     0.000000
dtype: float64

注意,由特徵文件說明中信息可知許多NA項並不是缺失,而是表示「沒有」此功能的含義, 如PoolQC游泳池質量的缺失NA,實際含義表示沒有游泳池,故須要仔細對照說明信息進行處理。

如下根據缺失值實際狀況進行填充:

#PoolQC, NA表示沒有游泳池,爲一個類型
print(features["PoolQC"].unique())
print(features["PoolQC"].fillna("None").unique())   #空值填充爲str型數據"None",表示沒有泳池。
[nan 'Ex' 'Fa' 'Gd']
['None' 'Ex' 'Fa' 'Gd']
#MiscFeature, NA表示-其餘類別中「沒有」未涵蓋的其餘特性,故填充爲"None"
print(features["MiscFeature"].unique())
print(features["MiscFeature"].fillna("None").unique())
[nan 'Shed' 'Gar2' 'Othr' 'TenC']
['None' 'Shed' 'Gar2' 'Othr' 'TenC']
#因爲類別型變量的許多NA均表示沒有此功能,先從data_distribution中找出這樣的列而後統一填充爲"None"
(features[categorical_cols].isna().sum()/features.shape[0]).sort_values(ascending=False)[:25]
PoolQC          0.996915
MiscFeature     0.964004
Alley           0.932122
Fence           0.804251
FireplaceQu     0.486802
GarageCond      0.054508
.....
SaleType        0.000343
KitchenQual     0.000343
LotShape        0.000000
LandContour     0.000000
dtype: float64
for col in ('PoolQC', 'MiscFeature','Alley', 'Fence', 'FireplaceQu', 'MasVnrType', 'Utilities',
            'GarageCond', 'GarageQual', 'GarageFinish', 'GarageType', 
            'BsmtQual', 'BsmtCond', 'BsmtExposure', 'BsmtFinType1', 'BsmtFinType2'):
    features[col] = features[col].fillna('None')
    
(features[categorical_cols].isna().sum()/features.shape[0]).sort_values(ascending=False)[:10]
MSZoning       0.001371
Functional     0.000686
SaleType       0.000343
Exterior2nd    0.000343
Exterior1st    0.000343
Electrical     0.000343
KitchenQual    0.000343
BldgType       0.000000
ExterQual      0.000000
MasVnrType     0.000000
dtype: float64
#其他類別型變量由所在列的衆數填充
for col in ('Functional', 'SaleType', 'Electrical', 'Exterior2nd', 'Exterior1st', 'KitchenQual'):
    features[col] = features[col].fillna(features[col].mode()[0])

(features[categorical_cols].isna().sum()/features.shape[0]).sort_values(ascending=False)[:3]
MSZoning      0.001371
BldgType      0.000000
Foundation    0.000000
dtype: float64
#因爲MSSubClass(肯定銷售涉及的住宅類型)和 MSZoning(銷售分區的通常分類肯定)之間有必定聯繫。
#具體來講是指在MSSubClass基礎上肯定MSZoning,故能夠按照'MSSubClass'列中的元素分佈進行分組,而後將'MSZoning'列分組後取衆數填充。
features['MSZoning'] = features.groupby('MSSubClass')['MSZoning'].transform(lambda x: x.fillna(x.mode()[0]))
print('類別型數據缺失值數量爲:', features[categorical_cols].isna().sum().sum())
類別型數據缺失值數量爲: 0

最後的df.groupby()工具用法詳見: Groupby的用法及原理詳解

到這裏,類別型數據缺失填充已經完成啦~

接下來就是數值型的特徵:

#數值型變量缺失狀況
(features[numerical_cols].isna().sum()/features.shape[0]).sort_values(ascending=False)[:12]
LotFrontage     0.166610
GarageYrBlt     0.054508
MasVnrArea      0.007885
BsmtFullBath    0.000686
BsmtHalfBath    0.000686
GarageArea      0.000343
GarageCars      0.000343
BsmtFinSF1      0.000343
BsmtFinSF2      0.000343
BsmtUnfSF       0.000343
TotalBsmtSF     0.000343
OpenPorchSF     0.000000
dtype: float64
#由於某些類別型變量爲"None",表示不包含此項,因此形成數值型變量也會缺失,故將這樣的數值變量缺失值填充爲"0"
for col in ('GarageYrBlt', 'GarageArea', 'GarageCars', 'MasVnrArea',
            'BsmtHalfBath', 'BsmtFullBath', 'BsmtFinSF1', 'BsmtFinSF2', 'BsmtUnfSF', 'TotalBsmtSF'):
    features[col] = features[col].fillna(0)
    
(features[numerical_cols].isna().sum()/features.shape[0]).sort_values(ascending=False)[:3]
LotFrontage     0.16661
BsmtFullBath    0.00000
LotArea         0.00000
dtype: float64
#對於 LotFrontage (鏈接到地產的街道的直線英尺距離)而言,其受Neighborhood(城市限制內的物理位置)的影響
#故對於這兩個特徵進行分組後取列的中位數填充
features['LotFrontage'] = features.groupby('Neighborhood')['LotFrontage'].transform(lambda x: x.fillna(x.median()))
print('數值型數據缺失值數量爲:',features[numerical_cols].isna().sum().sum())
數值型數據缺失值數量爲: 0

至此,數據缺失值填充所有完成!!(先放一個小煙花,嘣~ 嘣 ~ 嘣~)

特徵工程

這一步是整個Baseline中最核心的部分,特徵工程的好壞將影響最終的模型效果。所以,業界都流傳着一句話:「數據和特徵決定了機器學習的上線,而模型和算法只是逼近這個上線而已」, 因而可知特徵工程在機器學習中的重要性。具體來講,特徵越好、靈活性越強,則構建的模型越簡單且性能出色。(更多關於特徵工程的知識請參考:機器學習實戰之特徵工程

特徵建立:基於已有特徵進行組合

#GrLivArea: 地上居住總面積
#TotalBsmtSF: 地下室總面積
#將兩者加和造成新的「總居住面積」特徵
features['TotalSF'] = features['GrLivArea'] + features['TotalBsmtSF']

#LotArea: 建築面積
#LotFrontage: 房子同街道之間的距離
#將兩者乘積造成新的「區域面積」特徵
features['Area'] = features['LotArea'] * features['LotFrontage']

#OpenPorchSF :開放式門廊面積
#EnclosedPorch :封閉式門廊面積
#3SsnPorch :時令門廊面積
#ScreenPorch :屏風門廊面積
#將四者加和造成新的"門廊總面積"特徵
features['Total_porch_sf'] = (features['OpenPorchSF'] + features['EnclosedPorch'] + 
                              features['3SsnPorch'] + features['ScreenPorch'])
                              
#FullBath :地面上的全浴室數目
#HalfBath :地面以上半浴室數目
#BsmtFullBath :地下室全浴室數量
#BsmtHalfBath :地下室半浴室數量
#將半浴室權重設爲0.5,全浴室爲1,將四者加和造成新的"總浴室數目"特徵
features['Total_Bathrooms'] = (features['FullBath'] + (0.5 * features['HalfBath']) +
                               features['BsmtFullBath'] + (0.5 * features['BsmtHalfBath']))

#將新特徵加入到數值變量中
numerical_cols.append('TotalSF')
numerical_cols.append('Area')
numerical_cols.append('Total_porch_sf')
numerical_cols.append('Total_Bathrooms')
print('特徵建立後的數據維度 :', features.shape)
特徵建立後的數據維度 : (2917, 83)

小夥伴們能夠根據本身對特徵的理解來自定義構建新的特徵,這裏就因人而異了,充分發揮大家的創造力吧,奧裏給~~

對影響房價關鍵因子進行分箱

許多與房價屬性高度相關的特徵可能須要分箱 binning 來表達更明確的含義,或者有效地去減小對於數值的擬合來增長其泛化性(在測試集上的準確度)。

分箱也是一門學問,我仍是把知識連接給放上吧…

#查看與標籤10個最相關的特徵屬性
train_ = features.iloc[:len(y),:]
train_ = pd.concat([train_,y],axis=1)
cols = train_ .corr().nlargest(10, 'SalePrice').index

plt.subplots(figsize=(8,8))
sns.set(font_scale=1.1)
sns.heatmap(train_ [cols].corr(),square=True, annot=True)

在這裏插入圖片描述
由熱圖可知,‘完工質量和材料’,‘總居住面積’,‘地面上居住面積’,'車庫容量數’,‘總浴室數目’,‘車庫面積’,‘總地下室面積’,'第一層面積’等都是與房價密切相關的特徵。

#完工質量和材料
sns.distplot(features['OverallQual'],bins=10,kde=False)

在這裏插入圖片描述

#完工質量和材料分組
def OverallQual_category(cat):
    if cat <= 4:
        return 1
    elif cat <= 6 and cat > 4:
        return 2
    else:
        return 3

features['OverallQual_cat'] = features['OverallQual'].apply(OverallQual_category)

#總居住面積
sns.distplot(features['TotalSF'],bins=10,kde=False)

在這裏插入圖片描述

#總居住面積分組
def TotalSF_category(cat):
    if cat <= 2000:
        return 1
    elif cat <= 3000 and cat > 2000:
        return 2
    elif cat <= 4000 and cat > 3000:
        return 3
    else:
        return 4

features['TotalSF_cat'] = features['TotalSF'].apply(TotalSF_category)

博主後面還進行了車庫面積、地面上居住面積、地下室總面積、建築相關時間等特徵的分箱操做,原理都同樣,這裏再也不貼代碼。

#而後將建立的分組加入類別型變量中
categorical_cols.append('GarageArea_cat')  
categorical_cols.append('GrLivArea_cat')   
categorical_cols.append('TotalBsmtSF_cat') 
categorical_cols.append('TotalSF_cat') 
categorical_cols.append('OverallQual_cat')   
categorical_cols.append('LotFrontage_cat')  
categorical_cols.append('YearBuilt_cat')    
categorical_cols.append('YearRemodAdd_cat') 
categorical_cols.append('GarageYrBlt_cat') 

#打印當前數據維度
features.shape
(2917, 92)

數值型變量偏度修正

針對一些線性迴歸模型,它們自己對數據分佈有必定要求,例如正態分佈等。因此須要在使用這些模型以前將所使用的特徵儘量轉化爲正態分佈狀態,就須要對數據的偏度和峯度進行了解和轉化。不瞭解數據偏度和峯度的小夥伴看這裏

#查看數值型特徵變量的偏度狀況並繪圖
skew_features = features[numerical_cols].apply(lambda x: skew(x)).sort_values(ascending=False)

sns.set_style("white")
f, ax = plt.subplots(figsize=(8, 12))
ax.set_xscale("log")
ax = sns.boxplot(data=features[numerical_cols], orient="h", palette="Set1")
ax.xaxis.grid(False)
ax.set(ylabel="Feature names")
ax.set(xlabel="Numeric values")
ax.set(title="Numeric Distribution of Features")
sns.despine(trim=True, left=True)

在這裏插入圖片描述

#對特徵變量'GrLivArea',繪製直方圖和Q-Q圖,以清楚數據分佈結構
plt.figure(figsize=(8,4))
ax_121 = plt.subplot(1,2,1)
sns.distplot(features['GrLivArea'],fit=stats.norm)
ax_122 = plt.subplot(1,2,2)
res = stats.probplot(features['GrLivArea'],plot=plt)

在這裏插入圖片描述

#以0.5做爲閾值,統計偏度超過此數值的高偏度分佈數據列,獲取這些數據列的index
high_skew = skew_features[skew_features > 0.5]
skew_index = high_skew.index
print("There are {} numerical features with Skew > 0.5 :".format(high_skew.shape[0]))
high_skew.sort_values(ascending=False)
There are 28 numerical features with Skew > 0.5 :
MiscVal           21.939672
Area              18.642721
PoolArea          17.688664
LotArea           13.109495
LowQualFinSF      12.084539
3SsnPorch         11.372080
...
HalfBath           0.696666
TotalBsmtSF        0.671751
BsmtFullBath       0.622415
OverallCond        0.569314
dtype: float64

對高偏度數據進行處理,將其轉化爲正態分佈時,通常使用Box-Cox變換。它可使數據知足線性性、獨立性、方差齊次以及正態性的同時,又不丟失信息。
boxcox1p轉換公式

#使用boxcox_normmax用於找出最佳的λ值
for i in skew_index:
    features[i] = boxcox1p(features[i], boxcox_normmax(features[i] + 1))

features[numerical_cols].apply(lambda x: skew(x)).sort_values(ascending=False)
BsmtFinSF2          2.578329
EnclosedPorch       2.149132
Area                1.000000
MasVnrArea          0.977618
2ndFlrSF            0.895453
WoodDeckSF          0.785550
HalfBath            0.732625
OpenPorchSF         0.621231
BsmtFullBath        0.616643
Fireplaces          0.553135
.....
GarageArea          0.216857
OverallQual         0.189591
FullBath            0.165514
LotFrontage         0.059189
BsmtUnfSF           0.054195
TotRmsAbvGrd        0.047190
TotalSF             0.027351
GrLivArea           0.008823
dtype: float64
#box-cox變換後的對特徵變量'GrLivArea'
plt.figure(figsize=(8,4))
ax_121 = plt.subplot(1,2,1)
sns.distplot(features['GrLivArea'],fit=stats.norm)
ax_122 = plt.subplot(1,2,2)
res = stats.probplot(features['GrLivArea'],plot=plt)

在這裏插入圖片描述
至此,數字型特徵列偏度校訂所有完成!

(呼~好累,活動一下手臂繼續肝!!)

刪除單一值特徵

在某些類別型特徵中,某個種類佔據了99%以上的部分,也就是說特徵之間的具備明顯的單一值特色,這些特徵對模型也沒有什麼貢獻可言,須要刪除。

查看類別型特徵的惟一值分佈狀況
features[categorical_cols].describe(include='O').T
count	unique	top	freq
MSZoning	2917	5	RL	2265
Street	2917	2	Pave	2905
Alley	2917	3	None	2719
LotShape	2917	4	Reg	1859
LandContour	2917	4	Lvl	2622
Utilities	2917	3	AllPub	2914
LotConfig	2917	5	Inside	2132
......
SaleType	2917	9	WD	2526
SaleCondition	2917	6	Normal	2402
MSSubClass	2917	16	20	1079
YrSold	2917	5	2007	691
MoSold	2917	12	6	503
#對於類別型特徵變量中,單個類型佔比超過99%以上的特徵(即> 2888個)進行刪除.
freq_ = features[categorical_cols].describe(include='O').T.freq
drop_cols = []
for index,num in enumerate(freq_):
    if (freq_[index] > 2888) :
        drop_cols.append(freq_.index[index])

features = features.drop(drop_cols, axis=1)
print('These drop_cols are:', drop_cols)
print('The new shape is :', features.shape)

categorical_cols.remove('Street')
categorical_cols.remove('PoolQC')
categorical_cols.remove('Utilities')
These drop_cols are: ['Street', 'Utilities', 'PoolQC']
The new shape is : (2917, 89)

特徵簡化:0/1二值化處理

對於某些分佈單調的數字型數據列, 按照「有」和「沒有」來進行二值化處理,以擴充更多地特徵維度。

#經過對於特徵含義理解,篩選出瞭如下幾個變量進行二值化處理
features['HasPool'] = features['PoolArea'].apply(lambda x: 1 if x > 0 else 0)
features['HasWoodDeckSF'] = features['WoodDeckSF'].apply(lambda x: 1 if x > 0 else 0)
features['Hasfireplace'] = features['Fireplaces'].apply(lambda x: 1 if x > 0 else 0)
features['HasBsmt'] = features['TotalBsmtSF'].apply(lambda x: 1 if x > 0 else 0)
features['HasGarage'] = features['GarageArea'].apply(lambda x: 1 if x > 0 else 0)

#查看當前特徵數
print("通過特徵處理後的特徵維度爲 :",features.shape)
通過特徵處理後的特徵維度爲 : (2917, 94)

至此,特徵構造處理完成所有完成!

特徵編碼

對於類別型數據,通常採用獨熱編碼onehot形式,對於彼此有數量關聯的特徵通常採用labelencoder編碼。

#使用pd.get_dummies()方法對特徵矩陣進行相似「座標投影」操做,得到在新空間下onehot的特徵表達。
final_features = pd.get_dummies(features,columns=categorical_cols).reset_index(drop=True)
print("通過onehot編碼後的特徵維度爲 :", final_features.shape)
通過onehot編碼後的特徵維度爲 : (2917, 370)
#訓練集&測試集數據還原
X_train = final_features.iloc[:len(y), :]
X_sub = final_features.iloc[len(y):, :]
print("訓練集特徵維度爲:", X_train.shape)
print("測試集特徵維度爲:", X_sub.shape)
訓練集特徵維度爲: (1458, 370)
測試集特徵維度爲: (1459, 370)

異常值複查:基於迴歸模型

除了根據可視化的異常值篩查之外,使用模型對數據進行擬合,而後設定一個殘差閾值(y_true - y_pred) 也能從另外一個角度找出可能潛在的異常值。

#定義迴歸模型找出異常值並繪圖的函數
def find_outliers(model, X, y, sigma=4):
    try:
        y_pred = pd.Series(model.predict(X), index=y.index)
    except:
        model.fit(X,y)
        y_pred = pd.Series(model.predict(X), index=y.index)
    
    #計算模型預測y值與真實y值之間的殘差
    resid = y - y_pred
    mean_resid = resid.mean()
    std_resid = resid.std()
    
    #計算異常值定義的參數z參數,數據的|z|大於σ將會被視爲異常
    z = (resid - mean_resid) / std_resid
    outliers = z[abs(z) > sigma].index
    
    #打印結果並繪製圖像
    print('R2 = ',model.score(X,y))
    print('MSE = ',mean_squared_error(y, y_pred))
    print('RMSE = ',np.sqrt(mean_squared_error(y, y_pred)))
    print('------------------------------------------')
    
    print('mean of residuals',mean_resid)
    print('std of residuals',std_resid)
    print('------------------------------------------')
    
    print(f'find {len(outliers)}','outliers:')
    print(outliers.tolist())
    
    plt.figure(figsize=(15,5))
    ax_131 = plt.subplot(1,3,1)
    plt.plot(y,y_pred,'.')
    plt.plot(y.loc[outliers],y_pred.loc[outliers],'ro')
    plt.legend(['Accepted','Outliers'])
    plt.xlabel('y')
    plt.ylabel('y_pred');
    
    ax_132 = plt.subplot(1,3,2)
    plt.plot(y, y-y_pred, '.')
    plt.plot(y.loc[outliers],y.loc[outliers] - y_pred.loc[outliers],'ro')
    plt.legend(['Accepted','Outliers'])
    plt.xlabel('y')
    plt.ylabel('y - y_pred');
    
    ax_133 = plt.subplot(1,3,3)
    z.plot.hist(bins=50, ax=ax_133)
    z.loc[outliers].plot.hist(color='r', bins=30, ax=ax_133)
    plt.legend(['Accepted','Outliers'])
    plt.xlabel('z')
    
    return outliers

#使用LR迴歸模型
outliers_lr = find_outliers(LinearRegression(), X_train, y, sigma=3.5)
R2 =  0.9533461995514986
MSE =  0.007448781362371816
RMSE =  0.08630632284121376
------------------------------------------
mean of residuals -2.8022090059126034e-17
std of residuals 0.08633593557937841
------------------------------------------
find 15 outliers:
[30, 88, 431, 462, 580, 587, 631, 687, 727, 873, 967, 969, 1322, 1430, 1451]

在這裏插入圖片描述

#使用Elasnet模型
outliers_ent = find_outliers(ElasticNetCV(), X_train, y, sigma=3.5)
R2 =  0.8237243364637833
MSE =  0.028144306885302683
RMSE =  0.16776265044789523
------------------------------------------
mean of residuals -1.6593950721969417e-15
std of residuals 0.1678202118324841
------------------------------------------
find 10 outliers:
[30, 185, 410, 462, 495, 631, 687, 915, 967, 1243]

在這裏插入圖片描述

#使用XGB模型
outliers_xgb = find_outliers(XGBRegressor(), X_train, y, sigma=4)
R2 =  0.9993821316841015
MSE =  9.864932656333151e-05
RMSE =  0.00993223673516351
------------------------------------------
mean of residuals 6.241242620683598e-06
std of residuals 0.009935642643516977
------------------------------------------
find 3 outliers:
[883, 1055, 1279]

在這裏插入圖片描述
後面還用了LGB模型GBDT模型SVR模型來肯定outliers,這裏省略繪圖了。

而後比較每一個模型下的異常值序號,進行人工投票選擇,超過半數即爲異常值,這樣最終肯定了outliers,並在特徵集和標籤集中刪除。

outliers = [30, 462, 631, 967]
X_train = X_train.drop(X_train.index[outliers])
y = y.drop(y.index[outliers])

消除one-hot特徵矩陣的過擬合

當使用one-hot編碼後,一些列可能會帶來過擬合的風險。判斷某一列是否將產生過擬合的條件是:

特徵矩陣某一列中的某個值出現的次數除以特徵矩陣的列數超過99.95%,即其幾乎在被投影的各個維度上都有着一樣的取值,並不具備「主成分」的性質,則記爲過擬合的列。
#記錄產生過擬合的數據列的序號

overfit = []
for i in X_train.columns:
    counts = X_train[i].value_counts(ascending=False)
    zeros = counts.iloc[0]
    if zeros / len(X_train) * 100 > 99.95:
        overfit.append(i)
        
overfit
['Area', 'MSSubClass_150']
#對訓練集和測試集同時刪除這些列
X_train = X_train.drop(overfit, axis=1).copy()
X_sub = X_sub.drop(overfit, axis=1).copy()
print('通過異常值和過擬合刪除後訓練集的特徵維度爲:', X_train.shape)
print('通過異常值和過擬合刪除後測試集的特徵維度爲:', X_sub.shape)
通過異常值和過擬合刪除後訓練集的特徵維度爲: (1454, 368)
通過異常值和過擬合刪除後測試集的特徵維度爲: (1459, 368)

至此,數據預處理和特徵工程部分所有完成!(喘一口粗氣)

那麼本期的Kaggle入門案例解析就到此啦,實在沒辦法一下所有寫完,分紅兩期寫吧。數據處理和特徵工程已經能夠結束了,下一期的話給你們帶來後面的模型搭建、調優和融合部分的代碼解析和講解。感謝努力學習知識,而且沉穩帥氣/美麗動人的你~,我們後續再見!

相關文章
相關標籤/搜索