利用Python的pandas、pyecharts、sklearn库,对之前从链家网站爬取的北京二手房的数据进行统计和可视化,分析北京二手房价格的影响因素,进而构建随机森林回归模型对北京二手房平均价格进行预测。
环境: Python3.5.2,Anaconda4.2.0;
思路:
- 查看数据基本情况,包含哪些属性、是否存在异常数据和缺失值;
- 运用groupby+pyecharts统计并可视化,分析所在区、所在位置、面积、户型、电梯等各因素对二手房价格的影响;
- 房价预测:运用sklearn构建随机森林回归模型,预测二手房平均价格。
1.数据基本情况
数据包含链家网站上11个区的二手房数据28560条:
- 11个区包括:东城区、西城区、朝阳区、海淀区、丰台区、石景山区、通州区、昌平区、大兴区、顺义区、房山区
- 数据包含属性:所在区、小区名称、户型、面积(平米)、朝向、装修、有无电梯、所在楼层、建造年份、所在位置、总价(万元)、单价(元/平米)。
// An highlighted block
import pandas as pd
import numpy as np
data=pd.read_csv('house_bj.csv',encoding='gb2312')
data.head()
data.info()
data.describe()
2.房价影响因素可视化分析
运用groupby从所在区、所在位置、面积等各方面分组统计,然后用pyecharts可视化。
(1)所在区对房价的影响
// An highlighted block
from pyecharts import Bar, Line, Overlap
zone_price=pd.merge(data.groupby(['所在区'])['单价(元/平米)'].mean().round(0).sort_values(ascending=False).to_frame().reset_index(),data.groupby(['所在区'])['总价(万元)'].mean().round(0).to_frame().reset_index(),on=['所在区'])
bar = Bar("北京各区二手房价格")
bar.add("单价(元/平米)", zone_price['所在区'], zone_price['单价(元/平米)'],is_label_show=True,label_pos='inside',label_text_color ='#000',is_toolbox_show=False,)
line = Line()
line.add("总价(万元)", zone_price['所在区'], zone_price['总价(万元)'],is_label_show=True,is_toolbox_show=False,)
overlap = Overlap(height=400,width=900)
overlap.add(bar)
overlap.add(line,yaxis_index=1, is_add_yaxis=True,)
overlap
根据二手房平均单价将北京各个划分为四个梯队:
- 第一梯队:西城区、东城区、海淀区,平均单价为8万–10万/平米;
- 第二梯队:朝阳区、石景山区、丰台区,平均单价为6万–8万/平米;
- 第三梯队:大兴区、通州区、昌平区、顺义区,平均单价为4万–6万/平米;
- 第四梯队:房山区,平均单价低于4万/平米。
但从总价来看,海淀区的平均总价仅低于西城区,位于第二名;大兴区和通州区的平均总价分别为第五、六名。
(2)户型对房价的影响
#分析户型与二手房价格
from pyecharts import Pie,Bar
layout_price=pd.merge(data.groupby(['户型'])['单价(元/平米)'].mean().round(0).sort_values(ascending=False).to_frame().reset_index(),data.groupby(['户型']).size().to_frame().reset_index(),on='户型')
layout_price.columns =['户型','单价(元/平米)','计数']
print (layout_price)
#进行户型统计,发现存在‘3室0厅’和‘3室3厅’的户型,且数量较少,因此删除
layout_price=layout_price[(layout_price['户型'] !='3室0厅') & (layout_price['户型'] !='3室3厅')]
print(layout_price)
pie = Pie("北京二手房各户型房源数量统计",width=700,height=400,)
pie.add("", layout_price['户型'],layout_price['计数'],is_label_show=True,legend_orient="vertical",legend_pos="right",is_toolbox_show=False,label_formatter='{b}:{c},{d}%')
pie
bar= Bar("北京二手房各户型平均房价(元/平米)",width=600,height=400)
bar.add("",layout_price['户型'],layout_price['单价(元/平米)'],is_label_show=True,bar_category_gap='40%',is_toolbox_show=False)
bar
对二手房进行户型统计发现,北京二手房中2室1厅的户型最多,有12609套(占比44.54%);其次是3室2厅、3室1厅的户型,分别有4822套(占比17.03%)、4468套(占比15.78%)。从各户型平均房价看,北京3室2厅的二手房最便宜,平均单价55996元/平米;而4室2厅的最贵,平均单价为70609;房屋数量最多的2室1厅户型的房屋的价格居中,平均单价为59400元/平米。
(3)所在位置对房价的影响
##分析北京二手房位置和单价
from pyecharts import Bar
#print (data.ix[:,['小区名称','所在区']])
pos_perprice=data.groupby(['所在位置','所在区'])['单价(元/平米)'].mean().round(0).sort_values(ascending=False).to_frame().reset_index()
pos_perprice.columns =['所在位置','所在区','单价(元/平米)']
top10_pos=pos_perprice.head(10).sort_values(['单价(元/平米)'],ascending=True)#.merge(data.ix[:,['小区名称','所在区']],on='小区名称',how = 'left')
bar=Bar('北京二手房平均单价TOP10位置(元/平米)',width=500)
bar.add('',top10_pos['所在位置'],top10_pos['单价(元/平米)'],is_label_show=True,label_text_color='#000',label_pos='inside',label_formatter='{b}:{c}',is_yaxis_show=False,is_convert=True)
bar
##筛选各区域内平均单价排名top5的位置
from pyecharts import Bar
pos_zone=data.groupby(['所在区','所在位置'])['单价(元/平米)'].mean().round(0).sort_values(ascending=False).to_frame().reset_index()
pos_zone.columns =['所在区','所在位置','单价(元/平米)']
top5_pos_zone=pos_zone.assign(rn=pos_zone.sort_values(['单价(元/平米)'], ascending=False)
.groupby(['所在区'])
.cumcount()+1)\
.query('rn < =5').sort_values(['所在区','单价(元/平米)'],ascending=False)
print (top5_pos_zone)
在北京平均单价top10位置中,东城区有4个;西城区3个;海淀区2个;朝阳区1个。其中,西城区六铺炕和海淀区的世纪城的平均单价超过了13万/平米;海淀区的万柳和朝阳区太阳宫的平均单价为12万-13万/平米;其余位置的平均单价为11万-12万/平米。
(4)所在小区对房价的影响
##分析北京二手房所在小区和单价
from pyecharts import Bar
#print (data.ix[:,['小区名称','所在区']])
house_perprice=data.groupby(['小区名称','所在区','所在位置'])['单价(元/平米)'].mean().round(0).sort_values(ascending=False).to_frame().reset_index()
house_perprice.columns =['小区名称','所在区','所在位置','单价(元/平米)']
top10_house=house_perprice.head(10).sort_values(['单价(元/平米)'],ascending=False)#.merge(data.ix[:,['小区名称','所在区']],on='小区名称',how = 'left')
bar=Bar('北京二手房平均单价TOP10小区(元/平米)',width=500)
bar.add('',top10_house['小区名称'],top10_house['单价(元/平米)'],is_label_show=True,label_text_color='#000',label_pos='inside',label_formatter='{b}:{c}',is_yaxis_show=False,is_convert=True)
#bar
top10_house
##筛选各区域内平均单价排名前五的小区
from pyecharts import Bar
house_zone=data.groupby(['所在区','小区名称'])['单价(元/平米)'].mean().round(0).sort_values(ascending=False).to_frame().reset_index()
house_zone.columns =['所在区','小区名称','单价(元/平米)']
top5_hosue_zone=house_zone.assign(rn=house_zone.sort_values(['单价(元/平米)'], ascending=False)
.groupby(['所在区'])
.cumcount()+1)\
.query('rn < =5').sort_values(['所在区','单价(元/平米)'],ascending=False)
print (top5_hosue_zone)
在北京平均单价top10小区中,东城区有4个小区;西城区2个;海淀区3个;朝阳区1个。其中,西城区的六铺炕有2个小区平均单价在top10内。此外,西城区六铺炕三区和海淀区的观山园小区的平均单价超过了13万/平米;其余小区平均单价为12万-13万/平米。
(5)电梯对房价的影响
from pyecharts import Bar,Pie
house_loft= data.groupby(['有无电梯','所在区'])['单价(元/平米)'].agg(['size','mean','sum']).round(0).reset_index()
house_loft.columns=['有无电梯','所在区','数量','单价','总价之和']
print (house_loft)
#value=data[u'有无电梯'].groupby(data[u'所在区']).size().round(0).tolist()
#print (value)
#print (name)
pie = Pie("北京二手房有无电梯房源数量(套)",width=700,height=400,)
pie.add("", ['有电梯','无电梯'],[house_loft[house_loft['有无电梯']=='有电梯']['数量'].sum(),house_loft[house_loft['有无电梯']=='无电梯']['数量'].sum()],is_label_show=True,is_toolbox_show=False,label_formatter='{b}:{c},{d}%')
pie
bar=Bar('北京各区二手房有无电梯房源数量(套)',width=900)
bar.add('有电梯',house_loft[house_loft['有无电梯']=='有电梯']['所在区'],house_loft[house_loft['有无电梯']=='有电梯']['数量'],is_label_show=True,label_text_color='#000',label_pos='inside',is_stack=True)
bar.add('无电梯',house_loft[house_loft['有无电梯']=='无电梯']['所在区'],house_loft[house_loft['有无电梯']=='无电梯']['数量'],is_label_show=True,is_stack=True)
bar
bar=Bar('北京各区有无电梯二手房平均单价(元/平米)',width=900)
bar.add('有电梯',house_loft[house_loft['有无电梯']=='有电梯']['所在区'],house_loft[house_loft['有无电梯']=='有电梯']['单价'],is_label_show=True,mark_line=['average'],is_toolbox_show=False)
bar.add('无电梯',house_loft[house_loft['有无电梯']=='无电梯']['所在区'],house_loft[house_loft['有无电梯']=='无电梯']['单价'],is_label_show=True,mark_line=['average'],is_toolbox_show=False)
bar
#data.groupby(['所在区']).count()
在北京二手房中,有电梯的房源有18132套,占比为63.49%;无电梯的房源有10428套,占比为36.51%。分区看,除昌平区无电梯房数量多于有电梯房,顺义区两种房源数量持平,其余区均是有电梯房源较多。此外,北京二手房中有电梯的二手房平均单价高于无电梯的二手房。但是分区而言,除东城区和西城区无电梯房平均单价高于有电梯房平均单价,其余区无电梯房平均单价低于有电梯房。另外,在有电梯房源中,东城区、朝阳区、海淀区和西城区的平均单价高于北京市平均水平;在无电梯房源中,东城区、海淀区和西城区的平均单价低于北京市的平均水平。
(6)装修类型对房价的影响
from pyecharts import Bar,Pie
fit_up_price=pd.merge(data.groupby(['装修'])['单价(元/平米)'].mean().round(0).sort_values(ascending=False).to_frame().reset_index(),data.groupby(['装修']).size().to_frame().reset_index(),on='装修')
fit_up_price.columns =['装修','单价(元/平米)','计数']
print (fit_up_price)
pie = Pie("北京二手房各装修类型房源数量统计",width=700,height=400,)
pie.use_theme("macarons") #use_theme换图表主题
pie.add("", fit_up_price['装修'],fit_up_price['计数'],is_label_show=True,legend_orient="vertical",legend_pos="right",is_toolbox_show=False,label_formatter='{b}:{c},{d}%')
pie
##统计各装修类型房屋数量及房价
house_fitup= data.groupby(['所在区','装修'])['单价(元/平米)'].agg(['size','mean','sum']).round(0).reset_index()
house_fitup.columns=['所在区','装修','数量','单价','总价之和']
print (house_fitup)
bar=Bar('北京各区二手房精/简装修房源数量(套)',width=900)
bar.use_theme("macarons")
bar.add('精装',house_fitup[house_fitup['装修']=='精装']['所在区'],house_fitup[house_fitup['装修']=='精装']['数量'],is_label_show=True,label_text_color='#000',label_pos='inside',is_stack=True)
bar.add('简装',house_fitup[house_fitup['装修']=='简装']['所在区'],house_fitup[house_fitup['装修']=='简装']['数量'],is_label_show=True,label_text_color='#000',label_pos='inside',is_stack=True)
#bar.add('毛坯',house_fitup[house_fitup['装修']=='毛坯']['所在区'],house_fitup[house_fitup['装修']=='毛坯']['数量'],is_label_show=True,is_stack=True)
bar
bar=Bar("北京各区二手房精/简装修房源单价(元/平米)",width=900)
bar.use_theme("macarons")
bar.add('精装',house_fitup[house_fitup['装修']=='精装']['所在区'],house_fitup[house_fitup['装修']=='精装']['单价'],is_label_show=True,label_text_color='#000',bar_category_gap='30%',mark_line=['average'],is_toolbox_show=False)
bar.add('简装',house_fitup[house_fitup['装修']=='简装']['所在区'],house_fitup[house_fitup['装修']=='简装']['单价'],is_label_show=True,label_text_color='#000',label_pos='inside',bar_category_gap='30%',mark_line=['average'],is_toolbox_show=False)
#bar.add('毛坯',house_fitup[house_fitup['装修']=='毛坯']['所在区'],house_fitup[house_fitup['装修']=='毛坯']['单价'],is_label_show=True,label_text_color='#000',bar_category_gap='30%')
bar
北京二手房中,精装修的房源14702套,占比达51.48%;其次是简装修的房源,占比达39.97%。除丰台区的简装修房源数量多于精装修房源,大兴区两种房源数据持平,其余区都是精装修房源数量多于简装修房源。另外,北京市精装修房源平均单价高于简装修房源。但是分区看,除西城区和丰台区简装修平均单价高于精装修外,其余区都是精装修平均单价高于简装修。在精装修房源中,东城区、朝阳区、海淀区和西城区平均单价超过了北京市平均水平;在简装修房源中,仅东城区、海淀区和西城区平均单价超过了北京市平均水平。
(7)房屋面积对房价的影响
from pyecharts import Scatter
area=set(data['所在区'].values.tolist())
for i in area:
cor=round(data[data['所在区']==i]['面积(平米)'].corr(data[data['所在区']==i]['单价(元/平米)']),2)
print(i,cor)
print(data['面积(平米)'].corr(data['单价(元/平米)']))
#修改所在区名称,画各个区房屋面积与房价的散点图
#v1 = data[data['所在区']=='大兴区']['面积(平米)']
#v2 = data[data['所在区']=='大兴区']['单价(元/平米)']/10000
v1 = data['面积(平米)']
v2 = data['单价(元/平米)']/10000
scatter = Scatter(width=500,height=300)
scatter.add("房屋面积与单价关系", v1, v2, legend_pos="40%",yaxis_name='单价(万元/平米)',yaxis_name_pos='end',xaxis_name='面积')
scatter
通过相关系数和散点图,房屋面积和房价之间的线性关系不明显。
3.北京二手房房价预测
通过上面的分析可得,所在区、所在位置、小区、面积等都会对房价产生影响,因此用这些因素构建模型预测房价。
- 将除面积之外的其他属性,如所在区、户型等类别属性进行编码;并划分训练集和验证集。
from sklearn import preprocessing
from sklearn.model_selection import train_test_split
le = preprocessing.LabelEncoder()
X1=le.fit(data['所在区'].unique()).transform(data['所在区']) #X1表示房屋所在区
X2=le.fit(data['户型'].unique()).transform(data['户型']) #X2表示房屋的户型
X3=data['面积(平米)'] #X3表示房屋的面积
X4=le.fit(data['装修'].unique()).transform(data['装修']) #X4表示房屋的装修
X5=le.fit(data['有无电梯'].unique()).transform(data['有无电梯']) #X5表示房屋有无电梯
X6=le.fit(data['小区名称'].unique()).transform(data['小区名称']) #X5表示房屋所在小区
X7=le.fit(data['所在位置'].unique()).transform(data['所在位置']) #X5表示房屋所在位置
X=np.mat([X1,X2,X3,X4,X5,X6,X7]).T
Y=data['单价(元/平米)']
print (np.array(X).shape)
#划分训练集和验证集
X_train,X_test,Y_train,Y_test= train_test_split(X,Y,test_size=0.1, random_state=0)
print(X_train.shape, Y_train.shape)
print(X_test.shape, Y_test.shape)
- 利用RandomizedSearchCV进行模型调参;
#随机森林模型调参
##参数选择
from sklearn.model_selection import RandomizedSearchCV
criterion=['mse','mae']
n_estimators = [int(x) for x in np.linspace(start = 200, stop = 1200, num = 50)]
max_features = ['auto', 'sqrt']
max_depth = [int(x) for x in np.linspace(10, 100, num = 10)]
max_depth.append(None)
min_samples_split = [2, 5, 10]
min_samples_leaf = [1, 2, 4]
bootstrap = [True, False]
random_grid = {'criterion':criterion,
'n_estimators': n_estimators,
'max_features': max_features,
'max_depth': max_depth,
'min_samples_split': min_samples_split,
'min_samples_leaf': min_samples_leaf,
'bootstrap': bootstrap}
#构建模型
clf= RandomForestRegressor()
clf_random = RandomizedSearchCV(estimator=clf, param_distributions=random_grid,
n_iter = 10,
cv = 3, verbose=2, random_state=42, n_jobs=1)
#回归
clf_random.fit(X_train, Y_train)
print (clf_random.best_params_)
经过调参选择的最优参数为:
{‘n_estimators’: 506, ‘min_samples_split’: 10, ‘min_samples_leaf’: 4, ‘max_features’: ‘sqrt’, ‘max_depth’: 70, ‘criterion’: ‘mse’, ‘bootstrap’: True}
- 模型训练及评估
##构建随机森林模型
##模型训练、验证、评估
from sklearn.ensemble import RandomForestRegressor
from pyecharts import Bar
#{'n_estimators': 506, 'min_samples_split': 10, 'min_samples_leaf': 4, 'max_features': 'sqrt', 'max_depth': 70, 'criterion': 'mse', 'bootstrap': True}
rf=RandomForestRegressor(criterion='mse',bootstrap=True,max_features='sqrt', max_depth=70,min_samples_split=10, n_estimators=506,min_samples_leaf=4)
rf.fit(X_train, Y_train)
Y_train_pred=rf.predict(X_train)
Y_test_pred=rf.predict(X_test)
#变量重要性
print ("变量重要性:",rf.feature_importances_)
feature=['所在区','户型','面积','装修','有无电梯','小区名称','所在位置']
bar=Bar()
bar.add('指标重要性',feature, rf.feature_importances_.round(2),is_label_show=True,label_text_color='#000')
bar.render('指标重要性.html')
#bar
对北京二手房房价影响因素排名依次为:所在区、所在位置和房屋面积,这与我们平时的认知相符合,好的地段房价自然就高,比如学区房。
#模型评估
from sklearn.metrics import mean_squared_error,explained_variance_score,mean_absolute_error,r2_score
print ("决策树模型评估--训练集:")
print ('训练r^2:',rf.score(X_train,Y_train))
print ('均方差',mean_squared_error(Y_train,Y_train_pred))
print ('绝对差',mean_absolute_error(Y_train,Y_train_pred))
print ('解释度',explained_variance_score(Y_train,Y_train_pred))
print ("决策树模型评估--验证集:")
print ('验证r^2:',rf.score(X_test,Y_test))
print ('均方差',mean_squared_error(Y_test,Y_test_pred))
print ('绝对差',mean_absolute_error(Y_test,Y_test_pred))
print ('解释度',explained_variance_score(Y_test,Y_test_pred))
- 房价预测
从链家网站重新爬取11个区共1620条数据,用构建的模型预测房价。
from sklearn import preprocessing
data_pre=pd.read_csv('house_bj_new.csv',encoding='gb18030')
data_pre.head()
##对类别属性进行编码
le = preprocessing.LabelEncoder()
x1=le.fit(data_pre['所在区'].unique()).transform(data_pre['所在区']) #X1表示房屋所在区
x2=le.fit(data_pre['户型'].unique()).transform(data_pre['户型']) #X2表示房屋的户型
x3=data_pre['面积(平米)'] #X3表示房屋的面积
x4=le.fit(data_pre['装修'].unique()).transform(data_pre['装修']) #X4表示房屋的装修
x5=le.fit(data_pre['有无电梯'].unique()).transform(data_pre['有无电梯']) #X5表示房屋有无电梯
x6=le.fit(data_pre['小区名称'].unique()).transform(data_pre['小区名称']) #X5表示房屋所在小区
x7=le.fit(data_pre['所在位置'].unique()).transform(data_pre['所在位置']) #X5表示房屋所在位置
x=np.mat([x1,x2,x3,x4,x5,x6,x7]).T
print (np.array(x).shape)
#对房价进行预测并保存到house_pre.csv文件中
y_pred=rf.predict(x)
print ('######')
result=pd.DataFrame()
result['所在区']=data_pre['所在区']
result['小区名称']=data_pre['小区名称']
result['所在位置']=data_pre['所在位置']
result['真实值']=data_pre['单价(元/平米)']
result['预测值']=y_pred.round(2)
result.head()
result.to_csv("house_pre.csv",encoding='gb18030')
需要预测的数据集:
预测结果数据:
- 可视化预测的房价
调用百度地图API获取每个位置的经纬度,利用pyecharts的geo绘制自定义地图,将预测的二手房房价按所在位置取平均值并显示在地图上。
##调用百度地图,获取每个位置经纬度
# -*- coding: utf-8 -*-
import json
from urllib.request import urlopen, quote
import pandas as pd
import numpy as np
ak = '############' # ak是申请的调用百度地图的key值
##根据地址名称获取经纬度函数
def getlnglat(address):
url = 'http://api.map.baidu.com/geocoder/v2/'
output = 'json'
add = quote(address) #由于本文城市变量为中文,为防止乱码,先用quote进行编码
uri = url + '?' + 'address=' + add + '&output=' + output + '&ak=' + ak
req = urlopen(uri)
res = req.read().decode() #将其他编码的字符串解码成unicode
temp = json.loads(res) #对json数据进行解析
return temp
data=pd.read_csv('house_pre.csv',encoding='gb18030')
print (data.head())
#将预测值按小区名称分组统计
df=data.groupby(['所在位置','所在区'])['真实值','预测值'].mean().round(2).reset_index()
add=[]
for i in range(len(df)):
df.iloc[i,0]
add.append('北京市'+df.iloc[i,1]+df.iloc[i,0])
df['地址']=add
lat=[]
lng=[]
for addr in df[u'地址'].values:
res = getlnglat(addr)
lat.append(res['result']['location']['lat'])
lng.append(res['result']['location']['lng'])
df[u'维度']=lat
df[u'经度']=lng
print (df.head())
df.to_csv('house_add.csv',index=False,encoding='gb18030')
df.head()
from pyecharts import Geo
import pandas as pd
df=pd.read_csv('house_add.csv',encoding='gb18030')
geo_cities_coords={df.iloc[i]['地址']:[df.iloc[i]['经度'],df.iloc[i]['维度']] for i in range(len(df))}
attr=list(df['地址'])
value=list(df['预测值'])
geo = Geo("北京市各区二手房房价量", "预测值", title_color="#fff",
title_pos="left", width=1000,
height=600, background_color='#404a59')
##自定义坐标点
for i in range(len(df)):
geo.add_coordinate(df.iloc[i]['地址'],df.iloc[i]['经度'],df.iloc[i]['维度'])
geo.add("", attr,value, maptype='北京', visual_range=[min(value), max(value)], visual_text_color="#fff",
symbol_size=15, is_visualmap=True,is_geo_effect_show=True,type='effectScatter',border_color = '#eef')
geo#直接在notebook中显示
自定义坐标点的官方网址:https://github.com/pyecharts/geo-region-coords
推荐能够查看大部分图表的地址:http://pyecharts.herokuapp.com/geo