从某房地产门户网站爬取城市区域二手房房产信息,存入数据库或文件,从数据库读取数据,进行分析、模型构建和房价预测。
网页数据爬取
以房/天/下成都天府新区二手房为抓取目标
import requests as rq
from bs4 import BeautifulSoup
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline
from sqlalchemy import create_engine
from collections import Counter
from IPython.display import Image
import scipy.stats as stats
from sklearn.model_selection import train_test_split
from sklearn.model_selection import train_test_split
url = 'http://cd.esf.fang.com/house-a016418/'
res = rq.get(url)
print(res.text[:100])
<!DOCTYPE html>
<html>
<head>
<title>【天府新区二手房|成都天府新区二手房出售】- 成都房天下</title>
<meta http-e
soup = BeautifulSoup(res.text, 'html.parser')
houses = soup.select('.shop_list dl')
def getHouseInfo(url):
house = {}
soup = BeautifulSoup(rq.get(url).text, 'html.parser')
res = soup.select('.tab-cont-right .trl-item1')
for r in res:
data = r.text.strip().split('\n')
key = data[1].strip()
if('朝向' in key):
key = key.strip('进门')
if('楼层' in key or '层数' in key):
key = '楼层'
if('装修程度' in key):
key = '装修'
house[key] = data[0].strip()
community = soup.select('.rcont .blue')[0].text
house['小区名字'] = community
price = soup.select('.tab-cont-right .trl-item')
house['总价'] = price[0].text
return house
print(getHouseInfo('http://cd.esf.fang.com/chushou/3_205701155.htm'))
{'户型': '3室2厅2卫', '建筑面积': '138平米', '单价': '24638元/平米', '朝向': '南', '楼层': '低层', '装修': '豪华装修', '小区名字': '阳光华苑', '总价': '340万'}
import time
domain = 'http://cd.esf.fang.com/'
city = 'house-a016418/i3'
def houses_at_page(i):
page_url = domain + city + str(i)
res = rq.get(page_url)
soup = BeautifulSoup(res.text, 'html.parser')
houses = soup.select('.shop_list dl')
house_list = []
for house in houses:
try:
url = domain + house.select('.floatl a')[0]['href']
house = getHouseInfo(url)
house_list.append(house)
time.sleep(0.5)
except Exception as e:
print(e)
data = pd.DataFrame(house_list)
return data
data = houses_at_page(4)
print(data)
单价 小区名字 建筑面积 总价 户型 朝向 楼层 装修
0 12892元/平米 南湖左岸 89.2平米 115万 3室2厅1卫 南 高层 简装修
1 15375元/平米 和泓半山 160平米 246万 3室2厅1卫 东 低层 豪华装修
2 14533元/平米 华银美景 75平米 109万 2室2厅1卫 暂无 高层 精装修
3 46205元/平米 蔚蓝卡地亚 606平米 2800万 5室2厅5卫 暂无 低层 豪华装修
4 16589元/平米 光明城市 92.23平米 153万 3室2厅1卫 南北 中层 毛坯
——(为了方便阅读这里只显示前五条记录)
将房屋信息存入数据库
connect = create_engine('mysql+pymysql://root:Carbon#[email protected]:3306/spider?charset-utf8')
for i in range(1, 101):
try:
data = houses_at_page(i)
except Exception as e:
print(e)
pd.io.sql.to_sql(data, 'house_price', connect, schema='spider', if_exists='append')
将房屋信息存入csv文件
def houses_to_csv():
data = pd.DataFrame()
prefix = 'houses_'
for i in range(1, 101):
try:
data_a = houses_at_page(i)
data = data_a.append(data)
print('第{0}页信息当前文件抓取进度:{1}%'.format(i, i*10))
except Exception as e:
print(e)
if(i % 10 == 0):
data.to_csv(prefix + str(i) + '.csv')
data = pd.DataFrame()
return data
houses_to_csv()
数据库数据提取和观察
conn = create_engine('mysql+pymysql://root:Carbon#[email protected]:3306/spider?charset-utf8')
data = pd.io.sql.read_sql('select * from house_price', con=conn)
data.head()
|
index |
单价 |
小区名字 |
建筑面积 |
总价 |
户型 |
朝向 |
楼层 |
装修 |
0 |
0 |
11333元/平米 |
滨江和城 |
30平米 |
34万 |
2室1厅1卫 |
南北 |
中层 |
简装修 |
1 |
1 |
10000元/平米 |
滨江和城 |
45平米 |
45万 |
2室1厅1卫 |
南 |
中层 |
精装修 |
2 |
2 |
10667元/平米 |
三利麓山城 |
30平米 |
32万 |
1室1厅1卫 |
南北 |
中层 |
毛坯 |
3 |
3 |
16497元/平米 |
万科海悦汇城东区 |
58.8平米 |
97万 |
2室2厅1卫 |
西南 |
低层 |
精装修 |
4 |
4 |
26875元/平米 |
\r\n 天府新区\r\n |
160平米 |
430万 |
4室2厅3卫 |
南 |
高层 |
简装修 |
- “index”字段作为数据库索引,不具有分析预测意义
- “单价”、“建筑面积”和“总价”三个字段的单位需要去除掉,并转化为数值型以便分析
- “小区名字”字段有的记录首末有\r\n特殊字符可以处理掉,但是该字段应该无助于预测
- “朝向”字段有不具有太大意义的项(如“南北”),需要处理
data.tail()
|
index |
单价 |
小区名字 |
建筑面积 |
总价 |
户型 |
朝向 |
楼层 |
装修 |
3339 |
10 |
19167元/平米 |
万科海悦汇城西区 |
60平米 |
115万 |
2室2厅1卫 |
北 |
中层 |
精装修 |
3340 |
11 |
43925元/平米 |
麓湖生态城麒麟荟 |
428平米 |
1880万 |
5室3厅4卫 |
南 |
高层 |
简装修 |
3341 |
12 |
12083元/平米 |
洛森堡映山 |
120平米 |
145万 |
3室2厅2卫 |
南 |
高层 |
简装修 |
3342 |
13 |
87719元/平米 |
麓湖生态城黑蝶贝 |
1140平米 |
10000万 |
7室3厅6卫 |
南 |
低层 |
毛坯 |
3343 |
14 |
25758元/平米 |
麓山国际圣安德鲁 |
330平米 |
850万 |
5室2厅4卫 |
暂无 |
低层 |
毛坯 |
- “朝向”字段有“暂无”值,需要结合“南北”等值综合处理,并要考虑其他字段是否有类似情况(如户型、楼层和装修)
data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3344 entries, 0 to 3343
Data columns (total 9 columns):
index 3344 non-null int64
单价 3344 non-null object
小区名字 3344 non-null object
建筑面积 3344 non-null object
总价 3344 non-null object
户型 3344 non-null object
朝向 3344 non-null object
楼层 3344 non-null object
装修 3344 non-null object
dtypes: int64(1), object(8)
memory usage: 235.2+ KB
小区名字
data.小区名字.unique()
array(['滨江和城', '三利麓山城', '万科海悦汇城东区',
'\r\n 天府新区\r\n ', '心怡紫晶城', '滨江天樾',
'合能枫丹铂麓', '棠湖泊林城', '中铁骑士府邸', '海伦春天', '新鸿基悦城', '三都汇朝外', '原乡',
'南湖半岛', '河畔新世界', '麓山国际麓镇帕萨迪纳', '中德英伦世邦', '德商华府天骄', '麓山国际圣安德鲁',
'宏达世纪丽景', '碧桂园海昌天澜', '麓山国际茵特拉肯', '双华麓港', '心怡中丝园', '建发浅水湾',
'府河音乐花园', '麓山国际社区云堤曦岸', '恒大天府半岛二期', '中德麓府', '兰桥尚舍', '麓山国际别墅',
'麓山国际社区茵特拉肯', '和泓半山', '麓湖生态城麒麟荟', '麓湖生态城白玉台', '瑞升橡树林华府', '蔚蓝卡地亚',
'麓山国际悦林湖', '恒大天府半岛', '麓湖生态城黑珍珠', '佳兆业君汇上品', '麓山国际塞尔维蒙', '石化家园',
'保利叶语', '鑫苑鑫都汇', '美城悦荣府别墅', '成都玩家', '麓山国际长岛', '麓湖生态城蓝花屿',
'融创Nano公馆', '戛纳湾金棕榈', '九龙仓时代上城', '麓湖生态城隐溪岸', '南湖世纪', '雅居乐富春山居',
'中铁诺德壹号', '水印城', '北辰南湖香麓', '洛森堡映山', '南湖国际社区三期', '麓山国际半月湾',
'华阳滨河花园', '麓山国际逸翠谷', '麓山国际碧湖岸', '三利宅院白云渡', '铂雅苑', '黄金海岸', '麓山国际',
'光明城市', '麓山国际黑鹰庄园', '南湖左岸', '天投北鑫苑', '麓山国际拉佩维尔', '南阳盛世', '远大*公园',
'南湖国际社区二期', '蜀郡一期', '香山半岛', '欣宇都市港湾', '双峰嶺', '宏达世纪锦城', '万科海悦汇城西区',
'恒大名都', '麓山国际香溪堤', '麓山国际圣芭芭拉', '麓山国际社区云曦台', '德商御府天骄', '麓湖生态城云树',
'美城悦荣府', '棠湖泊林城别墅', '雅居乐花园一期', '中海左岸', '秦皇帝锦', '蓝山美树', '复地御香山',
'双兴名邸四季春天', '和贵馨城', '融创南湖逸家', '麓山国际碧影溪', '麓山国际翠云岭', '戛纳湾滨江',
'亚丁小镇', '万锦城', '蜀郡又一城', '宏信南樾', '麓山国际橡树坡', '中海右岸', '和贵南山上',
'洛森堡蝶郡', '洛森堡新殿', '景茂城果', '青南美湾', '雅居乐十里花巷', '麓湖生态城沉香谷', '麓山国际叠溪谷',
'蜀郡别墅', '保利锦江里', '麓山国际圆石滩', '麓湖生态城黑蝶贝', '恺信时代天城', '美高登A座', '枫渡莱茵',
'麓山国际solo', '鸿阁一号', '南阳锦城', '凯华丽景', '成都雅居乐花园别墅', '蓝山国际爵悦半岛',
'麓湖生态城澜语溪岸', '海昌天澜别墅', '棕榈南岸', '南湖国际社区四期', '麓山国际黑钻山庄', '欧香小镇别墅',
'锦官丽城亲水湾', '三利云锦', '蓝山国际伯爵山', '蓝润ISC', '蓝岸丽景', '嘉美地', '天府美岸',
'美城云庭', '长冶南阳锦城香樟阁', '慕和南道', '永泰雅居', '碧桂园海昌天澜别墅', '鸿阁新座', '心怡德盛苑',
'南湖国际社区一期', '华银美景', '油建苑', '麓山印象', '丽都新城一期', '家益欣城', '地源吾舍',
'华阳府河名居', '成南领寓', '楠域丽景', '麓山国际黑檀庄园', '凯莱丽景雅筑', '华阳花苑', '麓湖生态城玲珑屿',
'万科翡翠公园', '融创玖棠府', '金南园', '六菱小区', '阳光华苑', '麓岭汇', '大城际', '戛纳湾畔',
'三利麓山城别墅', '顺发苑', '翠拥天地', '明珠怡园', '阳光华庭', '欣雨苑', '金城花园', '保利御景台',
'瑞雪港湾', '麓湖生态城原溪岸'], dtype=object)
- 该字段分类较多表面上看没有分析价值,可能需要结合地理位置分类,暂不考虑留到以后处理
户型
data.户型.unique()
array(['2室1厅1卫', '1室1厅1卫', '2室2厅1卫', '4室2厅3卫', '3室2厅2卫', '4室2厅2卫',
'4室3厅4卫', '2室2厅2卫', '3室1厅1卫', '5室3厅4卫', '3室2厅1卫', '5室2厅4卫',
'4室3厅2卫', '4室3厅3卫', '4室2厅4卫', '9室5厅8卫', '5室2厅3卫', '5室3厅3卫',
'6室3厅5卫', '4室1厅1卫', '4室1厅2卫', '5室1厅3卫', '5室3厅5卫', '3室3厅3卫',
'4室5厅4卫', '4室4厅4卫', '6室3厅3卫', '5室3厅2卫', '7室6厅7卫', '3室2厅3卫',
'8室5厅6卫', '5室2厅5卫', '3室3厅1卫', '8室3厅8卫', '6室4厅5卫', '4室3厅5卫',
'6室2厅5卫', '6室2厅4卫', '6室2厅7卫', '6室3厅6卫', '6室3厅4卫', '6室4厅6卫',
'6室4厅4卫', '6室1厅1卫', '6室2厅6卫', '7室4厅6卫', '5室2厅2卫', '9室1厅1卫',
'5室4厅4卫', '8室6厅7卫', '2室2厅3卫', '5室1厅5卫', '5室3厅1卫', '3室4厅2卫',
'3室1厅2卫', '7室3厅5卫', '5室4厅5卫', '8室4厅8卫', '4室2厅5卫', '4室1厅3卫',
'7室3厅6卫', '暂无', '6室3厅1卫', '1室2厅1卫', '7室3厅7卫', '5室4厅2卫', '7室5厅6卫',
'7室3厅4卫', '8室4厅4卫', '5室4厅3卫', '5室2厅6卫', '7室2厅5卫', '4室2厅1卫',
'5室3厅6卫', '5室1厅4卫', '3室3厅2卫', '4室21厅4卫', '9室4厅7卫', '7室2厅6卫',
'6室5厅3卫', '7室4厅4卫', '7室2厅3卫', '6室5厅5卫', '5室5厅2卫'], dtype=object)
朝向
data.朝向.unique()
array(['南北', '南', '西南', '西', '东', '东南', '暂无', '东西', '北', '东北', '西北'],
dtype=object)
data.groupby(by=['朝向'], as_index=False).count().sort_values(by=['朝向'])
|
朝向 |
index |
单价 |
小区名字 |
建筑面积 |
总价 |
户型 |
楼层 |
装修 |
0 |
东 |
370 |
370 |
370 |
370 |
370 |
370 |
370 |
370 |
1 |
东北 |
35 |
35 |
35 |
35 |
35 |
35 |
35 |
35 |
2 |
东南 |
196 |
196 |
196 |
196 |
196 |
196 |
196 |
196 |
3 |
东西 |
55 |
55 |
55 |
55 |
55 |
55 |
55 |
55 |
4 |
北 |
84 |
84 |
84 |
84 |
84 |
84 |
84 |
84 |
5 |
南 |
1318 |
1318 |
1318 |
1318 |
1318 |
1318 |
1318 |
1318 |
6 |
南北 |
659 |
659 |
659 |
659 |
659 |
659 |
659 |
659 |
7 |
暂无 |
345 |
345 |
345 |
345 |
345 |
345 |
345 |
345 |
8 |
西 |
118 |
118 |
118 |
118 |
118 |
118 |
118 |
118 |
9 |
西北 |
40 |
40 |
40 |
40 |
40 |
40 |
40 |
40 |
10 |
西南 |
124 |
124 |
124 |
124 |
124 |
124 |
124 |
124 |
- 除了常用的指向外,有“东西”、“南北”和“暂无”三个分类,占据比较大的份额
楼层
data.楼层.unique()
array(['中层', '低层', '高层'], dtype=object)
装修
data.装修.unique()
array(['简装修', '精装修', '毛坯', '豪华装修', '中装修', '暂无'], dtype=object)
data.groupby(by=['装修'], as_index=False).count()
|
装修 |
index |
单价 |
小区名字 |
建筑面积 |
总价 |
户型 |
朝向 |
楼层 |
0 |
中装修 |
76 |
76 |
76 |
76 |
76 |
76 |
76 |
76 |
1 |
暂无 |
6 |
6 |
6 |
6 |
6 |
6 |
6 |
6 |
2 |
毛坯 |
1318 |
1318 |
1318 |
1318 |
1318 |
1318 |
1318 |
1318 |
3 |
简装修 |
536 |
536 |
536 |
536 |
536 |
536 |
536 |
536 |
4 |
精装修 |
1039 |
1039 |
1039 |
1039 |
1039 |
1039 |
1039 |
1039 |
5 |
豪华装修 |
369 |
369 |
369 |
369 |
369 |
369 |
369 |
369 |
数据清洗
index & 小区名字
del data['index']
del data['小区名字']
单价 & 建筑面积 & 总价
data['单价'] = data['单价'].apply(lambda x:x.replace('元/平米', '')).astype(float)
data['建筑面积'] = data['建筑面积'].apply(lambda x:x.replace('平米', '')).astype(float)
data['总价'] = data['总价'].apply(lambda x:x.replace('万', '')).astype(float)
户型
temp = data.户型.mode()[0]
data.loc[data.户型 == '暂无', '户型'] = temp
data.户型.unique()
array(['2室1厅1卫', '1室1厅1卫', '2室2厅1卫', '4室2厅3卫', '3室2厅2卫', '4室2厅2卫',
'4室3厅4卫', '2室2厅2卫', '3室1厅1卫', '5室3厅4卫', '3室2厅1卫', '5室2厅4卫',
'4室3厅2卫', '4室3厅3卫', '4室2厅4卫', '9室5厅8卫', '5室2厅3卫', '5室3厅3卫',
'6室3厅5卫', '4室1厅1卫', '4室1厅2卫', '5室1厅3卫', '5室3厅5卫', '3室3厅3卫',
'4室5厅4卫', '4室4厅4卫', '6室3厅3卫', '5室3厅2卫', '7室6厅7卫', '3室2厅3卫',
'8室5厅6卫', '5室2厅5卫', '3室3厅1卫', '8室3厅8卫', '6室4厅5卫', '4室3厅5卫',
'6室2厅5卫', '6室2厅4卫', '6室2厅7卫', '6室3厅6卫', '6室3厅4卫', '6室4厅6卫',
'6室4厅4卫', '6室1厅1卫', '6室2厅6卫', '7室4厅6卫', '5室2厅2卫', '9室1厅1卫',
'5室4厅4卫', '8室6厅7卫', '2室2厅3卫', '5室1厅5卫', '5室3厅1卫', '3室4厅2卫',
'3室1厅2卫', '7室3厅5卫', '5室4厅5卫', '8室4厅8卫', '4室2厅5卫', '4室1厅3卫',
'7室3厅6卫', '6室3厅1卫', '1室2厅1卫', '7室3厅7卫', '5室4厅2卫', '7室5厅6卫',
'7室3厅4卫', '8室4厅4卫', '5室4厅3卫', '5室2厅6卫', '7室2厅5卫', '4室2厅1卫',
'5室3厅6卫', '5室1厅4卫', '3室3厅2卫', '4室21厅4卫', '9室4厅7卫', '7室2厅6卫',
'6室5厅3卫', '7室4厅4卫', '7室2厅3卫', '6室5厅5卫', '5室5厅2卫'], dtype=object)
data[['室', '厅', '卫']] = data.户型.str.extract('(\d+)室(\d+)厅(\d+)卫')
D:\Anaconda3\lib\site-packages\ipykernel_launcher.py:1: FutureWarning: currently extract(expand=None) means expand=False (return Index/Series/DataFrame) but in a future version of pandas this will be changed to expand=True (return DataFrame)
"""Entry point for launching an IPython kernel.
data.室 = data.室.astype(float)
data.厅 = data.厅.astype(float)
data.卫 = data.卫.astype(float)
del data['户型']
装修
temp = data.装修.mode()[0]
data.loc[data.装修 == '暂无', '装修'] = temp
data.groupby(by='装修', as_index=False).count()
|
装修 |
单价 |
建筑面积 |
总价 |
朝向 |
楼层 |
室 |
厅 |
卫 |
0 |
中装修 |
76 |
76 |
76 |
76 |
76 |
76 |
76 |
76 |
1 |
毛坯 |
1324 |
1324 |
1324 |
1324 |
1324 |
1324 |
1324 |
1324 |
2 |
简装修 |
536 |
536 |
536 |
536 |
536 |
536 |
536 |
536 |
3 |
精装修 |
1039 |
1039 |
1039 |
1039 |
1039 |
1039 |
1039 |
1039 |
4 |
豪华装修 |
369 |
369 |
369 |
369 |
369 |
369 |
369 |
369 |
map_decor = {'毛坯': 0, '简装修': 1, '中装修': 2, '精装修': 3, '豪华装修': 4}
data.装修 = data.装修.map(map_decor)
楼层
map_floor = {'低层': 0, '中层': 1, '高层': 2}
data.楼层 = data.楼层.map(map_floor)
朝向
map_ort = {'南北': 0, '东西': 1, '南': 2, '东南': 3, '西南': 4, '东': 5, '西': 6, '东北': 7, '西北': 8, '北': 9, '暂无': 10}
data.朝向 = data.朝向.map(map_ort)
data.head()
|
单价 |
建筑面积 |
总价 |
朝向 |
楼层 |
装修 |
室 |
厅 |
卫 |
0 |
11333.0 |
30.0 |
34.0 |
0 |
1 |
1 |
2.0 |
1.0 |
1.0 |
1 |
10000.0 |
45.0 |
45.0 |
2 |
1 |
3 |
2.0 |
1.0 |
1.0 |
2 |
10667.0 |
30.0 |
32.0 |
0 |
1 |
0 |
1.0 |
1.0 |
1.0 |
3 |
16497.0 |
58.8 |
97.0 |
4 |
0 |
3 |
2.0 |
2.0 |
1.0 |
4 |
26875.0 |
160.0 |
430.0 |
2 |
2 |
1 |
4.0 |
2.0 |
3.0 |
- 字段已全部转化为数值,用于分析和预测
- 网页应该还有更多的信息可供推测房价,暂时分析这些其他的后续添加
data.describe()
|
单价 |
建筑面积 |
总价 |
朝向 |
楼层 |
装修 |
室 |
厅 |
卫 |
count |
3344.000000 |
3344.000000 |
3344.000000 |
3344.000000 |
3344.000000 |
3344.000000 |
3344.000000 |
3344.000000 |
3344.000000 |
mean |
21775.498804 |
210.135939 |
625.367285 |
3.320574 |
0.773624 |
1.579246 |
3.558014 |
2.172548 |
2.460227 |
std |
13051.462878 |
176.833842 |
1056.908959 |
3.058916 |
0.790618 |
1.523080 |
1.251352 |
0.689097 |
1.350896 |
min |
7105.000000 |
20.000000 |
26.000000 |
0.000000 |
0.000000 |
0.000000 |
1.000000 |
1.000000 |
1.000000 |
25% |
13842.750000 |
90.932500 |
135.000000 |
2.000000 |
0.000000 |
0.000000 |
3.000000 |
2.000000 |
1.000000 |
50% |
16941.500000 |
141.000000 |
210.000000 |
2.000000 |
1.000000 |
1.000000 |
3.000000 |
2.000000 |
2.000000 |
75% |
26342.500000 |
280.352500 |
640.000000 |
5.000000 |
1.000000 |
3.000000 |
4.000000 |
2.000000 |
3.000000 |
max |
134328.000000 |
2340.000000 |
12000.000000 |
10.000000 |
2.000000 |
4.000000 |
9.000000 |
21.000000 |
8.000000 |
data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3344 entries, 0 to 3343
Data columns (total 9 columns):
单价 3344 non-null float64
建筑面积 3344 non-null float64
总价 3344 non-null float64
朝向 3344 non-null int64
楼层 3344 non-null int64
装修 3344 non-null int64
室 3344 non-null float64
厅 3344 non-null float64
卫 3344 non-null float64
dtypes: float64(6), int64(3)
memory usage: 235.2 KB
异常值处理
plt.rcParams['font.sans-serif'] = ['SimHei']
otl = sns.lmplot('建筑面积', '总价', data, fit_reg=False)
- 大部分数据表现出相对集中的趋势,价格随建筑面积增加而上升
- 可以观察到部分“离群”异常值,建筑面积大约1500的一个点的总价过低,应该去除
data.sort_values(by='建筑面积', ascending=False).head(3)
|
单价 |
建筑面积 |
总价 |
朝向 |
楼层 |
装修 |
室 |
厅 |
卫 |
2587 |
27350.0 |
2340.0 |
6400.0 |
5 |
1 |
0 |
1.0 |
1.0 |
1.0 |
515 |
56693.0 |
1270.0 |
7200.0 |
2 |
0 |
4 |
9.0 |
1.0 |
1.0 |
550 |
83325.0 |
1200.0 |
9999.0 |
5 |
1 |
0 |
8.0 |
6.0 |
7.0 |
data.drop(data[(data['建筑面积'] >= 1500)].index, inplace=True)
data.sort_values(by='建筑面积', ascending=False).head(3)
|
单价 |
建筑面积 |
总价 |
朝向 |
楼层 |
装修 |
室 |
厅 |
卫 |
515 |
56693.0 |
1270.0 |
7200.0 |
2 |
0 |
4 |
9.0 |
1.0 |
1.0 |
550 |
83325.0 |
1200.0 |
9999.0 |
5 |
1 |
0 |
8.0 |
6.0 |
7.0 |
926 |
82969.0 |
1145.0 |
9500.0 |
2 |
0 |
0 |
7.0 |
3.0 |
6.0 |
plt.figure()
sns.lmplot('建筑面积', '总价', data, fit_reg=False);
plt.xlim(0,1500)
plt.ylim(0,13000);
<matplotlib.figure.Figure at 0x1c48f0b8>
plt.figure(figsize=(9, 6))
sns.distplot(data.总价)
plt.title('总价分布')
plt.ylabel('频率')
print("偏度为{:.3f}".format(data['总价'].skew()))
plt.figure(figsize=(9, 6))
plt.rcParams['axes.unicode_minus'] = False
stats.probplot(data['总价'], plot=plt)
plt.show()
D:\Anaconda3\lib\site-packages\scipy\stats\stats.py:1633: FutureWarning: Using a non-tuple sequence for multidimensional indexing is deprecated; use `arr[tuple(seq)]` instead of `arr[seq]`. In the future this will be interpreted as an array index, `arr[np.array(seq)]`, which will result either in an error or a different result.
return np.add.reduce(sorted[indexer] * weights, axis=axis) / sumval
偏度为4.671
拆分数据集为训练集和测试集
price_data = data.总价
data = data.drop('总价',axis=1)
data.insert(0,'总价',price_data)
x, y = data.ix[:,1:], data.ix[:,0]
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3, random_state=0)
D:\Anaconda3\lib\site-packages\ipykernel_launcher.py:1: DeprecationWarning:
.ix is deprecated. Please use
.loc for label based indexing or
.iloc for positional indexing
See the documentation here:
http://pandas.pydata.org/pandas-docs/stable/indexing.html#ix-indexer-is-deprecated
"""Entry point for launching an IPython kernel.
plt.figure(figsize=(9, 6))
plt.scatter(x_train.建筑面积, y_train)
plt.ylabel('总价')
plt.xlabel('建筑面积')
Text(0.5,0,'建筑面积')
sns.distplot(y_train)
plt.ylabel('频率')
plt.title('分布图')
plt.show()
D:\Anaconda3\lib\site-packages\scipy\stats\stats.py:1633: FutureWarning: Using a non-tuple sequence for multidimensional indexing is deprecated; use `arr[tuple(seq)]` instead of `arr[seq]`. In the future this will be interpreted as an array index, `arr[np.array(seq)]`, which will result either in an error or a different result.
return np.add.reduce(sorted[indexer] * weights, axis=axis) / sumval
stats.probplot(y_train, plot=plt)
((array([-3.43509518, -3.18695128, -3.04948019, ..., 3.04948019,
3.18695128, 3.43509518]),
array([ 26., 27., 28., ..., 9500., 9500., 9999.])),
(751.4122330833427, 625.6583461538461, 0.7313168979168443))
print('偏度为:{0}'.format(y_train.skew()))
偏度为:4.258903339034095
组建训练集
data_train = x_train.join(y_train)
data_train.head()
|
单价 |
建筑面积 |
朝向 |
楼层 |
装修 |
室 |
厅 |
卫 |
总价 |
563 |
14157.0 |
89.00 |
0 |
0 |
3 |
3.0 |
2.0 |
2.0 |
126.0 |
2025 |
10263.0 |
114.00 |
0 |
2 |
0 |
3.0 |
2.0 |
2.0 |
117.0 |
3253 |
13478.0 |
92.00 |
2 |
1 |
3 |
3.0 |
1.0 |
2.0 |
124.0 |
921 |
61947.0 |
565.00 |
10 |
2 |
4 |
6.0 |
3.0 |
6.0 |
3500.0 |
1770 |
26889.0 |
185.95 |
2 |
2 |
3 |
4.0 |
3.0 |
3.0 |
500.0 |
相关性检验
热力图
plt.figure(figsize=(12, 10))
corr = data_train.corr()
col = corr.sort_values('总价', ascending=False).index
coeff = np.corrcoef(data[col].values.T)
sns.heatmap(coeff, annot=True, xticklabels=col.values, yticklabels=col.values, linewidth=0.1, cmap=plt.cm.jet, linecolor='white')
<matplotlib.axes._subplots.AxesSubplot at 0x1d7ff390>
- 表面上看,建筑面积和户型对总价影响很大,单价相关系数也很大(单价可能在实际中是未知因素)
- 朝向表现出正相关(拥有向阳面的价格较贵);楼层变现出负相关(楼层越低价格越高)
散点图矩阵
sns.pairplot(data_train[col], size=2);
- 从第一列可以看出建筑单位、单价和户型等特征与总价表现出明显的相关性
建立模型
from sklearn.model_selection import KFold
from sklearn.tree import DecisionTreeRegressor
from sklearn.metrics import make_scorer
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import r2_score
def fit_model(X, y):
cross_validator = KFold(10, shuffle=True)
regressor = DecisionTreeRegressor()
params = {'max_depth': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}
scoring_fnc = make_scorer(performance_metric)
grid = GridSearchCV(estimator=regressor, param_grid=params, scoring=scoring_fnc, cv=cross_validator)
grid = grid.fit(X, y)
return grid.best_estimator_
def performance_metric(y_true, y_predict):
score = r2_score(y_true, y_predict)
return score
使用了 KFold 方法减缓过拟合,GridSearchCV 方法进行最优参数自动搜查,最后使用R2评分来给模型打分。
调参优化模型
from https://raw.githubusercontent.com/udacity/machine-learning/master/projects/boston_housing/visuals.py
这里借用了visuals.py代码,为方便阅读暂不放进内容,请自行下载查看
ModelLearning(x_train, y_train)
ModelComplexity(x_train, y_train)
optimal_reg1 = fit_model(x_train, y_train)
print("最优模型参数max_depth={}".format(optimal_reg1.get_params()['max_depth']))
predicted_value = optimal_reg1.predict(x_test)
r2 = performance_metric(y_test, predicted_value)
print("最优模型在测试数据上的R^2分数={:, .2f}。".format(r2))
最优模型参数max_depth=10
最优模型在测试数据上的R^2分数=0.97
待解决问题
- 房地产门户网站上应该还有一些诸如建筑年代、小区位置、绿化程度等指标可以列入分析和预测的考虑
- 一些字段的处理可能需要优化,如朝向和户型
预测模型参考
https://segmentfault.com/a/1190000015613967