从数据爬取到决策树建模——预测北京二手房房价
一、项目背景
北京地区的房价一直是广大家庭所关注的焦点话题。本次研究项目的主要目标是深入剖析北京市二手住宅市场价格规律。基于Scrapy框架的技术支持,本项目采用了系统性的方式获取链家网站上详尽的二手房房源数据。对采集到的信息进行了基础性的统计分析,并通过可视化工具构建相应的数据展示模块。运用决策树算法模型对未来的市场走势进行了科学预测。最后阶段,则重点考察了训练过程中的学习曲线变化情况,并评估模型是否陷入了过拟合的风险状态。(仅供参考)
二、爬取数据
链家网站的二手房房源信息展示如下:

共有77049条房源信息,并非全部公开展示。根据现有设置,默认情况下仅提供前100页的数据结果,默认每页包含30条记录。本数据集未启用反爬机制,默认情况下即可进行数据抓取。本文主要关注并提取了标有红色方框包围的11个字段信息:包括:房源详细描述(如价格走势)、地理位置信息(如周边商圈)、空间布局规划(如功能分区)、面积大小(如平面面积)、朝向方位(如南北向)、室内装修状况(如材料选择)、所处楼层信息以及建筑建造时间等详细参数,并对上述各项进行了深入解析与数据分析

如下是具体的爬虫代码:
item.py
import scrapy
class LianjiaspiderprojectItem(scrapy.Item):
# define the fields for your item here like:
Description = scrapy.Field() #房源描述
Location = scrapy.Field() #房源所在位置
Layout = scrapy.Field() #房源布局
Size = scrapy.Field() #房源面积
Direction = scrapy.Field() #房源朝向
Renovation = scrapy.Field() #房源装修情况
Floorinfo = scrapy.Field() #房源所在楼层信息
Year = scrapy.Field() #房源建造年份
Type = scrapy.Field() #房源类型
Price = scrapy.Field() #房源总价
unitPrice = scrapy.Field() #房源单价
pass
import scrapy
from lxml import etree
from LianjiaSpiderProject.items import LianjiaspiderprojectItem
class LianjiaSpider(scrapy.Spider):
name = 'lianjia'
#allowed_domains = ['www.xxx.com']
start_urls = ['https://bj.lianjia.com/ershoufang/pg1/']
initial_url = "https://bj.lianjia.com/ershoufang/pg"
current_page = 2
def parse(self, response):
#获取第一页中所有房源信息所在的标签,其中每页包括30条房源信息,即30条li标签
sell_list = response.xpath('//ul[@class="sellListContent"]/li')
#对30条li标签进行解析获取相应的房源信息
for sell in sell_list:
Houseinfo = sell.xpath('./div[1]/div[@class="address"]/div//text()').extract()[0]
if len(Houseinfo.split(' | ')) == 7:
Layout = Houseinfo.split(' | ')[0]
Size = Houseinfo.split(' | ')[1]
Direction = Houseinfo.split(' | ')[2]
Renovation = Houseinfo.split(' | ')[3]
Floorinfo = Houseinfo.split(' | ')[4]
Year = Houseinfo.split(' | ')[5]
Type = Houseinfo.split(' | ')[6]
else:
break
Description = sell.xpath('./div[1]/div[@class="title"]/a/text()').extract()[0]
Location = sell.xpath('./div[1]/div[@class="flood"]//text()').extract()
Location_new = "".join([x.strip() for x in Location if len(x.strip()) > 0]) # 去除列表中的空格和空字符串,并将其拼接成一个字符串
Price = sell.xpath('./div[1]/div[@class="priceInfo"]/div[1]//text()').extract()
Price_new = "".join(Price)
unitPrice = sell.xpath('./div[1]/div[@class="priceInfo"]/div[2]//text()')[0].extract()
#将爬取的数据与item文件里面的数据对应起来
item = LianjiaspiderprojectItem()
item['Description'] = Description
item['Location'] = Location_new
item['Layout'] = Layout
item['Size'] = Size
item['Direction'] = Direction
item['Renovation'] = Renovation
item['Floorinfo'] = Floorinfo
item['Year'] = Year
item['Type'] = Type
item['Price'] = Price_new
item['unitPrice'] = unitPrice
yield item
#链家只展示了100页的内容,抓完100页就停止爬虫
#组装下一页要抓取的网址
if self.current_page != 101:
new_url = self.initial_url + str(self.current_page) + '/'
print('starting scrapy url:', new_url)
yield scrapy.Request(new_url, callback=self.parse)
self.current_page += 1
else:
print('scrapy done')
pass
from itemadapter import ItemAdapter
import csv
class LianjiaspiderprojectPipeline(object):
fp = None
index = 0
#该方法只在爬虫开始的时候被调用一次
def open_spider(self, spider):
print('开始爬虫......')
self.fp = open('./lianjia.csv', 'a', encoding='utf-8')
def process_item(self, item, spider):
Description = item['Description']
Location = item['Location']
Layout = item['Layout']
Size = item['Size']
Direction = item['Direction']
Renovation = item['Renovation']
Floorinfo = item['Floorinfo']
Year = item['Year']
Type = item['Type']
Price = item['Price']
unitPrice = item['unitPrice']
if self.index == 0:
columnnames = "房源描述,位置,布局,面积,朝向,装修情况,楼层,建造年份,类型,总计,单价"
self.fp.write(columnnames+'\n')
self.index = 1
self.fp.write("{},{},{},{},{},{},{},{},{},{},{}\n".format(Description, Location, Layout, Size, Direction, Renovation, Floorinfo, Year, Type, Price, unitPrice))
return item
def close_spider(self, spider):
print('爬虫结束!')
self.fp.close()
pass
三、导入爬取的数据
#导入相关库
import warnings
warnings.filterwarnings('ignore')
import numpy as np
import pandas as pd
from IPython.display import display
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns
sns.set(style='darkgrid',context='notebook',font_scale=1.5) # 设置背景
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False #处理中文和坐标负号显示
#导入链家网站二手房数据集
lianjia = pd.read_csv('lianjia.csv')
display(lianjia.head())
Direction District Elevator Floor Garden Id Layout Price Region Renovation Size Year
0 东西 灯市口 NaN 6 锡拉胡同21号院 101102647043 3室1厅 780.0 东城 精装 75.0 1988
1 南北 东单 无电梯 6 东华门大街 101102650978 2室1厅 705.0 东城 精装 60.0 1988
2 南西 崇文门 有电梯 16 新世界中心 101102672743 3室1厅 1400.0 东城 其他 210.0 1996
3 南 崇文门 NaN 7 兴隆都市馨园 101102577410 1室1厅 420.0 东城 精装 39.0 2004
4 南 陶然亭 有电梯 19 中海紫御公馆 101102574696 2室2厅 998.0 东城 精装 90.0 2010
lianjia.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 23677 entries, 0 to 23676
Data columns (total 12 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Direction 23677 non-null object
1 District 23677 non-null object
2 Elevator 15440 non-null object
3 Floor 23677 non-null int64
4 Garden 23677 non-null object
5 Id 23677 non-null int64
6 Layout 23677 non-null object
7 Price 23677 non-null float64
8 Region 23677 non-null object
9 Renovation 23677 non-null object
10 Size 23677 non-null float64
11 Year 23677 non-null int64
dtypes: float64(2), int64(3), object(7)
memory usage: 2.2+ MB
lianjia.describe()
Floor Id Price Size Year
count 23677.000000 2.367700e+04 23677.000000 23677.000000 23677.000000
mean 12.765088 1.011024e+11 610.668319 99.149301 2001.326519
std 7.643932 5.652477e+05 411.452107 50.988838 9.001996
min 1.000000 1.010886e+11 60.000000 2.000000 1950.000000
25% 6.000000 1.011022e+11 365.000000 66.000000 1997.000000
50% 11.000000 1.011025e+11 499.000000 88.000000 2003.000000
75% 18.000000 1.011027e+11 717.000000 118.000000 2007.000000
max 57.000000 1.011028e+11 6000.000000 1019.000000 2017.000000
观察结果表明:该数据集中共有23677条记录(样本),涉及共11个特征字段(指标)。其中price字段是我们关注的目标变量(因变量)。elevator字段存在缺失记录(缺失值)。此外,在统计指标中发现size字段的最大面积达到约1019平方米(m²),最小仅2平方米(m²)。这种极端差异在现实场景中不太可能发生(合理推测),因此这可能是一个异常观测点(outlier),会对模型性能产生显著影响(影响)。需要注意的是,“异常观测点”这一结论目前还处于初步阶段(阶段判断),尚未得到验证
#添加新特征-房屋均价
df = lianjia.copy()
df['PerPrice'] = lianjia['Price']/lianjia['Size']
#调整各个特征的排列顺序,其中'Id'特征实际意义不大,故将其移除
columns = ['Region', 'District', 'Garden', 'Layout', 'Floor', 'Year', 'Size', \
'Elevator', 'Direction', 'Renovation', 'PerPrice', 'Price']
df = pd.DataFrame(df, columns = columns)
#重新审视数据
display(df.head())
Region District Garden Layout Floor Year Size Elevator Direction Renovation PerPrice Price
0 东城 灯市口 锡拉胡同21号院 3室1厅 6 1988 75.0 NaN 东西 精装 10.400000 780.0
1 东城 东单 东华门大街 2室1厅 6 1988 60.0 无电梯 南北 精装 11.750000 705.0
2 东城 崇文门 新世界中心 3室1厅 16 1996 210.0 有电梯 南西 其他 6.666667 1400.0
3 东城 崇文门 兴隆都市馨园 1室1厅 7 2004 39.0 NaN 南 精装 10.769231 420.0
4 东城 陶然亭 中海紫御公馆 2室2厅 19 2010 90.0 有电梯 南 精装 11.088889 998.0
四、数据可视化分析
Region特征分析
#对Region进行分组,研究不同区域的二手房数量、单价和总价情况
df_house_count = df.groupby('Region').count()['Price'].sort_values(ascending=False).reset_index().rename({'Price':'Count'},axis=1)
df_house_mean = df.groupby('Region').mean()['PerPrice'].sort_values(ascending=False).reset_index().rename({'PerPrice':'MeanPrice'},axis=1)
plt.figure(figsize=(20,30))
plt.subplot((311))
sns.barplot(x='Region', y='Count', palette='Blues_d', data=df_house_count)
plt.title('北京各区域二手房数量对比', fontsize=15)
plt.xlabel('区域')
plt.ylabel('数量')
plt.subplot((312))
sns.barplot(x='Region', y='MeanPrice', palette='Greens_d', data=df_house_mean)
plt.title('北京各区域二手房单价对比', fontsize=15)
plt.xlabel('区域')
plt.ylabel('每平米单价')
plt.subplot((313))
sns.boxplot(x='Region', y='Price', palette='Blues_d', data=df)
plt.title('北京各区域二手房房价对比', fontsize=15)
plt.xlabel('区域')
plt.ylabel('房总价')
plt.show()

- 二手房数量方面数据显示:海淀区与朝阳区并列第一,在售二手房约为3千套左右。随后是丰台区,在过去几年里正快速推进改建工程,并呈现出赶超的趋势。
- 在二手房价格方面统计结果表明:西城区房价最高约为1.1万元/平方米的原因在于其位于二环核心地段并且集中了众多优质教育资源房型产品。紧随其后的是东城区售价约1.0万元/平方米之后依次为海淀区售价约850元/平方米其余区域则多在8万元以下徘徊。
- 关于二手房总价数据呈现中位数普遍低于一千万元的特点其中间值普遍低于一千万元同时各个地区的售价呈现较高的离散程度值得注意的是西城某楼盘售价高达六千万远超其他区域水平这反映出该地区房价分布并非典型的正态分布特征。
Size特征分析
plt.figure(figsize=(20,20))
plt.subplot((211))
#二手房面积的分布情况
sns.distplot(df['Size'], bins=20, color='r', kde=True, kde_kws={'color':'steelblue','shade':True,'linewidth':6})
#二手房价格与面积的关系
plt.subplot((212))
sns.regplot(x='Size', y='Price', data=df)
plt.show()

- Size的分布情况:通过分析直方图(distplot)和核密度估计图(KDE),二手房面积呈现出长尾型的分布特征,在这种情况下存在大量超出常规范围的房屋面积。
- Size与Price之间的关系:Price与Size之间呈现出明显的线性关联,在大多数情况下面积越大则价格越高这一现象符合市场规律。然而需要特别关注两种异常情况:一是房屋面积小于10平方米却价格超过1千万;二是房屋面积超过千平方米却价格偏低的情况需要进一步核实具体情况。
df.loc[df['Size']<10]
df.loc[df['Size']>1000]
*通过检查发现:这一异常点并非普通的普通住宅,并且很可能属于商业用途的房产。因为仅有1间卧室且无卫生间,并且其面积超过约93平方英尺(即约8.6平方米),因此决定将其从项目中剔除。
#移除异常数据
df = df[(df['Layout']!='叠拼别墅') & (df['Size']<1000)]
#重新绘制Size分布、Size与Price之间的关系
plt.figure(figsize=(20,20))
plt.subplot((211))
#二手房面积的分布情况
sns.distplot(df['Size'], bins=20, color='r', kde=True, kde_kws={'color':'steelblue','shade':True,'linewidth':6})
#二手房价格与面积的关系
plt.subplot((212))
sns.regplot(x='Size', y='Price', data=df)
plt.show()

Layout特征分析
plt.figure(figsize=(20,20))
sns.countplot(y='Layout', data=df)
plt.title('户型', fontsize=15)
plt.xlabel('数量')
plt.ylabel('户型')
plt.show()

通过细致观察可以看出:户型类别繁多,在主流居住需求中占据主导地位的是2室1厅的住宅 layouts. 在深入分析 layouts 的特征后发现,在分类体系中存在命名方式不统一的问题. 例如有些 layout 被称作2室1厅 有些则被称作2房间1卫 这样的差异性容易造成混淆. 因此在进行机器学习建模前必须对数据特征进行规范化处理
Renovation特征分析
df['Renovation'].value_counts()
精装 11345
简装 8496
其他 3239
毛坯 576
Name: Renovation, dtype: int64
#绘制Renovation各分类的数量情况
plt.figure(figsize=(15,30))
plt.subplot((311))
sns.countplot(df['Renovation'])
#绘制Renovation与Price之间的关系
plt.subplot((312))
sns.barplot(x='Renovation', y='Price', data=df)
plt.subplot((313))
sns.boxplot(x='Renovation', y='Price', data=df)
plt.show()

通过观察分析得出:精装修二手房的数量占比最大;其后是简约装修类房产;同时发现毛坯类型的房屋价格最高;其后是精装修类型的.
Elevator特征分析
df['Elevator'].value_counts()
有电梯 9341
无电梯 6078
Name: Elevator, dtype: int64
#查看缺失值
df.loc[df['Elevator'].isnull()].shape
(8237, 12)
本研究针对Elevator特征的8237条缺失记录进行了处理。研究思路表明,在本研究中采用填补法对缺失值进行填充,并基于经验规律构建了填充模型:即当楼层超过6层时具备电梯;而当楼层低于或等于6层时通常不具备电梯。
#填补缺失值
df.loc[(df['Floor']>6)&(df['Elevator'].isnull()), 'Elevator'] = '有电梯'
df.loc[(df['Floor']<=6)&(df['Elevator'].isnull()), 'Elevator'] = '无电梯'
#绘制Elevator不同类别的数量
plt.figure(figsize=(20,15))
plt.subplot((121))
sns.countplot(df['Elevator'])
plt.title('有无电梯数量对比',fontsize=15)
plt.xlabel('是否有电梯',fontsize=15)
plt.ylabel('数量',fontsize=15)
#绘制Elevator类别与Price的关系
plt.subplot((122))
sns.barplot(x='Elevator', y='Price', data=df)
plt.title('有无电梯房价对比',fontsize=15)
plt.xlabel('是否有电梯',fontsize=15)
plt.ylabel('房价',fontsize=15)
plt.show()

通过观察分析发现:拥有电梯的二手房数量较多,在城市中尤其是像北京这样的大城市里人口规模庞大对居住需求也有很高的要求因此为了满足这些需求就必须要有相应的电梯系统支持。与此同时在拥有电梯的情况下房价通常会比较高
Year特征分析
#绘制Elevator和Renovation不同的分类情况下,Year与Price的关系
grid = sns.FacetGrid(df, row='Elevator', col='Renovation', palette='pal', size=5) #size可调节图形间距
grid.map(plt.scatter, 'Year', 'Price')
grid.add_legend()
plt.show()

经过观察发现:
- 随着房龄的增长,二手房的价格逐渐下降;
- 相比20世纪90年代,近年来新建二手房价格呈现出明显上涨趋势;
- 直至1980年前,基本看不到带电梯的新建住宅,这表明在此之前小区普遍未安装电梯;
- 即使是在没有电梯的情况下,大多数二手房都是简约风格,精装修的比例相对较低。
Floor特征分析
#绘制不同楼层的数量关系
plt.figure(figsize=(20,8))
sns.countplot(x='Floor', data=df)
plt.title('二手房楼层',fontsize=15)
plt.xlabel('楼层')
plt.ylabel('数量')
plt.show()

在调查过程中发现:6层的二手房数量最多;然而单个楼层本身并没有太多意义(因为各个小区所拥有的总楼层数存在差异)。通常情况下来说,在多数情况下中间楼层往往受到较高程度的关注(价格较高),而底层和顶层则不太受关注,并且其价格相对较低。
四、特征工程
机器学习之前需要对数据进一步处理,具体如下:
Layout特征
#只保留'xx室xx厅'数据,将其它少数房间与卫移除
df = df.loc[df['Layout'].str.extract('^\d(.*?)\d.*?', expand=False)=='室']
#提取'室'和'厅'创建新特征
df['Layout_room_num'] = df['Layout'].str.extract('(^\d).*', expand=False).astype('int64')
df['Layout_hall_num'] = df['Layout'].str.extract('^\d.*?(\d).*', expand=False).astype('int64')
df['Layout'].value_counts()
2室1厅 9485
3室1厅 3999
3室2厅 2765
1室1厅 2681
2室2厅 1671
4室2厅 930
1室0厅 499
4室1厅 295
5室2厅 200
4室3厅 96
5室3厅 75
1室2厅 67
6室2厅 59
2室0厅 50
3室3厅 43
6室3厅 29
3室0厅 29
5室1厅 27
7室3厅 7
7室2厅 6
2室3厅 5
8室3厅 4
4室4厅 4
5室4厅 4
6室4厅 4
8室2厅 3
4室0厅 3
6室0厅 2
9室3厅 1
9室2厅 1
7室1厅 1
8室5厅 1
5室0厅 1
8室4厅 1
6室5厅 1
1室3厅 1
9室1厅 1
6室1厅 1
Name: Layout, dtype: int64
Year特征
#对Year特征进行分箱,等频分段(按年限,Year划分太细,故将连续型数值Year特征离散化)
df['Year'] = pd.qcut(df['Year'],8).astype('object')
df['Year'].value_counts()
(2000.0, 2003.0] 3705
(2004.0, 2007.0] 3369
(1990.0, 1997.0] 3110
(1949.999, 1990.0] 3024
(1997.0, 2000.0] 2829
(2010.0, 2017.0] 2687
(2007.0, 2010.0] 2571
(2003.0, 2004.0] 1757
Name: Year, dtype: int64
Direction特征
df['Direction'].value_counts()
南北 11367
南 2726
东西 1388
东南 1311
西南 1094
东 843
西 802
西北 733
东北 645
北 484
东南北 465
南西北 370
南西 158
东西北 139
东南西 133
西南北 124
东南西北 90
西南东北 23
南东北 19
东南西南 15
东南南 13
东东南 10
西东北 10
西南西北 10
东西南 9
南西南 9
东南东北 7
西南西 5
东南南北 5
南西东北 3
东西南北 3
西西南 2
东东北 2
东北东北 2
南西西北 2
西北东北 2
西西北 2
南西北北 2
南东 2
南西南北 2
西北北 2
南西南西 2
西南西北东北 1
南北东 1
东南南西北 1
东西北东北 1
东南西北北 1
东南西北东北 1
东西北北 1
北南 1
东西东北 1
西南西北北 1
南北西 1
东南西南东北 1
东南北西 1
东南西南北 1
东东南南 1
南北东北 1
北西 1
Name: Direction, dtype: int64
建立一个函数用于对Direction中的特征值进行去重处理,并去除一些不符合逻辑的组合结果。例如,在方向序列中出现的类似情况如'西南北'与'南西北'等。
list_one_num = ['东','西','南','北']
list_two_num = ['东西','东南','东北','西南','西北','南北']
list_three_num = ['东西南','东西北','东南北','西南北']
list_four_num = ['东西南北']
def direct_func(x):
if not isinstance(x, str):
raise TypeError
x = x.strip()
x_len = len(x)
x_list = pd.unique([y for y in x])
if x_len != len(x_list):
return 'no'
if (x_len==2)&(x not in list_two_num):
m0=x[0]
m1=x[1]
return m1+m0
elif (x_len==3)&(x not in list_three_num):
for n in list_three_num:
if (x_list[0] in n)&(x_list[1] in n)&(x_list[2] in n):
return n
elif (x_len==4)&(x not in list_four_num):
return list_four_num[0]
else:
return x
df['Direction'] = df['Direction'].apply(direct_func)
df = df.loc[(df['Direction']!='no')&(df['Direction']!='nan')]
df['Direction'].value_counts()
南北 11368
南 2726
东西 1388
东南 1313
西南 1252
东 843
西 802
西北 734
东北 645
西南北 495
东南北 485
北 484
东西北 149
东西南 142
东西南北 120
Name: Direction, dtype: int64
创建新特征
#每个房间的平均面积
df['Layout_total_num'] = df['Layout_room_num'] + df['Layout_hall_num']
df['Size_room_ratio'] = df['Size']/df['Layout_total_num']
删除旧的特征
#删除无用特征
df = df.drop(['Layout','PerPrice','Garden'], axis=1)
display(df.head())
Region District Floor Year Size Elevator Direction Renovation Price Layout_room_num Layout_hall_num Layout_total_num Size_room_ratio
0 东城 灯市口 6 (1949.999, 1990.0] 75.0 无电梯 东西 精装 780.0 3 1 4 18.75
1 东城 东单 6 (1949.999, 1990.0] 60.0 无电梯 南北 精装 705.0 2 1 3 20.00
2 东城 崇文门 16 (1990.0, 1997.0] 210.0 有电梯 西南 其他 1400.0 3 1 4 52.50
3 东城 崇文门 7 (2003.0, 2004.0] 39.0 有电梯 南 精装 420.0 1 1 2 19.50
4 东城 陶然亭 19 (2007.0, 2010.0] 90.0 有电梯 南 精装 998.0 2 2 4 22.50
对object类型的特征进行One-hot编码
#定义函数,One-hot编码
def one_hot_encoder(df, nan_as_category=True):
original_columns = list(df.columns)
categorical_columns = [col for col in df.columns if df[col].dtype=='object']
df = pd.get_dummies(df, columns=categorical_columns, dummy_na=nan_as_category)
new_columns = [c for c in df.columns if c not in original_columns]
return df, new_columns
df, df_cat = one_hot_encoder(df)
特征相关性
colormap = plt.cm.RdBu
plt.figure(figsize=(30,20))
sns.heatmap(df.corr(), linewidths=0.1, vmax=1.0, square=True, cmap=colormap, linecolor='white', annot=True)

颜色呈现明显红色或蓝色均表明两者间的关联程度较高;即这两个特征对目标变量的作用强度基本相当;这提示我们应关注存在明显的冗余信息;进而可依据这些分析结果筛选出具有独特意义的特征;以避免模型过拟合的问题。
五、决策树算法预测房价
采用基于Cart决策树的回归模型用于二手房价格数据的预测。
通过交叉验证方法最大化利用训练数据量,并尽量避免因数据划分不均而导致的信息损失。
采用网格搜索技术来优化模型参数设置。
借助学习曲线工具分析模型是否出现过拟合现象。
prices = df['Price'] #目标值
features = df.drop(['Price'], axis=1) #特征值
prices = np.array(prices)
features = np.array(features)
#建立决策树回归模型
from sklearn.model_selection import train_test_split
from sklearn.model_selection import KFold
from sklearn.tree import DecisionTreeRegressor
from sklearn.model_selection import GridSearchCV
#利用GridSearchCV计算最优解
def fit_model(features, prices):
features_train, features_test, prices_train, prices_test = train_test_split(features, prices, test_size=0.2, random_state=22)
corss_validation = KFold(10, shuffle=True)
regressor = DecisionTreeRegressor()
params = {'max_depth':[1,2,3,4,5,6,7,8,9,10]}
grid = GridSearchCV(estimator=regressor, param_grid=params, cv=corss_validation)
grid.fit(features_train, prices_train)
print('预测的准确率为:',grid.score(features_test, prices_test))
print('选择的决策树深度为{}'.format(grid.best_estimator_))
print('交叉验证中最好的结果为{}'.format(grid.best_score_))
return None
fit_model(features, prices)
# 可视化模拟学习曲线,观察是否出现过拟合问题。
from sklearn.model_selection import learning_curve, validation_curve
from sklearn.model_selection import ShuffleSplit
#绘制Learning_curve曲线
def ModelLearning(X, y):
cv = ShuffleSplit(n_splits=10, test_size=0.2, random_state=0)
train_sizes = np.rint(np.linspace(1, X.shape[0]*0.8-1, 10)).astype(int)
#print(train_sizes)
fig = plt.figure(figsize=(30,20))
for k, depth in enumerate([1,3,6,10]):
regressor = DecisionTreeRegressor(max_depth=depth)
sizes, train_scores, test_scores = learning_curve(regressor, X, y, cv=cv, train_sizes=train_sizes)
train_std = np.std(train_scores, axis=1)
train_mean = np.mean(train_scores, axis=1)
valid_std = np.std(test_scores, axis=1)
valid_mean = np.mean(test_scores, axis=1)
ax = fig.add_subplot(2, 2, k+1)
ax.plot(sizes, train_mean, 'o-', color='r', linewidth=6, markersize=15, label='Training Score')
ax.plot(sizes, valid_mean, 'o-', color='g', linewidth=6, markersize=15, label='Validation score')
ax.fill_between(sizes, train_mean - train_std, train_mean + train_std, alpha=0.15, color='r')
ax.fill_between(sizes, valid_mean - valid_std, valid_mean + valid_std, alpha=0.15, color='g')
ax.set_title('max_depth={}'.format(depth), fontsize=30)
ax.set_xlabel('训练集数量', fontsize=30)
ax.set_ylabel('score', fontsize=30)
ax.set_xlim([0, X.shape[0]*0.8])
ax.set_ylim([-0.05, 1.05])
ax.legend(bbox_to_anchor=(1.05,2.05), loc='upper right', fontsize=30)
fig.show()
#绘制Validation_curve曲线
def ModelComplexity(X, y):
cv = ShuffleSplit(n_splits=10, test_size=0.2, random_state=0)
max_depth = np.arange(1,11)
train_scores, valid_scores = validation_curve(DecisionTreeRegressor(), X, y, param_name='max_depth', param_range=max_depth, cv=cv)
train_std = np.std(train_scores, axis=1)
train_mean = np.mean(train_scores, axis=1)
valid_std = np.std(valid_scores, axis=1)
valid_mean = np.mean(valid_scores, axis=1)
plt.figure(figsize=(8,6))
plt.plot(max_depth, train_mean, 'o-', color='r', label='Training Score', linewidth=3, markersize=6)
plt.plot(max_depth, valid_mean, 'o-', color='g', label='Validation Score', linewidth=3, markersize=6)
plt.fill_between(max_depth, train_mean - train_std, train_mean + train_std, alpha=0.15, color='r')
plt.fill_between(max_depth, valid_mean - valid_std, valid_mean + valid_std, alpha=0.15, color='g')
plt.legend(loc='lower right')
plt.xlabel('max_depth')
plt.ylabel('Score')
plt.ylim([-0.05, 1.05])
plt.show()
ModelLearning(features_train, prices_train)
ModelComplexity(features_train, prices_train)


预测模型的准确度值为: 0.831288503230392
所选决策树的最大深度参数设置为DecisionTreeRegressor(max_depth=10)
交叉验证的最佳表现值为: 0.7887002299501087
- 由以上曲线可见,在决策深度接近10时(偏差与方法达到了均衡状态),此时对应的模型即为最优模型。
- 本模型的准确率高达83%,大致能对二手房价进行预测。(参考价值仅为83%)
