Kaggle比賽入門新手教程(房價預測案例:前篇)
- Kaggle房價預測全流程詳解
- 競賽鏈接與背景介紹
- 競賽代碼決議
- 匯入工具包
- 資料加載
- 資料預處理
- 例外值初篩
- 標簽值對數變換
- 明確變數型別
- 缺失值處理
- 特征工程
- 特征創建:基于已有特征進行組合
- 對影響房價關鍵因子進行分箱
- 數值型變數偏度修正
- 洗掉單一值特征
- 特征簡化:0/1二值化處理
- 特征編碼
- 例外值復查:基于回歸模型
- 消除one-hot特征矩陣的過擬合
Kaggle房價預測全流程詳解
對于剛剛入門機器學習的童孩來說,如何快速地通過不同實戰演練以提高代碼能力和流程理解是一個需要關注的問題,Kaggle平臺正好提供了資料科學家的所需要的交流環境,并且為癡迷于人工智能的狂熱的愛好者舉辦了各種型別的競賽(如,資料科學/影像分類/影像識別/自然語言處理/漏洞檢測),
Kaggle社區是一種全球性的交流社區,集中大量優秀的AI科學家和資料分析家,能夠相互分享實戰經驗和代碼,并且有基礎入門教程,對新手非常友好~
競賽鏈接與背景介紹

- Kaggle平臺官網:https://www.kaggle.com
- 房價預測競賽網址: https://www.kaggle.com/c/house-prices-advanced-regression-techniques
房價是一個生活中耳熟能詳的概念,在大城市買房尤其成為了上班族幾乎最大的苦惱(以后即將面臨····),而在美國的愛荷華州埃姆斯市有許多因素影響著房屋的最終價格,例如房屋面積、地下室、浴室和車庫等等;
kaggle平臺收集了約80個可能影響房價的特征變數,要求資料科學家利用機器學習等工具對房價進行預測,即該案例是一種簡單的回歸問題,
官方提供的房屋特征描述檔案我已翻譯成中文,供大家參考,英文原版的可以點擊Kaggle競賽欄目下的下載按鈕,資料集也是一樣,如下所示:
- 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個特征,
資料預處理
例外值初篩
#先將樣本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變換,它可以使資料滿足線性性、獨立性、方差齊次以及正態性的同時,又不丟失資訊,

#使用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入門案例決議就到此啦,實在沒辦法一下全部寫完,分成兩期寫吧,資料處理和特征工程已經可以結束了,下一期的話給大家帶來后面的模型搭建、調優和融合部分的代碼決議和講解,感謝努力學習知識,并且沉穩帥氣/美麗動人的你~,咱們后續再見!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/177338.html
標籤:其他
