数据科学竞赛-房价预测
房價預測
簡介
這是Kaggle上的一個Getting Started級別的新手賽,主要為房價預測的回歸賽。具體比賽的級別之分,可以查看我關于Kaggle介紹的博客。詳細的思路均在Notebook中標注,本文不多贅述過多理論思路,具體可以查看我之前關于數據挖掘賽的思路博客。共提交了兩次,baseline和final兩個版本,final版本參考了Kaggle上部分人的stacking思路。本文只簡述Final版本的思路,baseline版本比較簡單,可以在文末給出的Github地址找到源碼。
數據獲取
官方給出了數據,可以在Kernel(現Notebook)中直接訪問該數據集,也可以下載到本地,官方給出了下載地址。
數據的大致分為下面四個文件,官方給出了文件說明。
具體的表頭屬性的含義可以在data_description.txt查看,這對數據預處理和特征構造階段尤其重要。
探索性數據分析
只是進行了一些必要的EDA操作。
離群點分析
fig, ax = plt.subplots() ax.scatter(x=df_train['GrLivArea'], y=df_train['SalePrice']) plt.xlabel('GrLivArea') plt.xlabel('SalePrice') plt.show() # 該離群點可以刪除是因為確實離群范圍過大,嚴重影響模型擬合,不是所有的離群點都應該刪除的 df_train = df_train.drop(df_train[(df_train['GrLivArea'] > 4000)&(df_train['SalePrice']<300000)].index)目標分布分析
sns.distplot(df_train['SalePrice'], fit=norm) # 使用正態分布擬合數據 (mu, sigma) = norm.fit(df_train['SalePrice']) # 對樣本進行擬合,得到最合適的采樣數據的概率密度函數的系數 plt.legend(['Normal dist. ($\mu=$ {:.2f} and $\sigma=$ {:.2f} )'.format(mu, sigma)], loc='best') plt.ylabel('Frequency') plt.title('SalePrice distribution')fig = plt.figure() res = probplot(df_train['SalePrice'], plot=plt) plt.show()空值分析
all_data_null = df_all.isnull().sum() / len(df_all) * 100 # 統計各列的空值數目 all_data_null = all_data_null[all_data_null>0].sort_values(ascending=False) miss_data = pd.DataFrame({'MissRatio': all_data_null}) miss_data.head(30)相關性分析
corr_mat = df_train.corr() plt.subplots(figsize=(16, 8)) sns.heatmap(corr_mat, vmax=0.9, square=True)特征工程
這一步是整個流程費時最久的,進行了大量的屬性處理和理解。
空值處理
一般不會對空值記錄進行刪除,這會造成信息的大量丟失,最合理的做法是依照說明文件,對各屬性進行理解,選擇最合適的填充方法進行空值填充。
# 按照說明文件,PoolQC為空表示沒有泳池,考慮到該列較高的缺失率以及大多數房子都是沒有泳池的,直接None填充 df_all['PoolQC'] = df_all['PoolQC'].fillna("None") # 下面幾項均可以按照說明文件,直接填None cols = ['MiscFeature', 'Alley', 'Fence', 'FireplaceQu', 'GarageType', 'GarageFinish', 'GarageQual', 'GarageCond'] for col in cols:df_all[col] = df_all[col].fillna("None") # 街道面積與同社區的其他房屋的街道面積類似,取所有鄰居中位數即可 df_all["LotFrontage"] = df_all.groupby("Neighborhood")["LotFrontage"].transform(lambda x: x.fillna(x.median())) # 根據屬性含義,下面的屬性直接填0 cols = ['GarageYrBlt', 'GarageArea', 'GarageCars', 'BsmtFinSF1', 'BsmtFinSF2', 'BsmtUnfSF','TotalBsmtSF', 'BsmtFullBath', 'BsmtHalfBath'] for col in cols:df_all[col] = df_all[col].fillna(0) # 下面的屬性為空表示無值 cols = ['BsmtQual', 'BsmtCond', 'BsmtExposure', 'BsmtFinType1', 'BsmtFinType2'] for col in cols:df_all[col] = df_all[col].fillna('None') # 下面兩個屬性為NA最可能意味空值 df_all["MasVnrType"] = df_all["MasVnrType"].fillna("None") df_all["MasVnrArea"] = df_all["MasVnrArea"].fillna(0) # 眾數填,RL最合適 df_all['MSZoning'] = df_all['MSZoning'].fillna(df_all['MSZoning'].mode()[0]) # 該屬性只有三個不同值,其余均為一個結果,該列對模型擬合沒有太大意義,刪除即可 df_all = df_all.drop(columns=['Utilities'], axis=1) # 根據說明,Typ代表典型值 df_all["Functional"] = df_all["Functional"].fillna("Typ") # 經過分析,下面的屬性均眾數填充即可 cols = ['Electrical', 'KitchenQual', 'Exterior1st', 'Exterior2nd', 'SaleType'] for col in cols:df_all[col] = df_all[col].fillna(df_all[col].mode()[0]) # 填None df_all['MSSubClass'] = df_all['MSSubClass'].fillna("None")屬性變換
這一步主要將字符型數據進行自然數編碼,onehot編碼之類的,最終產生全部為數值的數據。
# 部分看起來數值型的變量,其實取值只有幾種,轉換為分類變量合適一些 df_all['MSSubClass'] = df_all['MSSubClass'].apply(str) df_all['OverallCond'] = df_all['OverallCond'].astype(str) df_all['YrSold'] = df_all['YrSold'].astype(str) df_all['MoSold'] = df_all['MoSold'].astype(str) # 將部分分類變量轉化為數值型 from sklearn.preprocessing import LabelEncoder cols = ('FireplaceQu', 'BsmtQual', 'BsmtCond', 'GarageQual', 'GarageCond', 'ExterQual', 'ExterCond','HeatingQC', 'PoolQC', 'KitchenQual', 'BsmtFinType1', 'BsmtFinType2', 'Functional', 'Fence', 'BsmtExposure', 'GarageFinish', 'LandSlope','LotShape', 'PavedDrive', 'Street', 'Alley', 'CentralAir', 'MSSubClass', 'OverallCond', 'YrSold', 'MoSold') for col in cols:lbl = LabelEncoder() lbl.fit(list(df_all[col].values)) df_all[col] = lbl.transform(list(df_all[col].values))# labelencoder不會像get_dummies那樣生成多個屬性 df_all.shape屬性構造
有理由的對已有特征進行組合,構造對模型有效的新特征。本賽題只構建了房屋全面積這個屬性。隨后,對所有特征進行高偏特征的Box-Cox變換,具體查看源碼。
# 根據經驗,面積對于房價的影響時非常大的,構造房屋總面積這個屬性 df_all['TotalArea'] = df_all['TotalBsmtSF'] + df_all['1stFlrSF'] + df_all['2ndFlrSF']模型構建
本部分還是采用挖掘賽常用的思路,對若模型結果進行集成,主要使用的是Stacking方法。
平均模型
# 采用stacking方法 class StackingAveragedModels(BaseEstimator, RegressorMixin, TransformerMixin):def __init__(self, base_models, meta_model, n_folds=5):self.base_models = base_modelsself.meta_model = meta_modelself.n_folds = n_foldsdef fit(self, X, y):self.base_models_ = [list() for x in self.base_models]self.meta_model_ = clone(self.meta_model)kfold = KFold(n_splits=self.n_folds, shuffle=True, random_state=156)out_of_fold_predictions = np.zeros((X.shape[0], len(self.base_models)))for i, model in enumerate(self.base_models):for train_index, holdout_index in kfold.split(X, y):instance = clone(model)self.base_models_[i].append(instance)instance.fit(X[train_index], y[train_index])y_pred = instance.predict(X[holdout_index])out_of_fold_predictions[holdout_index, i] = y_predself.meta_model_.fit(out_of_fold_predictions, y)return selfdef predict(self, X):meta_features = np.column_stack([np.column_stack([model.predict(X) for model in base_models]).mean(axis=1)for base_models in self.base_models_ ])return self.meta_model_.predict(meta_features)stacked_averaged_models = StackingAveragedModels(base_models = (ENet, GBoost, KRR, ABR), meta_model = lasso) score = rmse_cv(stacked_averaged_models) print("Stacking Averaged models score: {:.4f} ({:.4f})".format(score.mean(), score.std()))- 集成的模型具有不錯的效果。
多模型加權組合
將多個集成模型,分別為采用Stacking的自定義模型,采用Boosting的Xgboost和Lightgbm進行結果加權組合。
print("Final RMSE Loss in training dataset", rmse(y_train, stacked_train_pred*0.70+xgb_train_pred*0.15+lgb_train_pred*0.15))加權組合的結果如下,看起來不錯,這就是最后提交的模型。
模型應用
使用模型進行預測,測試集的處理同訓練集。final版本的提交達到了top10%,由于這里只是使用最簡單的幾種模型stacking,嘗試更多的模型和參數調整會獲得更好的結果。(調參只是“錦上添花”)
提交結果
- baseline
- final
補充說明
項目的源碼和數據集都上傳到我的Github,歡迎查看。文章同步到我的個人博客網站,歡迎查看其他文章。如有錯誤,歡迎指正。
總結
以上是生活随笔為你收集整理的数据科学竞赛-房价预测的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: TensorFlow2-基础操作
- 下一篇: TensorFlow2-高阶操作