数据科学竞赛-房价预测

房价预测

简介

这是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_models
        self.meta_model = meta_model
        self.n_folds = n_folds

    def 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_pred
                
        self.meta_model_.fit(out_of_fold_predictions, y)
        return self

    def 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,欢迎查看。文章同步到我的个人博客网站,欢迎查看其他文章。如有错误,欢迎指正。

发布了225 篇原创文章 · 获赞 109 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/zhouchen1998/article/details/101909181