重庆二手房房价影响因素

house
当城市规模发展到一定程度之后,城市建设将会由外延式拓展逐渐让位于内涵式的更新、改造和保护。这意味着受城市土地资源的限制,新建房数量会逐步减少,而存量房的保有量将会越来越大,此时房地产市场的交易重心必然会从新房转移到二手房上来。本次试验通过对链家重庆二手房公开数据的统计分析,研究影响重庆二手房房价的影响,为二手房定价提供理论依据。

2017年7月,重庆二手住宅成交14125套,而2016年7月成交则是4407套,同比增长3倍多。重庆二手房市场正在蓬勃发展。研究二手房房价问题也正在情理之中。那么二手房房价的合理价格到底是多少呢?这个问题牵扯到很多因素:卧室数、客厅数、楼层高度、房屋朝向、装修状况、总面积、建造年份、房屋类型、有无电梯、周围有没有轻轨以及所在的位置。众多因素中,哪些重要?哪些不重要?能否将它们的相对重要性量化出来?如果能回答这个问题,我们就可以帮助自己为理想的二手住房明码标价。这是研究的主要目的。

目标网址:链家

lianjia

一、数据抓取

数据由pythonscrapy框架采集,并存入MySQL。项目源代码已经托管到Githubhttps://github.com/Gripex-lee/CQhouse_data

主要的文件CQhouse.py内容如下:

  • parse为对第一页的解析方法
  • detail_parse通过parse方法提取详细信息
  • 每一个item为一条数据
  • 通过xpath得到数据
-------------------点击显/隐代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import re
import scrapy
from scrapy.http import Request
from house_data.items import HouseDataItem

class CqhouseSpider(scrapy.Spider):
name = 'CQhouse'
allowed_domains = ['cq.lianjia.com']
start_urls = ['https://cq.lianjia.com/ershoufang/pg1']

def parse(self, response):
for url in response.xpath('//div[@class="info clear"]/div[@class="title"]/a/@href').extract():
yield Request(url=url,callback=self.detail_parse)

for i in range(1,101):
next_url = self.start_urls[0].replace("pg1","pg"+str(i))
yield Request(url = next_url,callback=self.parse)

def detail_parse(self,response):
item = HouseDataItem()
item["url"] = response.xpath('//div[@class="houseRecord"]/span[@class="info"]/text()').extract_first()
item["price_per"] = response.xpath('//div[@class="unitPrice"]/span/text()').extract_first()
item["price_total"] = response.xpath('//div[@class="price "]/span/text()').extract_first()
item["room_number"] = re.findall(r"\d+",response.xpath('//div[@class="room"]/div[@class="mainInfo"]/text()').extract_first())[0]
item["living_room_number"] = re.findall(r"\d+",response.xpath('//div[@class="room"]/div[@class="mainInfo"]/text()').extract_first())[1]
item["high"] = re.findall(r"[\d.]+",response.xpath('//div[@class="room"]/div[@class="subInfo"]/text()').extract_first().split("/")[1])[0]

if len(response.xpath('//div[@class="type"]/div[@class="mainInfo"]/text()').extract_first()) >= 3:
item["house_toward"] = response.xpath('//div[@class="type"]/div[@class="mainInfo"]/text()').extract_first().split(" ")[0]
else:
item["house_toward"] = response.xpath('//div[@class="type"]/div[@class="mainInfo"]/text()').extract_first()

if re.findall(r".*?/.*?",response.xpath('//div[@class="type"]/div[@class="subInfo"]/text()').extract_first()):
item["house_type"] = response.xpath('//div[@class="type"]/div[@class="subInfo"]/text()').extract_first().split("/")[1]
else:
item["house_type"] = response.xpath('//div[@class="type"]/div[@class="subInfo"]/text()').extract_first()

item["size"] = re.findall(r"[\d.]+",response.xpath('//div[@class="area"]/div[@class="mainInfo"]/text()').extract_first())[0]
if re.findall(r"[\d.]+",response.xpath('//div[@class="area"]/div[@class="subInfo"]/text()').extract_first().split("/")[0]):
item["build_year"] = re.findall(r"[\d.]+",response.xpath('//div[@class="area"]/div[@class="subInfo"]/text()').extract_first().split("/")[0])[0]
else:
item["build_year"] = "未知"

item["buding_type"] = response.xpath('//*[@id="introduction"]/div/div/div[2]/div[2]/ul/li[2]/span[2]/text()').extract_first()
item["dianti"] = response.xpath('//*[@id="introduction"]/div/div/div[1]/div[2]/ul/li[11]/text()').extract_first()
item["name"] = response.xpath('/html/body/div[5]/div[2]/div[4]/div[1]/a[1]/text()').extract_first()
item["location"] = response.xpath('/html/body/div[5]/div[2]/div[4]/div[2]/span[2]/a[1]/text()').extract_first()
if response.xpath('//div[@class="areaName"]/a[@title]/text()').extract_first():
item["rail"] = "是"
else:
item["rail"] = "否"
yield item

通过改写pipelines.py将得到的数据存入MySQL,该文件内容如下(此处以本地数据库为例):

-------------------点击显/隐代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import pymysql

class HouseDataPipeline(object):
def process_item(self, item, spider):
return item

class MysqlPipeline(object):
'''
插入mysql数据库
'''
def __init__(self):
self.conn =pymysql.connect(host='localhost',port=3306,user='root',passwd='123456',db='cqhouse',use_unicode=True, charset="utf8")
self.cursor = self.conn.cursor()

def process_item(self,item,spider):
insert_sql = '''
insert into house_data(url,price_per,price_total,room_number,living_room_number,high,house_toward,house_type,size,build_year,buding_type,dianti,name,location,rail) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
'''

self.cursor.execute(insert_sql,(item["url"],item["price_per"],item["price_total"],item["room_number"],item["living_room_number"],item["high"],item["house_toward"],item["house_type"],item["size"],item["build_year"],item["buding_type"],item["dianti"],item["name"],item["location"],item["rail"]))
self.conn.commit()

前提是已经在本地数据库建立了cqhouse数据库并在其中建立表house_data:

1
2
3
create database cqhouse;
use cqhouse;
create table house_data(url char(100),price_per char(20),price_total char(100),room_number char(10),living_room_number char(20),high char(20),house_toward char(20),house_type char(20),size char(20),build_year char(20),buding_type char(20),dianti char(5),name char(50),location char(20),rail char(10));

二、R 语言的简单处理

MySQL中导入数据

-------------------点击显/隐代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
library(RMySQL)
library(mice)
# 连接数据库'cqhouse'
con1=dbConnect(RMySQL::MySQL(),
dbname='cqhouse',
username='root',
password='123456',
host
='localhost')
# 设置编码解决乱码问题
dbSendQuery(con1,"SET NAMES gbk")

# 从'cqhouse'的'house_data'中导入数据
res = dbSendQuery(con1,"select * from house_data;")
mydata = dbFetch(res,n=-1)

mydata的前几行如下:
mydata

重命名变量+更改变量类型

由于存入数据库时定义的是字符变量,在这里对变量的类型进行更改,为了使用方便我们修改变量名。

-------------------点击显/隐代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
colnames(mydata) = c("id","price","total_price","bedroom","living_room","floor","toward",
"type","size","build_year","building_type","elector","Community_name","location","rail")

mydata$id = as.integer(mydata$id)
mydata$price = as.numeric(mydata$price)
mydata$total_price = as.numeric(mydata$total_price)
mydata$bedroom = as.integer(mydata$bedroom)
mydata$living_room = as.integer(mydata$living_room)
mydata$floor = as.integer(mydata$floor)
mydata$size = as.numeric(mydata$size)
mydata$build_year = as.integer(mydata$build_year)
mydata$toward = as.factor(mydata$toward)
mydata$type = as.factor(mydata$type)
mydata$building_type = as.factor(mydata$building_type)
mydata$elector = as.factor(mydata$elector)
mydata$location = as.factor(mydata$location)
mydata$rail = as.factor(mydata$rail)

data = mydata[,!names(mydata) %in% c("id","Community_name")]
data$elector[which(data$elector == "暂无数据")] = NA
data$type[which(data$type == "其他")] = NA

修改后的data如下,其中有几个变量含有缺失值
data

查看缺失值

1
md.pattern(data)

queshi从中可以了解到bulid_year缺失323个,elector缺失522个,type缺失967个。build_yearelector的缺失值较少,所以不打算删除这两个变量,考虑到变量type装修状况是一个能影响二手房价的重要因素,虽然其缺失值相对较多,但是我们仍不采用删除变量的操作。

下面用R进行缺失值处理:

  • build_year的缺失值用随机森林随机插补的方法(mice),不考虑跟建筑年份无关的变量:id,bedroom,living_room,elector(含缺失值),price(因变量),type(含缺失值);
  • electortype的缺失值采用最近邻插补KNN的方法补齐,因为相似的二手房可能在有无电梯和装修状况上有类似情况。
-------------------点击显/隐代码
1
2
3
4
5
6
7
8
9
10
11
12
mice_mod <- mice(data[, !names(data) %in% c('id','bedroom','living_room','elector','price','type')],
method='rf') #rf为randomForest
mice_output <- complete(mice_mod)
data$build_year <- mice_output$build_year

require(DMwR)
knnOutput <- knnImputation(data[,!names(data) %in% c('id','price','bedroom','living_room','type','size','building_type')],meth = "weighAvg")
data$elector <- knnOutput$elector

knnOutput <- knnImputation(data[,!names(data) %in% c('id','price','bedroom','living_room','size','elector')],meth = "weighAvg")
data$type <- knnOutput$type
md.pattern(data)

mice此时缺失值已经全部补齐。

总共含2983条数据,每一个数据代表当时(2018-8-23)正在销售的二手房。因变量是房屋的销售价格(price),单位:元/平方米。除了因变量外,还考虑下面的解释变量,这些变量都能直接或间接地影响因变量(价格):

  • 卧室数 (bedroom)、客厅数 (living_room)、面积 (size):该类指标的大小间接反映了房屋的大小和居住体验。
  • 楼层高度 (floor):楼层越高空气流通越好,同时耗费的材料更多。
  • 房屋朝向 (toward):不同的朝向对于采光、通风有较大的影响。
  • 装修状况 (type):该指标刻画销售房屋是毛坯、简装还是精装房。显然,同一个房屋,精装的销售价格会显然高于毛坯和简装房。但是到底高多少还需要通过数据来得到结论。
  • 建筑年份 (build_year):该指标对价格的影响显而易见,同种样式的房屋建筑年份越近说明越新。
  • 房屋类型 (building_type):该指标刻画房屋是商品房还是经济适用房等,不同的类型对价格也有相应的影响。该指标有拆迁还建房、房改房、集资房、经济适用房、商品房5个值。
  • 有无电梯 (elector):显然有电梯的相对会比没有电梯的价格高。该指标有是否两个值。
  • 所在位置 (location):地理位置是一个重要的影响因素,我们这里主要研究在重庆,不同区的价格是否有显著的不同。
  • 是否靠近轻轨 (rail):周围是否有轻轨决定了所在位置是否方便出行,如今轻轨是重庆市民的主要出行方式,因此这个指标是一个及其重要的指标,该指标有是和否两个值。

数值型变量(bedroom,living_room,floor,size,build_year)

number
从中可以得到类似下面的信息:

  • bedroom的最小值为1,最大值为9,中位数为3,均值2.791表明重庆二手房的卧室数量平均为2.791个;
  • living_room最小值0表明有的二手房中没有客厅;
  • floor:最高的楼层有58层,最低1层;
  • size:房屋面积在20.39~574.08之间;
  • build_year:二手房中建造年限最大的达到30年,同时也有今年刚建造的。

相关系数

corr
从相关系数的可视化中看到:

  • floorprice具有较强的相关性,相关系数达到0.8;
  • sizeprice负相关;
  • living_roombedroom的相关性较弱。

分类型变量

lisan
从中可以得到类似下面的信息:

  • 重庆二手房中精装房较多,占比一半以上;
  • 样本中的房屋类型大多都是商品房,分布不均匀。

price的直方图

1
2
3
4
attach(data)
library(ggplot2)
library(corrplot)
ggplot(data,aes(x=price))+geom_histogram(binwidth = 200, fill = "blue", colour = "grey")

直方图
房屋价格大致服从正态分布,房价均值为16209.47元/平方米。

价格关于电梯装修状况分类下的箱线图显示:

1
2
3
ggplot(data,aes(x=elector,y=price))+
geom_boxplot()+
facet_wrap(~type,scales = "free")

elector
由图了解到:

  • 不同装修状况下二手房价没有明显差异;
  • 有电梯的二手房的房价高于没有电梯的二手房房价。

二手房的地理分布

根据data中的place得到下图(由于place变量区分度不高,所以看到的点较少):
ditu

-------------------点击显/隐代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
library(REmap)
library(dplyr)
data = filter(data,place != "渝中化龙桥") #找不到该点的坐标
pointdata = data$place

# 以重庆照母山森林公园为中心
remapB(center = c(106.5054,29.62866),
zoom = 12,
color = "Bright",
title = "重庆二手房地理分布",
subtitle = "二手房所在地区",
markLineData = NA,
markPointData = pointdata,
markLineTheme = markLineControl(),
markPointTheme = markPointControl(symbol = "circle",symbolSize = 4,color = "blue",effect = F),
geoData = NA)

由图了解到:

  • 大部分的二手房地址在轨道线附近(也可能是由于重庆的轨道线覆盖较广导致);
  • 江北、渝北、沙坪坝、渝中地区的房源较多;

由于房屋地理位置不是非常精确,图中无法反应过多信息,这一点可以在后期的改进中多花功夫。

价格关于地点location

1
ggplot(data,aes(x=location,y=price))+geom_boxplot()

location
由图了解到:

  • 位置的不同对房价有较大影响;
  • 江北区的房价较均值最高,而江津最低。

关于朝向toward

1
ggplot(data,aes(x=toward,y=price))+geom_boxplot()

toward
箱线图知道不同的朝向在样本中表现为:没有较大差异,这似乎与普遍认知‘南北通透’较好有所相悖,或许是因为地域影响,山城的人民也许并不会因为房屋朝向而改变对这座城市的喜爱。

就所有变量建立模型

本次采用简单的线性模型来拟合二手房价和各影响因素的关系:

1
2
lm1=lm(price~bedroom+living_room+floor+type+size+build_year+building_type+elector+location+toward+rail,data)
summary(lm1)

R1
模型的P值显著(说明至少有因素对二手房价有显著影响),拟合优度(该值越大说明拟合效果越好)为0.477。可能是由于变量对二手房价的影响实在太小或样本量分布不均所致,可以发现living_room,floor,building_type和房屋朝向都不显著。
summary

删除不显著变量后建立模型

1
2
lm2=lm(price~bedroom+type+size+build_year+elector+location+rail,data)
summary(lm2)

R2
此时模型P值显著,拟合优度稍有降低,发现type毛坯location北碚location大渡口较显著外,其余变量都非常显著。说明模型建立成功。

从模型我们可以得到类似以下的结论:

  • 在其他因素不变的情况下,bedroom(卧室)的数量每增加一,房屋价格将上升577.4(元/平方米);
  • 在其他因素不变的情况下,精装房要比简装房单价(元/平方米)平均842.2(元/平方米),毛坯房要比简装房单价(元/平方米)平均393.3(元/平方米),这似乎不能理解,原因可能是样本缺失值处理的不合理,也不乏毛坯房相比简装房会更方便按照自己的意愿装修的可能;
  • 在其他因素不变的情况下,二手房总面积增加1平方米,价格降低8.207(元/平方米);
  • 在其他因素不变的情况下,建筑年份较1989年一年,价格就257.3(元/平方米);
  • 在其他因素不变的情况下,有电梯的二手房比没有电梯的二手房的房价平均1401(元/平方米);
  • 在其他因素不变的情况下,北碚的二手房房价要比巴南(作为基准组)平均4420(元/平方米),而江北的二手房价比北碚平均6022-4420=1602(元/平方米),江津的二手房价比北碚平均4420-(-4647)=9067(元/平方米);
  • 在其他因素不变的情况下,在轻轨附近的二手房比不在轻轨附近的二手房的房价平均610.3(元/平方米)。
-------------本文结束感谢您的阅读-------------