深圳租房信息聚类与回归分析

一、简介

随着越来越多的人才涌入深圳工作,深圳的房价近两年来直线上升,增长速度一直居高不下。而每年刚来到来到深圳的毕业生们一开始都需要租房,而租房真的是一个折磨人的活计,想要找到一个租金便宜地段好真的是难上加难;一些黑心房东和中介坑害租客的话题也是层出不穷。因此笔者就想对链家网上的租房信息进行数据分析,看看影响房租的主要特征有那些,以及对房租进行线性回归预测。

本项目使用reques和BeatifulSoup抓取共5500余条链家网深圳租房信息,使用数据透视表简单分析租房信息,然后使用pandas对数据进行清洗和处理,将处理的数据进行聚类分析,并对租房价格进行线性回归预测。

二、抓取租房信息

首先使用Firefox打开链家深圳网站,并进入开发者模式,重新载入并找到网址请求信息:

然后再点击查看器找到放置每条租房信息的块,很容易就能发现在“con-box”下:

然后编写爬虫代码将这些信息都抓取下来并存入CSV文件中:

def get_url(url,page):
	html=requests.get(url+'/pg%s'%str(page),headers=headers).text
	table=BeautifulSoup(html,'lxml').find('div',{'class':'con-box'}).find_all('li',{'data-el':'zufang'})
	table_2=BeautifulSoup(html,'lxml')
	pattern=re.compile('"totalPage":(.*?),".*?')
	last_page=re.findall(pattern,str(table_2))
	pattern_mianji=re.compile(u"(.*?)平米.*?")
	pattern_diqu=re.compile(u"(.*?)租房")
	pattern_update=re.compile(u"(.*?)\s更新")
	result=[]
	for li in table:
		item={}
		item['website']=li.find('a').get('href')
		item['houseinfo']=li.find('img').get('alt')
		item['house_estate']=li.find('div',{'class':'where'}).find('a').get_text().replace(u'\xa0',u'')
		item['house_type']=li.find('div',{'class':'where'}).find_all('span')[1].get_text().replace(u'\xa0',u'')
		item['area']=re.findall(pattern_mianji,li.find('div',{'class':'where'}).find_all('span')[3].get_text())[0]
		item['zone']=re.findall(pattern_diqu,li.find('div',{'class':'other'}).find('a').get_text())[0]
		item['orientation']=li.find('div',{'class':'where'}).find_all('span')[4].get_text()
		item['storey']=li.find('div',{'class':'con'}).get_text().split('/')[1]
		item['age']=li.find('div',{'class':'con'}).get_text().split('/')[2]
		item['price']=li.find('div',{'class':'price'}).find('span',{'class':'num'}).get_text()
		item['updatetime']=re.findall(pattern_update,li.find('div',{'class':'price-pre'}).get_text())[0]
		item['viewers']=li.find('div',{'class':'square'}).find('span',{'class':'num'}).get_text()
		result.append(item)
	return result,last_page

详细爬虫代码就不贴了。

三、将抓取的.csv文件导入Excel表格

下图是导入的前20项数据:

房租排名前20项,可见租金最高的地区基本集中在南山区深圳湾一片:

罗湖区一室一厅的租金排名前20项如下图,可见一室一厅价格的面积差距不大,因此主要影响价格的是地段以及装修这类特征:

下面几张图分别是房型、区域、年限、朝向的数量统计直方图,从下图可以看出数量上房型比较多的是两室、三室和四室;租房数量集中在福田、龙岗、罗湖和南山这四个区;

建楼年限大部分集中在2000-2009年;楼房朝向基本都是南边;因此从这几张图可以简单分析出建房年限和楼房朝向对租金大体影响不大,对个例可能存在一定影响;房型主要与面积相关共同对影响房价,区域与地理位置相关,也对租金产生较大影响。


下图是面积里最高租金统计图,可以看出大体上租金与面积存在正比关系,但是信息里存在很多价格过高的特例,需要在之后的分析过程中剔除掉:

四、数据处理与聚类分析

首先将.csv文件通过pandas导入成DataFrame,然后对数据特征进行处理,代码如下:

def loaddata(filename):
 numfeat=pd.read_csv(filename)
 dataMat=[]; labelMat=[]
 zone_dummies=pd.get_dummies(numfeat['region'])
 zone_dummies.columns=['luohuqu','futianqu','nanshanqu','yantianqu','baoanqu','longgangqu','longhuaqu','pingshanqu','dapengxinqu']
 numfeat['age0']=numfeat['age'].str.extract('(.*?)年.*?',expand=False)
 numfeat=numfeat.convert_objects(convert_numeric=True)   #将object转化成float64型,需要注意
 numfeat.loc[numfeat['age0'].isnull(),'age0']=mean(numfeat['age0'])
 numfeat['age0']=numfeat['age0'].astype(int)
 numfeat['age0']=2017-numfeat['age0']
 numfeat['storey0']=numfeat['storey'].str.extract('(.*?)\(.*?',expand=False)
 storey0_dummies=pd.get_dummies(numfeat['storey0'])
 storey0_dummies.columns=['high','mid','low']
 numfeat['area']=numfeat['area'].astype(int)
 numfeat['price']=numfeat['price'].astype(int)
 numfeat.drop(['price']>60000,axis=0,inplace=True)
 data=numfeat[['area','age0']].join(zone_dummies)
 data_1=numfeat[['area','price']].values
 dataMat=data.values
 labelMat.append(np.array(numfeat['price']).tolist())
 numfeat.drop(['age','storey','website','orientation'],axis=1,inplace=True)
 return data_1,dataMat,labelMat,numfeat

上述代码主要是将年限、地区、区域等信息转化成数值型,并剔除价格大于60000的数据条目。

数据准备好后分别采用k-mean和二分k-means聚类方法对面积和租金这两个特征进行聚类,我设置的质心个数为5个,代码如下:

#K-均值聚类算法
def kMeans(dataset,k,distmeas=disteclud,createcent=randcent):
	m=shape(dataset)[0]
	clusterassment=mat(zeros((m,2)))
	centroids=createcent(dataset,k)
	clusterchanged=True
	while clusterchanged:
		clusterchanged=False
		for i in range(m):
			mindist=inf;minindex=-1
			for j in range(k):
				distJI=distmeas(centroids[j,:],dataset[i,:])
				if distJI<mindist:
					mindist=distJI;minindex=j
			if clusterassment[i,0]!=minindex:clusterchanged=True
			clusterassment[i,:]=minindex,mindist**2
		#print centroids
		for cent in range(k):
			ptsinclust=dataset[nonzero(clusterassment[:,0]==cent)[0]]
			centroids[cent,:]=mean(ptsinclust,axis=0)
	return centroids,clusterassment

#二分K-均值聚类算法

def bikmeans(dataset,k,distmeas=disteclud):
	m=shape(dataset)[0]
	clusterassment=mat(zeros((m,2)))
	centroid0=mean(dataset,axis=0).tolist()[0]
	centlist=[centroid0]
	for j in range(m):
		clusterassment[j,1]=distmeas(mat(centroid0),dataset[j,:])**2
	while(len(centlist)<k):
		lowestsse=inf
		for i in range(len(centlist)):
			ptsincurrcluster=dataset[nonzero(clusterassment[:,0].A==i)[0],:]
			centroidmat,splitclustass=kMeans(ptsincurrcluster,2,distmeas)
			ssesplit=sum(splitclustass[:,1])
			ssenotsplit=sum(clusterassment[nonzero(clusterassment[:,0].A!=i)[0],1])
			#print "ssesplit, and notsplit:",ssesplit,ssenotsplit
			if(ssesplit+ssenotsplit)<lowestsse:
				bestcenttosplit=i
				bestnewcents=centroidmat
				bestclusterass=splitclustass.copy()
				lowestsse=ssesplit+ssenotsplit
		print lowestsse
		bestclusterass[nonzero(bestclusterass[:,0].A==1)[0],0]=len(centlist)
		bestclusterass[nonzero(bestclusterass[:,0].A==0)[0],0]=bestcenttosplit
		print 'the bestcenttosplit is:',bestcenttosplit
		print 'the len of bestclusterass is:',len(centlist)
		centlist[bestcenttosplit]=bestnewcents[0,:]  #被划分的质心
		centlist.append(bestnewcents[1,:])  #添加新的质心
		clusterassment[nonzero(clusterassment[:,0].A==bestcenttosplit)[0],:]=bestclusterass
	return mat(array(centlist)),clusterassment
data_1,dataMat,labelMat,numfeat=loaddata('C:\software\Notepad++\lianjia_shenzhen_1.csv')
fig=plt.figure()
ax=fig.add_subplot(111)
centroids,clusterassment=k_means.kMeans(data_1[:,:2],5,distmeas=k_means.disteclud,createcent=k_means.randcent)
centroids,clusterassment=k_means.bikmeans(data_1[:,:2],6,distmeas=k_means.disteclud)
ax.scatter(data_1[:,0].tolist(),data_1[:,1].tolist())
ax.scatter(centroids[:,0].tolist(),centroids[:,1].tolist(),marker='+',s=300)
plt.show()

将质心的位置与散点图用matplotlib画出,效果如下:
从整体上看二分k-均值也就是第二张图的聚类效果好于第一张,因为二分k-均值收敛到全局最小值。

五、线性回归预测

本文考虑使用标准的线性回归预测,主要对面积与价格这两个特征进行线性回归,其他特征暂时先不考虑,主要代码如下:

def standRegress(xArr,yArr):
	xMat=mat(xArr);yMat=mat(yArr).T
	xTx=xMat.T*xMat
	if linalg.det(xTx)==0:
		print"This mattrix is singular, can not do inverse"
		return
	ws=xTx.I*(xMat.T*yMat)
	return ws

由于输入的X矩阵第一列为全1,所以还需要再转化一下:

m,n=shape(dataMat)
b=ones((m,1))
c=np.column_stack((b,data_1[:,0]))
ws_1=LinerRegretion.standRegress(c,labelMat)
c.sort(0)
yHat1=c*ws_1
print ws_1
ax.plot(c[:,1],yHat1,color='r')
plt.show()

最终结果为y=100.73727206x-876.67975953,拟合图如下,前半部分数据过于集中,效果一般,可见价格与面积存在一定线性关系:


a=np.column_stack((b,dataMat))
ws_2=LinerRegretion.standRegress(a,labelMat)
a.sort(0)
yHat2=a*ws_2
print ws_2

将区域、年限特征加入进行线性回归,打印出参数,发现数据过大,效果很差,所以放弃。
[  3.27680000e+04]
 [ -3.63260841e+01]
 [ -1.63211358e+03]
 [ -1.63840000e+04]
 [ -1.96608000e+05]
 [ -9.83040000e+04]
 [ -6.55360000e+04]
 [ -6.55360000e+04]
 [ -8.19200000e+04]
 [ -7.37280000e+04]
 [ -9.01120000e+04]
 [ -9.83040000e+04]]

六、总结

通过对深圳租房信息的项目,学会了如何使用reques和BeautifulSoup爬虫抓取网页信息,了解了租房信息的基本数据特征,加深了对k-means算法与线性回归算法的认识,学会了对数据进行聚类,并通过面积特征简单预测了租金。由于只对链家网的信息进行了抓取,数据量还是不够大,因此结论和效果也不是太理想,数据特征分析的也不够深入,今后还需多加积累。


参考文章与书籍:

Python爬取链家广州租房信息--https://zhuanlan.zhihu.com/p/30927438

《机器学习实战》--Peter Harrington

数据分析案例--以上海二手房为例--https://zhuanlan.zhihu.com/p/24802969


  • 4
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值