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

时间:2024-03-22 13:43:20

从某房地产门户网站爬取城市区域二手房房产信息,存入数据库或文件,从数据库读取数据,进行分析、模型构建和房价预测。

网页数据爬取

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

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: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文件

# 将全部房产信息每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: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 & 小区名字

# 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']
#plt.figure()
# 绘制线性回归散点图
otl = sns.lmplot('建筑面积', '总价', data, fit_reg=False)

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

  • 大部分数据表现出相对集中的趋势,价格随建筑面积增加而上升
  • 可以观察到部分“离群”异常值,建筑面积大约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>

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

# 绘制价格分布图
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

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

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

  • 总价总体上表现出正偏态分布,需要变化成正态分布

拆分数据集为训练集和测试集

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,'建筑面积')

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

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

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

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))

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

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()
#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>

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

  • 表面上看,建筑面积和户型对总价影响很大,单价相关系数也很大(单价可能在实际中是未知因素)
  • 朝向表现出正相关(拥有向阳面的价格较贵);楼层变现出负相关(楼层越低价格越高)

散点图矩阵

sns.pairplot(data_train[col], size=2);

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

  • 从第一列可以看出建筑单位、单价和户型等特征与总价表现出明显的相关性

建立模型

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))

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

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

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

待解决问题

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

预测模型参考

https://segmentfault.com/a/1190000015613967