城市区域二手房信息python爬取、保存和初步分析—笔记

从某房地产门户网站爬取城市区域二手房房产信息,存入数据库或文件,从数据库读取数据,进行分析、模型构建和房价预测。(本文为练习笔记总结,问题在所难免,待以后纠正和完善。)

网页数据爬取

以房/天/下成都天府新区二手房为抓取目标

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
# 通过BeautifulSoup获得该页房产列表信息
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)
            # 尽量避免反复快速访问页面爬取,推迟0.5秒
            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:root@localhost: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文件

# 将全部房产信息每10页存入一个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:root@localhost:3306/spider?charset-utf8')
# 从数据库里获取所有房产信息
data = pd.io.sql.read_sql('select * from house_price', con=conn)

data.head()
index单价小区名字建筑面积总价户型朝向楼层装修
0011333元/平米滨江和城30平米34万2室1厅1卫南北中层简装修
1110000元/平米滨江和城45平米45万2室1厅1卫中层精装修
2210667元/平米三利麓山城30平米32万1室1厅1卫南北中层毛坯
3316497元/平米万科海悦汇城东区58.8平米97万2室2厅1卫西南低层精装修
4426875元/平米\r\n 天府新区\r\n160平米430万4室2厅3卫高层简装修
  • “index”字段作为数据库索引,不具有分析预测意义
  • “单价”、“建筑面积”和“总价”三个字段的单位需要去除掉,并转化为数值型以便分析
  • “小区名字”字段有的记录首末有\r\n特殊字符可以处理掉,但是该字段应该无助于预测
  • “朝向”字段有不具有太大意义的项(如“南北”),需要处理
data.tail()
index单价小区名字建筑面积总价户型朝向楼层装修
33391019167元/平米万科海悦汇城西区60平米115万2室2厅1卫中层精装修
33401143925元/平米麓湖生态城麒麟荟428平米1880万5室3厅4卫高层简装修
33411212083元/平米洛森堡映山120平米145万3室2厅2卫高层简装修
33421387719元/平米麓湖生态城黑蝶贝1140平米10000万7室3厅6卫低层毛坯
33431425758元/平米麓山国际圣安德鲁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单价小区名字建筑面积总价户型楼层装修
0370370370370370370370370
1东北3535353535353535
2东南196196196196196196196196
3东西5555555555555555
48484848484848484
513181318131813181318131813181318
6南北659659659659659659659659
7暂无345345345345345345345345
8西118118118118118118118118
9西北4040404040404040
10西南124124124124124124124124
  • 除了常用的指向外,有“东西”、“南北”和“暂无”三个分类,占据比较大的份额

楼层

data.楼层.unique()
array(['中层', '低层', '高层'], dtype=object)

装修

data.装修.unique()
array(['简装修', '精装修', '毛坯', '豪华装修', '中装修', '暂无'], dtype=object)
data.groupby(by=['装修'], as_index=False).count()
装修index单价小区名字建筑面积总价户型朝向楼层
0中装修7676767676767676
1暂无66666666
2毛坯13181318131813181318131813181318
3简装修536536536536536536536536
4精装修10391039103910391039103910391039
5豪华装修369369369369369369369369
  • 该字段有五个暂无值,可以填充为众数

数据清洗

index & 小区名字

# 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中装修7676767676767676
1毛坯13241324132413241324132413241324
2简装修536536536536536536536536
3精装修10391039103910391039103910391039
4豪华装修369369369369369369369369
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()
单价建筑面积总价朝向楼层装修
011333.030.034.00112.01.01.0
110000.045.045.02132.01.01.0
210667.030.032.00101.01.01.0
316497.058.897.04032.02.01.0
426875.0160.0430.02214.02.03.0
  • 字段已全部转化为数值,用于分析和预测
  • 网页应该还有更多的信息可供推测房价,暂时分析这些其他的后续添加
data.describe()
单价建筑面积总价朝向楼层装修
count3344.0000003344.0000003344.0000003344.0000003344.0000003344.0000003344.0000003344.0000003344.000000
mean21775.498804210.135939625.3672853.3205740.7736241.5792463.5580142.1725482.460227
std13051.462878176.8338421056.9089593.0589160.7906181.5230801.2513520.6890971.350896
min7105.00000020.00000026.0000000.0000000.0000000.0000001.0000001.0000001.000000
25%13842.75000090.932500135.0000002.0000000.0000000.0000003.0000002.0000001.000000
50%16941.500000141.000000210.0000002.0000001.0000001.0000003.0000002.0000002.000000
75%26342.500000280.352500640.0000005.0000001.0000003.0000004.0000002.0000003.000000
max134328.0000002340.00000012000.00000010.0000002.0000004.0000009.00000021.0000008.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']
#plt.figure()
# 绘制线性回归散点图
otl = sns.lmplot('建筑面积', '总价', data, fit_reg=False)

在这里插入图片描述

  • 大部分数据表现出相对集中的趋势,价格随建筑面积增加而上升
  • 可以观察到部分“离群”异常值,建筑面积大约1500的一个点的总价过低,应该去除
# 获取总价前三名的记录
data.sort_values(by='建筑面积', ascending=False).head(3)
单价建筑面积总价朝向楼层装修
258727350.02340.06400.05101.01.01.0
51556693.01270.07200.02049.01.01.0
55083325.01200.09999.05108.06.07.0
# 将异常值移除
data.drop(data[(data['建筑面积'] >= 1500)].index, inplace=True)

data.sort_values(by='建筑面积', ascending=False).head(3)
单价建筑面积总价朝向楼层装修
51556693.01270.07200.02049.01.01.0
55083325.01200.09999.05108.06.07.0
92682969.01145.09500.02007.03.06.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()
单价建筑面积朝向楼层装修总价
56314157.089.000033.02.02.0126.0
202510263.0114.000203.02.02.0117.0
325313478.092.002133.01.02.0124.0
92161947.0565.0010246.03.06.03500.0
177026889.0185.952234.03.03.0500.0

相关性检验

热力图

plt.figure(figsize=(12, 10))
corr = data_train.corr()
#num = data.shape[1] - 1
#col = corr.nlargest(num, '总价')['总价'].index
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

# 利用GridSearchCV计算最优解
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)

    # 用输入数据[X, y]进行网格搜索
    grid = grid.fit(X, y)
    return grid.best_estimator_

# 计算R2
def performance_metric(y_true, y_predict):
    score = r2_score(y_true, y_predict)
    return score

使用了 KFold 方法减缓过拟合,GridSearchCV 方法进行最优参数自动搜查,最后使用R2评分来给模型打分。

调参优化模型

visuals.py

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)
# 打印最优模型R^2分数
print("最优模型在测试数据上的R^2分数={:, .2f}。".format(r2))

在这里插入图片描述

在这里插入图片描述

最优模型参数max_depth=10
最优模型在测试数据上的R^2分数=0.97

待解决问题

  1. 房地产门户网站上应该还有一些诸如建筑年代、小区位置、绿化程度等指标可以列入分析和预测的考虑
  2. 一些字段的处理可能需要优化,如朝向和户型

预测模型参考

https://segmentfault.com/a/1190000015613967

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值