广州二手房价分析与预测

一、概述

 

1.1问题介绍与分析

随着社会经济的迅猛发展,房地产开发建设的速度越来越快,二手房市场迅猛发展,对二手房房产价格评估的需求也随之增大.因此,对二手房房价预测与分析是必要的.详细文档与代码资料保存在我的百度云盘

数据收集:在链家网站上收集广州二手房数据,把数据分为训练集、测试集。训练和测试数据是一些二手房信息以及价格状况,要尝试根据它生成合适的模型并预测其他二手房的价格状况。这是一个回归问题,很多回归算法都可以解决。

收集工具:八爪鱼

 

 

1.2总体思路

首先从数据清洗开始,对缺失值的多维度处理、对离群点的剔除方法以及对字符、空格等的处理;其次进行特征工程,包括类别特征编码、连续特征编码等;再次进行特征选择,采用了xgboost,xgboost的训练过程即对特征重要性的排序过程;最后一部分是模型设计与分析,采用了岭回归模型、xgboost,取得了不错的效果,此外还介绍了模型融合方法。

 

二、 数据清洗

 

2.1缺失值的多维度处理

1)通常遇到缺值的情况,我们会有几种常见的处理方式

①如果缺值的样本占总数比例极高,我们可能就直接舍弃了,作为特征加入的话,可能反倒带入noise,影响最后的结果了

②如果缺值的样本适中,而该属性非连续值特征属性(比如说类目属性),那就把NaN作为一个新类别,加到类别特征中

③如果缺值的样本适中,而该属性为连续值特征属性,有时候我们会考虑给定一个step(比如这里的age,我们可以考虑每隔2/3岁为一个步长),然后把它离散化,之后把NaN作为一个type加到属性类目中。

2)有些情况下,缺失的值个数并不是特别多,那我们也可以试着根据已有的值,拟合一下数据,补充上。



2.2数据预处理

#载入数据:

train=pd.read_csv('train.csv')

test= pd.read_csv('test.csv')


#检视源数据

print(train.head())

 

#合并数据

这么做主要是为了用DF进行数据预处理的时候更加方便。等所有的需要的预处理进行完之后,我们再把他们分隔开。

首先,价格作为我们的训练目标,只会出现在训练集中,不会在测试集中。所以,我们先把价格这一列给拿出来。

 

#可视化价格特征

prices= pd.DataFrame({"price":train["价格"],"log(price + 1)":np.log1p(train["价格"])})

prices.hist()

#可见,label本身并不平滑。为了分类器的学习更加准确,首先把label给“平滑化”(正态化)

#这里使用log1p, 也就是 log(x+1),避免了复值的问题。值得注意的是,如果这里把数据都给平滑化了,那么最后算结果的时候,要把预测到的平滑数据给变回去。按照“怎么来的怎么去”原则,log1p()就需要expm1(); 同理,log()就需要exp()

 

y_train= np.log1p(train.pop('价格'))

#然后把剩下的部分合并起来     

all_df= pd.concat((train, test), axis=0)

 

三、特征工程

 

#变量转换

all_df["楼龄"]=2017-all_df["楼龄"]

#有一些数据是缺失的,根据缺失值的多少进行相应的处理

#查看缺失情况

print(all_df.isnull().sum().sort_values(ascending=False).head(10))

#可以看到,缺失最多的column是装修情况。由于装修情况的值缺失太多就删掉这一列

all_df.pop("装修情况")

 

#接着处理楼龄的缺失值,由于缺失不多,这里我用平均值进行填充

mean_col= all_df["楼龄"].mean()

all_df["楼龄"]=all_df["楼龄"].fillna(mean_col)

 

#由于变量交易权属 建筑类型 配备电梯的绝大多数的值相同,区分的效果不会很明显这里就删掉了

all_df.pop("建筑类型")

all_df.pop("配备电梯")

all_df.pop("交易权属")

#该特征很多都不相同,这里简单处理就删掉了

all_df.pop("房型")

 

#看看是不是没有空缺了?

print(all_df.isnull().sum().sum())

 

#把category的变量转变成numerical表达形式

#当我们用numerical来表达categorical的时候,要注意,数字本身有大小的含义所以乱用数字会给之后的模型学习带来麻烦。于是我们可以用One-Hot的方法来表达category。pandas自带的get_dummies方法可以帮你一键做到One-Hot。

pd.get_dummies(all['区域'], prefix='区域')

#此刻区域被分成了5个column,每一个代表一个category。是就是1,不是就是0。

#同理,我们把所有的category数据,都给One-Hot了

all_dummy_df= pd.get_dummies(all_df)

 

#标准化numerical数据

#这一步并不是必要,但是得看你想要用的分类器是什么。一般来说,regression的分类器最好是把源数据给放在一个标准分布内。不要让数据间的差距太大。这里,我们当然不需要把One-Hot的那些0/1数据给标准化。我们的目标应该是那些本来就是numerical的数据:

 

#先来看看哪些是numerical的:

numeric_cols= all_df.columns[all_df.dtypes != 'object']

#这里也是用log1p()方法进行平滑

all_dummy_df["楼龄"]=np.log1p(all_dummy_df["楼龄"])

all_dummy_df["建筑面积"]=np.log1p(all_dummy_df["建筑面积"])

 

四、 特征选择

 

在特征工程部分,这么多维特征一方面可能会导致维数灾难,另一方面很容易导致过拟合,需要做降维处理,降维方法常用的有如 PCA,t-SNE 等,这类方法的计算复杂度比较高。并且根据以往经验,PCA 或t-SNE 效果往往不好。除了采用降维算法之外,也可以通过特征选择来降低特征维度。特征选择的方法很多:最大信息系数(MIC)、皮尔森相关系数(衡量变量间的线性相关性)、正则化方法(L1,L2)、基于模型的特征排序方法。比较高效的是最后一种,即基于学习模型的特征排序方法,这种方法有一个好处:模型学习的过程和特征选择的过程是同时进行的,因此我们采用这种方法,基于xgboost 来做特征选择,xgboost 模型训练完成后可以输出特征的重要性,据此我们可以保留Top N 个特征,从而达到特征选择的目的。

 

五、 模型设计与分析

 

5.1单个分类器

从训练数据中随机划分部分数据作为验证集,剩下作为训练集,供模型调参使用。在训练集上采用十折交叉验证进行模型调参。得到多个单模型结果后,再在验证集上做进一步的模型融合。

#把数据集分回训练/测试集

dummy_train_df= all_dummy_df[:110]

dummy_test_df= all_dummy_df[110:-1]

X_train= dummy_train_df.values

X_test= dummy_test_df.values

(1)随机森林

max_features= [.1, .3, .5, .7, .9, .99]

test_scores= []

formax_feat in max_features:

    clf =RandomForestRegressor(n_estimators=200, max_features=max_feat)

    test_score = np.sqrt(-cross_val_score(clf,X_train, y_train, cv=5, scoring='neg_mean_squared_error'))

    test_scores.append(np.mean(test_score))

plt.plot(max_features,test_scores)

plt.title("MaxFeatures vs CV Error");

#用RF的最优值达到了0.194

 

(2)这里我们用一个Stacking的思维来汲取两种或者多种模型的优点首先,我们把最好的parameter拿出来,

做成我们最终的model

ridge= Ridge(alpha=1)

rf= RandomForestRegressor(n_estimators=500, max_features=.3)

ridge.fit(X_train,y_train)

rf.fit(X_train,y_train)

#上面提到了,因为最前面我们给label做了个log(1+x), 这里我们需要把predit的值给exp回去,并且减掉那个"1"

所以就是我们的expm1()函数。

y_ridge= np.expm1(ridge.predict(X_test))

y_rf= np.expm1(rf.predict(X_test))

#一个正经的Ensemble是把这群model的预测结果作为新的input,再做一次预测。

y_final= 0.8*y_ridge + 0.2*y_rf

print(y_final)

 

要判定一下当前模型所处状态(欠拟合or过拟合)

 

有一个很可能发生的问题是,我们不断地做feature engineering,产生的特征越来越多,用这些特征去训练模型,会对我们的训练集拟合得越来越好,同时也可能在逐步丧失泛化能力,从而在待预测的数据上,表现不佳,也就是发生过拟合问题。从另一个角度上说,如果模型在待预测的数据上表现不佳,除掉上面说的过拟合问题,也有可能是欠拟合问题,也就是说在训练集上,其实拟合的也不是那么好。而在机器学习的问题上,对于过拟合和欠拟合两种情形。我们优化的方式是不同的。

 

对过拟合而言,通常以下策略对结果优化是有用的:

  • 做一下feature selection,挑出较好的feature的subset来做training
  • 提供更多的数据,从而弥补原始数据的bias问题,学习到的model也会更准确

而对于欠拟合而言,我们通常需要更多的feature,更复杂的模型来提高准确度。

著名的learning curve可以帮我们判定我们的模型现在所处的状态。我们以样本数为横坐标,训练和交叉验证集上的错误率作为纵坐标,两种状态分别如下两张图所示:过拟合,欠拟合

 

著名的learningcurve可以帮我们判定我们的模型现在所处的状态。我们以样本数为横坐标,训练和交叉验证集上的错误率作为纵坐标

在实际数据上看,我们得到的learning curve没有理论推导的那么光滑,但是可以大致看出来,训练集和交叉验证集上的得分曲线走势还是符合预期的。目前的曲线看来,我们的model并不处于overfitting的状态(overfitting的表现一般是训练集上得分高,而交叉验证集上要低很多,中间的gap比较大)。

 

5.2 Bagging

 

一般来说,单个分类器的效果真的是很有限。我们会倾向于把N多的分类器合在一起,做一个“综合分类器”以达到最好的效果。模型融合可以比较好地缓解,训练过程中产生的过拟合问题,从而对于结果的准确度提升有一定的帮助。Bagging把很多的小分类器放在一起,每个train随机的一部分数据,然后把它们的最终结果综合起来(多数投票制)。Sklearn已经直接提供了这套构架,我们直接调用就行:在这里,我们用CV结果来测试不同的分类器个数对最后结果的影响。注意,我们在部署Bagging的时候,要把它的函数base_estimator里填上你的小分类器

 

(1)用bagging自带的DT

params= [1, 10, 15, 20, 25, 30, 40]

test_scores= []

forparam in params:

clf= BaggingRegressor(n_estimators=param)

 test_score = np.sqrt(-cross_val_score(clf,X_train, y_train, cv=10, scoring='neg_mean_squared_error'))

test_scores.append(np.mean(test_score))

   

plt.plot(params,test_scores)

plt.title("n_estimatorvs CV Error");

#用bagging自带的DT,结果是0.196

 

(2)ridge + bagging

ridge= Ridge(1)

params= [1, 10, 15, 20, 25, 30, 40]

test_scores= []

forparam in params:

    clf = BaggingRegressor(n_estimators=param,base_estimator=ridge)

    test_score = np.sqrt(-cross_val_score(clf,X_train, y_train, cv=10, scoring='neg_mean_squared_error'))

    test_scores.append(np.mean(test_score))

plt.plot(params,test_scores)

print(test_scores)

plt.title("n_estimatorvs CV Error");

 

#使用25个小ridge分类器的bagging,达到了0.163的结果.

#learning_curve曲线,准确率比较高,基本没有过拟合

 

5.3Boosting

#Boosting比Bagging理论上更高级点,它也是揽来一把的分类器。但是把他们线性排列。下一个分类器把上一个分类器分类得不好的地方加上更高的权重,这样下一个分类器就能在这个部分学得更加“深刻”。

 

(1)Adaboost+Ridge

params= [34,35,36,45,50,55,60,65]

test_scores= []

forparam in params:

    clf = AdaBoostRegressor(n_estimators=param,base_estimator=ridge)

    test_score = np.sqrt(-cross_val_score(clf,X_train, y_train, cv=10, scoring='neg_mean_squared_error'))

    test_scores.append(np.mean(test_score))

plt.plot(params,test_scores)

plt.title("n_estimatorvs CV Error");  

#Adaboost+Ridge在这里,55个小分类器的情况下,达到了0.163的结果。其learning_curve曲线如下:

 

(2)使用Adaboost自带的DT

params= [10, 15, 20, 25, 30, 35, 40, 45, 50]

test_scores= []

forparam in params:

    clf = AdaBoostRegressor(n_estimators=param)

    test_score = np.sqrt(-cross_val_score(clf,X_train, y_train, cv=10, scoring='neg_mean_squared_error'))

    test_scores.append(np.mean(test_score))

plt.plot(params,test_scores)

plt.title("n_estimatorvs CV Error");

#test_scores达到0.198的结果

 

5.3XGBoost

#这依旧是一个Boosting框架的模型,但是却做了很多的改进。用Sklearn自带的cross validation方法来测试模型

params= [1,2,3,4,5,6]

test_scores= []

forparam in params:

    clf = XGBRegressor(max_depth=param)

    test_score = np.sqrt(-cross_val_score(clf,X_train, y_train, cv=10, scoring='neg_mean_squared_error'))

    test_scores.append(np.mean(test_score))

 

plt.plot(params,test_scores)

plt.title("max_depthvs CV Error");

#当params值为6时,test_scores达到0.185

#轻微过拟合

 

5.4 GBM

params= [80,90,100,110,120,130,140,150]

test_scores= []

forparam in params:

    clf =GradientBoostingRegressor(n_estimators=param)

    test_score = np.sqrt(-cross_val_score(clf,X_train, y_train, cv=10, scoring='neg_mean_squared_error'))

    test_scores.append(np.mean(test_score))

   

plt.plot(params,test_scores)

plt.title("max_depthvs CV Error");

 

#当params值为90时,test_scores达到0.188,从其学习率曲线可以看出,模型过拟合挺严重

#综上,Bagging + Ridge 与Adaboost+Ridge模型的准确率和泛化能力是比较好。



六 使用ridge + bagging模型预测房价

 

ridge= Ridge(1)

br=BaggingRegressor(n_estimators=param, base_estimator=ridge)

br.fit(X_train,y_train)

#房价预测

y_br= np.expm1(br.predict(X_test))

 

features={'楼龄' , '建筑面积' , '区域_增城' , '区域_天河' , '区域_海珠’, '区域_番禺' , '区域_白云' ,  '楼层_中楼层' , '楼层_低楼层' , '楼层_独栋' , '楼层_高楼层' , '朝向_东' , '朝向_东北' , '朝向_东南' , '朝向_东西' , '朝向_北' , '朝向_南' , '朝向_南北' , '朝向_西' , '朝向_西北' , '朝向_西南' , '房屋用途_别墅' , '房屋用途_普通住宅'}   

 

#使用GBM模型进行特征选择

gbr= GradientBoostingRegressor(loss='ls', n_estimators=100, max_depth=3,verbose=1, warm_start=True)

gbr_fitted= gbr.fit(X_train, y_train)

 

#查看特征重要度,根据特征重要度可以进行特征选择,选取重要度高的特征,删除不重要的特征以减少维度降低过拟合

print(zip(features,list(gbr.feature_importances_)))

#可见建筑面积是影响房屋价格的最重要特征。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值