#本次是做一个北京二手房的数据挖掘案例,主要是汇总一下学过的知识,并且通过实例加深一下印象,话不多说,开干:
目的:预测二手房的价格;
工具:语言python;爬虫模块scrapy,数据清洗:xpath;数据分析:pandas,matplotlib,numpy三剑客;数据预测:sklearn;
操作流程:先爬取数据,然后清洗数据及分析数据,最后选择合适的机器学习算法进行房价预测。
1.数据爬取(爬虫模块scrapy,数据清洗:xpath):
a.首先介绍一下爬取信息的思路:
①.找到二手房的网站,查看一下网站的结构,及url的变化规律:
我们的网站第一页一般不会有后面这个/pg2/,所以我们先要看下第二页的网址,这就是全部的网址,我们可以试着把2改为1验证一下,回车后不出意外就会回到第一页,我们就知道了翻页只需要改变/pg1/…/pgn/即可,所以后面我们的工作就是思考如何把第一页信息爬取出来,循环一下就会得到全部的信息,思路就是这样。
可以看见全部页数100页,当前位置第二页:
②.接着我们需要把当前页面的房价信息爬取出来:
思路:我们做过网页爬虫就会知道,我们没必要爬取当前页面的信息,因为每条信息都会有个url,进入之后我们会有更清楚的房源详细信息,进入后继续爬取url获取的响应信息不迟:
从下面的图可以看出,我们的url找对了,并且我们需要的信息都在页面中,就看我们有没有本事拿到了:
③.每个页面的url获得了,那就爬取详细信息吧,还是那句话,想爬取得先找到:
所有的爬虫是都是一样的,进入开发者界面,找到自己的信息,然后想办法筛选出来:
b.上代码(scrapy,框架学会后还真的挺好用的):
①.代码结构(创建运行找下我之前的scrapy介绍的博客,时间长了我也忘,也是经常得回去瞅一瞅,不丢人):
②.settings.py(没有反扒,所以只需要把几条代码解开就行了,自行对比一下):
# -*- coding: utf-8 -*-
# Scrapy settings for lianjia_beijing project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
# https://docs.scrapy.org/en/latest/topics/settings.html
# https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
# https://docs.scrapy.org/en/latest/topics/spider-middleware.html
BOT_NAME = 'lianjia_beijing'
SPIDER_MODULES = ['lianjia_beijing.spiders']
NEWSPIDER_MODULE = 'lianjia_beijing.spiders'
# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'lianjia_beijing (+http://www.yourdomain.com)'
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
# Configure maximum concurrent requests performed by Scrapy (default: 16)
#CONCURRENT_REQUESTS = 32
# Configure a delay for requests for the same website (default: 0)
# See https://docs.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
#DOWNLOAD_DELAY = 3
# The download delay setting will honor only one of:
#CONCURRENT_REQUESTS_PER_DOMAIN = 16
#CONCURRENT_REQUESTS_PER_IP = 16
# Disable cookies (enabled by default)
#COOKIES_ENABLED = False
# Disable Telnet Console (enabled by default)
#TELNETCONSOLE_ENABLED = False
# Override the default request headers:
#DEFAULT_REQUEST_HEADERS = {
# 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
# 'Accept-Language': 'en',
#}
# Enable or disable spider middlewares
# See https://docs.scrapy.org/en/latest/topics/spider-middleware.html
#SPIDER_MIDDLEWARES = {
# 'lianjia_beijing.middlewares.LianjiaBeijingSpiderMiddleware': 543,
#}
# Enable or disable downloader middlewares
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
#DOWNLOADER_MIDDLEWARES = {
# 'lianjia_beijing.middlewares.LianjiaBeijingDownloaderMiddleware': 543,
#}
# Enable or disable extensions
# See https://docs.scrapy.org/en/latest/topics/extensions.html
#EXTENSIONS = {
# 'scrapy.extensions.telnet.TelnetConsole': None,
#}
# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'lianjia_beijing.pipelines.LianjiaBeijingPipeline': 300,
}
# Enable and configure the AutoThrottle extension (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/autothrottle.html
#AUTOTHROTTLE_ENABLED = True
# The initial download delay
#AUTOTHROTTLE_START_DELAY = 5
# The maximum download delay to be set in case of high latencies
#AUTOTHROTTLE_MAX_DELAY = 60
# The average number of requests Scrapy should be sending in parallel to
# each remote server
#AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
# Enable showing throttling stats for every response received:
#AUTOTHROTTLE_DEBUG = False
# Enable and configure HTTP caching (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
#HTTPCACHE_ENABLED = True
#HTTPCACHE_EXPIRATION_SECS = 0
#HTTPCACHE_DIR = 'httpcache'
#HTTPCACHE_IGNORE_HTTP_CODES = []
#HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
③.items.py(这里就是把需要爬取的信息列出来,格式都一样):
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html
import scrapy
class LianjiaBeijingItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
'''
url:ershoufang_url;位置:location;尺寸:size;户型:house_type;楼层:floor;价格:price;朝向:orientation;年限:year;
电梯:elevator;装修:fitment;优势:advantage;
抵押信息:Mortgage_information;交易权属:Trading_ownership;房屋用途:usage_of_the_house;产权所属:Property_rights_belong_to;
电话联系:telephone_connection;
'''
####################################
ershoufang_url = scrapy.Field()
location_1 = scrapy.Field()
location_2 = scrapy.Field()
size = scrapy.Field()
house_type = scrapy.Field()
floor = scrapy.Field()
price = scrapy.Field()
orientation = scrapy.Field()
elevator = scrapy.Field()
year = scrapy.Field()
fitment = scrapy.Field()
advantage = scrapy.Field()
###################################
Mortgage_information = scrapy.Field()
Trading_ownership = scrapy.Field()
usage_of_the_house = scrapy.Field()
Property_rights_belong_to = scrapy.Field()
######################################
telephone_connection = scrapy.Field()
④.beijing_ershoufang.py(这个就是爬虫的主程序,爬取的代码写在这里面,代码不难,该注释的也都做了,不明的的函数可以百度一下用法,不要看长就害怕,都是一个套路):
# -*- coding: utf-8 -*-
import scrapy
from ..items import LianjiaBeijingItem
class BeijingErshoufangSpider(scrapy.Spider):
number = 1
name = 'beijing_ershoufang'
allowed_domains = ['bj.lianjia.com']
url = 'https://bj.lianjia.com/ershoufang/pg'
start_urls = [url + str(number)]
def parse(self, response):
#第一页的每个房源信息的url爬取:
ershoufang_urls = response.xpath('//a[@class="title"]/@href').extract()
# print(ershoufang_urls)
print(len(ershoufang_urls))
# 发送每个帖子的请求,使用parse_item方法进行处理
for ershoufang_url in ershoufang_urls:
yield scrapy.Request(ershoufang_url, callback=self.parse_item, dont_filter=True)
#设置自动翻页
if self.number <= 100:
self.number += 1
# 重新发送新页面
yield scrapy.Request(self.url+str(self.number),callback=self.parse)
#爬取北京二手房信息
def parse_item(self , response):
# ===========================主要参数-数据传入item:
item = LianjiaBeijingItem()
#北京二手房的链接:url
item["ershoufang_url"] = response.url
#北京二手房位置:
item["location_1"] = response.xpath('//span[@class="info"]//a/text()').extract()[0] #在哪个区
a = response.xpath('//span[@class="info"]//a/text()').extract()[1] #在区的哪个位置
b = response.xpath('//span[@class="info"]/text()')[1].extract()[1:] #处于几环
item["location_2"] = a+":"+ b
#北京二手房尺寸:
item["size"] = response.xpath('//div[@class="base"]//div[@class="content"]//ul//li/text()').extract()[2]
#北京二手房房型:
item["house_type"] = response.xpath('//div[@class="base"]//div[@class="content"]//ul//li/text()').extract()[0]
# 北京二手房楼层:
item["floor"] = response.xpath('//div[@class="base"]//div[@class="content"]//ul//li/text()').extract()[1]
# 北京二手房价格:
price_1= response.xpath('//div[@class="unitPrice"]//span[@class="unitPriceValue"]/text()').extract()[0]
price_2 = response.xpath('//div[@class="unitPrice"]//span[@class="unitPriceValue"]//i/text()').extract()[0]
item["price"] = price_1+price_2
# 北京二手房朝向:
item["orientation"] = response.xpath('//div[@class="base"]//div[@class="content"]//ul//li/text()').extract()[6].strip() #去除字符串里空格
# 北京二手房电梯有无:
item["elevator"] = response.xpath('//div[@class="base"]//div[@class="content"]//ul//li/text()').extract()[11]
# 北京二手房年限:
item["year"] = response.xpath('//div[@class="transaction"]//div[@class="content"]//ul//li//span/text()').extract()[9]
# 北京二手房装修情况:
item["fitment"] = response.xpath('//div[@class="base"]//div[@class="content"]//ul//li/text()').extract()[8]
# 北京二手房优势:
item["advantage"] = response.xpath('//div[@class="baseattribute clear"]//div[@class="content"]/text()').extract()[0].strip() #去除字符串里面空格
#===========================参考方面:
# 北京二手房抵押信息:
item["Mortgage_information"] = response.xpath('//div[@class="transaction"]//div[@class="content"]//ul//li//span/text()').extract()[13].strip() #去除字符串里面空格
# 北京二手房交易权属:
item["Trading_ownership"] = response.xpath('//div[@class="transaction"]//div[@class="content"]//ul//li//span/text()').extract()[3]
# 北京二手房房屋用途:
item["usage_of_the_house"] = response.xpath('//div[@class="transaction"]//div[@class="content"]//ul//li//span/text()').extract()[7]
# 北京二手房产权所属:
item["Property_rights_belong_to"] = response.xpath('//div[@class="transaction"]//div[@class="content"]//ul//li//span/text()').extract()[11]
#==========================联系方式=
name = response.xpath('//div[@class="brokerInfoText"]//div[@class="brokerName"]/a/text()').extract()[0]
phone_number1 = response.xpath('//div[@class="brokerInfoText"]//div[@class="phone"]/text()').extract()[0]
phone_number2 = response.xpath('//div[@class="brokerInfoText"]//div[@class="phone"]/text()').extract()[1]
item["telephone_connection"] =name+":"+str(phone_number1)+"转"+str(phone_number2)
yield item
⑤.pipelines.py(这里面我们用来保存数据,这里面我用了两种方法,程序调试过程我喜欢保存txt文件,删除方便,程序调试ok我喜欢放在数据库中,感觉高大上,而且后面用数据的时候可以导出各种格式,顺便练练技术):
# -*- coding: utf-8 -*-
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
import pymysql
class LianjiaBeijingPipeline(object):
def __init__(self): # 定义需要初始化的参数(可省略)
# 1.创建数据库链接,给cursor添加一个参数,让其的到数据是一个字典
self.db = pymysql.connect(host='localhost',port=3306,user='root',password='f199506',db='scrapy_mysql',charset='utf8')
self.cursor = self.db.cursor(cursor=pymysql.cursors.DictCursor)
# 2.打开并创建txt文件用于测试
# self.file = open("url.txt", "a", encoding="utf-8")
def process_item(self, item, spider):
# 1.保存数据到数据库:
# a.创建表格
# self.sql = 'create table beijing_ershoufang (id int auto_increment key ,ershoufang_url varchar(100), ' \
# 'location_1 varchar(50),location_2 varchar(50),size varchar(50),' \
# 'house_type varchar(60),floor varchar(50),price varchar(30),' \
# 'orientation varchar(20),elevator varchar(10),year varchar(30),' \
# 'fitment varchar(10),advantage varchar(150),Mortgage_information varchar(100),Trading_ownership varchar(30),' \
# 'usage_of_the_house varchar(20),Property_rights_belong_to varchar(30),telephone_connection varchar(100))'
#
# self.cursor.execute(self.sql) # 执行mysql语句
# self.db.commit() # 提交事务
# b.插入数据
self.sql = 'INSERT INTO beijing_ershoufang(ershoufang_url,location_1,location_2,size,house_type,floor,price,orientation,' \
'elevator,year,fitment,advantage,Mortgage_information,Trading_ownership,usage_of_the_house,' \
'Property_rights_belong_to,telephone_connection) VALUES("%s","%s","%s","%s","%s","%s","%s","%s","%s","%s",' \
'"%s","%s","%s","%s","%s","%s","%s")'
self.cursor.execute(self.sql % (item["ershoufang_url"], item["location_1"], item["location_2"], item["size"],
item["house_type"],item["floor"],item["price"],item["orientation"],
item["elevator"],item["year"],item["fitment"],item["advantage"],
item["Mortgage_information"],item["Trading_ownership"],item["usage_of_the_house"],
item["Property_rights_belong_to"], item["telephone_connection"])) # 执行mysql语句
self.db.commit() # 提交事务
# c.删除数据
#truncate table beijing_ershoufang 可以让id从初始值开始
# self.sql = 'truncate table beijing_ershoufang'
# # self.sql = 'DELETE FROM beijing_ershoufang'
# self.cursor.execute(self.sql) # 执行mysql语句
# self.db.commit() # 提交事务
# 查询数据:
# self.sql = 'select *from beijing_ershoufang'
# self.a = self.cursor.execute(self.sql) # 执行mysql语句
# print(self.a)
# self.db.commit() # 提交事务
# 2.保存数据到本地文件
# content = str(item) + '\n'
# self.file.write(content) # 写入数据到本地
# return item
# 当爬取结束时候执行的方法(可省略)
def close_spider(self, spider):
# 1.关闭数据库链接
self.cursor.close()
self.db.close()
# 2.关闭文件保存数据
# self.file.close()
c.查看一下数据格式(证明一下我真的是运行成功了):
以下是爬取信息的一条数据,当时的思路是尽量爬取多的数据,我们可以根据条件进行筛选,其中加入了链接,可以去查看一下自己心仪房子的具体信息,如果只是自己看一下市场趋势我觉得到这里可以了,如果想更深入了解,我们继续:
【“2” “https://bj.lianjia.com/ershoufang/101101937975.html” “丰台” “七里庄:三至四环” “145.42㎡” “3室2厅1厨2卫” “顶层 (共24层)” “48137元/平米” “东南” “有” “满五年” “简装” “商品房,高楼层,视野开阔,无遮挡” “有抵押 500万元” “私产” “普通住宅” “非共有” "刘淼:4008896832转56019 "】
2.数据分析(pandas,matplotlib,numpy):
a.首先我们pandas把数据整理一下,为的是更好的进行数据的可视化及机器学习进行价格预测:
①.导入刚刚爬取的数据,第一步肯定是筛选数据,把我们认为需要可视化或者认为重要的数据筛选出来:
#conding=gb2312
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
#设置显示窗口数据显示别全是...
pd.set_option('display.max_rows', 500)
pd.set_option('display.max_columns', 100)
pd.set_option('display.width', 1000)
#1.读取本地CSV数据:
df = pd.read_csv(r"D:\SKYbox\天甜费的世界\4.梦想\1.python学习\6.数据分析实战项目\beijing_ershoufang.csv")
# print(df.info()) #看着没有缺失数据
#2.数据处理----把需要的数据拿出来,不重要的先不看:
df = df.loc[:,["id","location_1","location_2","size","house_type","floor","price","orientation","elevator","year","fitment","ershoufang_url"]]
print(df)
②.数据转换主要有以下几个方面
对size[57.26㎡]及price[ 50647元/平米]: 有单位的列去除单位,并转换为数值类型,方便可视化,机器计算也不会考虑你的这个单位;
对location_2[后沙峪:五至六环]: 进行分割,地点及环数分开才会容易识别;
对floor[ 中楼层 (共11层)]: 字符串里面包含数字的,把数字提取出来,方便可视化;
对house_type[1室1厅1厨1卫]: 特征进行分割,分割后更容易进行区分,数据可视化及机器学习也更方便;
#3.把数据单位去掉,转换为数字类型,方便后续观察:
df.loc[:,"size"] = df["size"].str.replace("㎡","").astype("float")
df.loc[:,"price"] = df["price"].str.replace("元/平米","").astype("float")
#4.把一个特征值分割成多个特征值,并且加入到dataframe里面:
df.loc[:,"location_11"] = df["location_2"].str.split(':',expand=True)[0]
df.loc[:,"location_12"] = df["location_2"].str.split(':',expand=True)[1]
df = df.loc[:,["id","location_1","location_11","location_12","size","house_type","floor","price","orientation","elevator","year","fitment","ershoufang_url"]]
#5.获取字符串里面的数字:
#a.这个之前不好用,expand=False加入可以了:
#pat : 字符串或正则表达式,flags : 整型,expand : 布尔型,是否返回数据框
#如果提取的规则结果有多组,则会返回数据框,不匹配的返回NaN
df.loc[:,"floor"] = df.loc[:,"floor"].str.extract('(\d+)',expand=False)
#6.提取“室”和“厅”创建新特征
df.loc[:,'house_type_1'] = df['house_type'].str.extract('(^\d).*', expand=False).astype('int64')
df.loc[:,'house_type_2'] = df['house_type'].str.extract('^\d.*?(\d).*', expand=False).astype('int64')
df = df.loc[:,["id","location_1","location_11","size","house_type_1","house_type_2","floor","year","price"]]
print(df)
#保存数据:
result = df.loc[:,["id","location_1","location_11","size","house_type_1","house_type_2","floor","year","price"]]
result.to_excel("./ershoufang_sk.xlsx", index=False)
print(result)
b.然后用matplotlib进行数据可视化,直观的感受一下特征-标签之间的关系(我是直接在pandas处理后可视化的,如果你想分开,那就保存一下数据,然后再导入):
①.首先想看一下价格和区域的关系,以及每个地区的样本分布,以及查看一下尺寸,楼层,年限,房型的分布情况:
从散点图中可以看出,西城,东城,海淀,朝阳价格相对会高一点,所以房价肯定和区域有关;但是后面的细分区域太多了,散点图看着比较乱,但是可以看出40000-80000之间房价很多,可以做个参考,后面尺寸,楼层,年限,房型等我们可以了解一下样本基本的分布。
#接上面的数据处理程序:
#7
#①.设置中文字体能够显示:
import matplotlib
from matplotlib import pyplot as plt
#①.设置中文字体能够显示:
font = {"family":"MicroSoft YaHei","weight":"bold","size":8}
matplotlib.rc("font",**font)
#②.价格区域散点图:
plt.figure(1)
ax1 = plt.subplot(121) #subplot [1,2,1],表示在本区域里显示1行2列个图像,最后的1表示本图像显示在第一个位置
plt.scatter(df["location_1"],df["price"],s=50, c='blue', marker='.', alpha=0.3) #图例,颜色,线型,线宽,透明度
plt.xlabel("区域")
plt.ylabel("二手房每个区域-价格")
plt.title("查看每个区域的二手房价格分布")
plt.xticks(rotation = 45) #rotation = 45旋转45℃
ax2 = plt.subplot(122) #subplot [1,2,1],表示在本区域里显示1行2列个图像,最后的1表示本图像显示在第一个位置
plt.scatter(df["location_11"],df["price"],s=50, c='blue', marker='.', alpha=0.3) #图例,颜色,线型,线宽,透明度
plt.xlabel("区域")
plt.ylabel("二手房每个区域-价格")
plt.title("查看每个区域的二手房价格分布")
plt.xticks(rotation = 45) #rotation = 45旋转45℃
plt.figure(2)
ax3 = plt.subplot(121) #subplot [1,2,1],表示在本区域里显示1行2列个图像,最后的1表示本图像显示在第一个位置
plt.scatter(df["size"],df["price"],s=50, c='blue', marker='.', alpha=0.3) #图例,颜色,线型,线宽,透明度
plt.xlabel("尺寸")
plt.ylabel("二手房尺寸-价格")
plt.title("查看每个尺寸的二手房价格分布")
plt.xticks(rotation = 45) #rotation = 45旋转45℃
ax4 = plt.subplot(122) #subplot [1,2,1],表示在本区域里显示1行2列个图像,最后的1表示本图像显示在第一个位置
plt.scatter(df["house_type_1"],df["price"],s=50, c='blue', marker='.', alpha=0.3) #图例,颜色,线型,线宽,透明度
plt.xlabel("房型")
plt.ylabel("二手房每个房型-价格")
plt.title("查看每个房型的二手房价格分布")
plt.xticks(rotation = 45) #rotation = 45旋转45℃
plt.figure(3)
ax5 = plt.subplot(121) #subplot [1,2,1],表示在本区域里显示1行2列个图像,最后的1表示本图像显示在第一个位置
plt.scatter(df["floor"],df["price"],s=50, c='blue', marker='.', alpha=0.3) #图例,颜色,线型,线宽,透明度
plt.xlabel("楼层")
plt.ylabel("二手房楼层-价格")
plt.title("查看不同楼层的二手房价格分布")
plt.xticks(rotation = 45) #rotation = 45旋转45℃
ax6 = plt.subplot(122) #subplot [1,2,1],表示在本区域里显示1行2列个图像,最后的1表示本图像显示在第一个位置
plt.scatter(df["year"],df["price"],s=50, c='blue', marker='.', alpha=0.3) #图例,颜色,线型,线宽,透明度
plt.xlabel("年限")
plt.ylabel("二手房每个年限-价格")
plt.title("查看不同年限的二手房价格分布")
plt.xticks(rotation = 45) #rotation = 45旋转45℃
plt.show()
散点图在这个数据上其实能看出的东西比较有限,主要就是看一下大致的分布,比如是我们能看出哪个位置的二手房多,哪个地方比较少;或者哪种房型,楼层,尺寸,年限的房子的个数多少,建立一个基本的印象。我们接着分析。
②.不论是从我们的认知,还是上面的散点图中,我们不难看出位置对价格的影响肯定是很大的,因此我想应用一下统计学来看一下房子位置和价格的关系,并且对每个位置的房源汇总,直观的观察一下每个地方二手房数量:
#8
#①.设置中文字体能够显示:
import matplotlib
from matplotlib import pyplot as plt
#①.设置中文字体能够显示:
font = {"family":"MicroSoft YaHei","weight":"bold","size":8}
matplotlib.rc("font",**font)
#②.画图:地区与价格:
df_house_count = df.groupby('location_1')['price'].count().sort_values(ascending=False).to_frame().reset_index() #ascending=False代表降序,ascending=True代表升序
df_house_mean = df.groupby('location_1')['price'].mean().sort_values(ascending=False).to_frame().reset_index() #reset_index()重置索引
#设置图形大小:
plt.figure(figsize=(10,4),dpi = 80)
plt.figure(1)
#ax1 = plt.subplot(221) 221表示将画板分成两行两列,取第一个区域,即左上角区域
#-plt.figure(1)表示取第一块画板,一个画板即一张图,如果有多个画板,运行完就会打开多张图(多个窗口)
ax1 = plt.subplot(121)
plt.bar(df_house_count["location_1"],df_house_count["price"])
#添加描述信息:
plt.xlabel("区域")
plt.ylabel("二手房数量")
plt.title("查看每个区域的二手房数量")
plt.xticks(rotation = 45) #rotation = 45旋转45℃
######################################################################################
ax2 = plt.subplot(122)
plt.bar(df_house_mean["location_1"],df_house_mean["price"])
#添加描述信息:
plt.xlabel("区域")
plt.ylabel("二手房每个区域平均价格")
plt.title("查看每个区域的二手房平均价格")
plt.xticks(rotation = 45) #rotation = 45旋转45℃
#绘制网格:
plt.grid(alpha=0.1)
# #添加图例:
# plt.legend(loc = "upper left")
plt.show()
③.盒图由五个数值点组成,最小观测值,下四分位数,中位数,上四分位数,最大观测值,主要可以看一看离群值是不是很多,多的话我们就可以看看,是不是这个区域的地区比较多,而且价格差别比较大呢:
#9
#①.设置中文字体能够显示:
import matplotlib
from matplotlib import pyplot as plt
#①.设置中文字体能够显示:
font = {"family":"MicroSoft YaHei","weight":"bold","size":8}
matplotlib.rc("font",**font)
#盒图由五个数值点组成,最小观测值,下四分位数,中位数,上四分位数,最大观测值
#价格区域箱图:
plt.figure(figsize=(10,4),dpi = 80)
a = df.loc[lambda df:(df['location_1']=="西城"),:]["price"]
b = df.loc[lambda df:(df['location_1']=="东城"),:]["price"]
c = df.loc[lambda df:(df['location_1']=="海淀"),:]["price"]
d = df.loc[lambda df:(df['location_1']=="朝阳"),:]["price"]
e = df.loc[lambda df:(df['location_1']=="丰台"),:]["price"]
f = df.loc[lambda df:(df['location_1']=="石景山"),:]["price"]
g = df.loc[lambda df:(df['location_1']=="昌平"),:]["price"]
h = df.loc[lambda df:(df['location_1']=="通州"),:]["price"]
i = df.loc[lambda df:(df['location_1']=="亦庄开发区"),:]["price"]
j = df.loc[lambda df:(df['location_1']=="大兴"),:]["price"]
k = df.loc[lambda df:(df['location_1']=="顺义"),:]["price"]
l = df.loc[lambda df:(df['location_1']=="门头沟"),:]["price"]
m = df.loc[lambda df:(df['location_1']=="房山"),:]["price"]
n = df.loc[lambda df:(df['location_1']=="怀柔"),:]["price"]
o = df.loc[lambda df:(df['location_1']=="密云"),:]["price"]
p = df.loc[lambda df:(df['location_1']=="平谷"),:]["price"]
data_box = np.array([a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p])
labels = ["西城","东城","海淀","朝阳","丰台","石景山","昌平","通州","亦庄开发区","大兴","顺义","门头沟","房山","怀柔","密云","平谷"]
plt.boxplot(data_box,labels=labels,sym="o",
boxprops=dict(color="blue"),medianprops={'color':'red'},whiskerprops = {'color': "black"},capprops = {'color': "cyan"},flierprops={'color':'purple','markeredgecolor':"purple"})
# print(df_house_mean)
# plt.show()
plt.xlabel("区域")
plt.ylabel("二手房每个区域价格分布")
plt.title("查看每个区域的二手房价格箱图分布")
plt.xticks(rotation = 45) #rotation = 45旋转45℃
plt.show()
④.上面多个绘图都验证了价格与区域确实有关,但是我们还想看看与其他特征是否相关,所以看下控制变量后的绘图结果:
价格与地区之间的关系(以西城为例,区域可换):
可以看出是有关系的:
#10
#①.设置中文字体能够显示:
import matplotlib
from matplotlib import pyplot as plt
#①.设置中文字体能够显示:
font = {"family":"MicroSoft YaHei","weight":"bold","size":8}
matplotlib.rc("font",**font)
#b.再一个,我们看一下相同区域,不同地区的位置与价格的关系:
plt.figure(figsize=(10,4),dpi = 80)
plt.figure(1)
plt.subplot(121)
aa_1 = df.loc[lambda df:(df['location_1']=="西城"),:]
aa_2 = aa_1.groupby('location_11')['price'].mean().sort_values(ascending=False).to_frame().reset_index()
plt.scatter(aa_1["location_11"],aa_1["price"],s=50, c='blue', marker='.', alpha=0.3) #图例,颜色,线型,线宽,透明度
plt.xlabel("地区-西城")
plt.ylabel("二手房每个地区价格分布")
plt.title("查看每个地区的二手房价格分布")
plt.xticks(rotation = 45) #rotation = 45旋转45℃
plt.subplot(122)
plt.bar(aa_2["location_11"],aa_2["price"])
plt.xticks(rotation = 45) #rotation = 45旋转45℃
plt.xlabel("地区-西城")
plt.ylabel("二手房每个地区平均价格")
plt.title("查看每个地区的二手房平均价格")
plt.xticks(rotation = 45) #rotation = 45旋转45℃
plt.show()
房型与地区之间的关系(以东城为例,区域可换):
不断切换尝试,发现大部分趋势是三室>一室>二室>其他,价格与房型还是有一定关系。
#11
#①.设置中文字体能够显示:
import matplotlib
from matplotlib import pyplot as plt
#①.设置中文字体能够显示:
font = {"family":"MicroSoft YaHei","weight":"bold","size":8}
matplotlib.rc("font",**font)
#c.房型与价格的关系:
plt.figure(figsize=(10,4),dpi = 80)
plt.figure(1)
plt.subplot(121)
aa_3 = df.loc[lambda df:(df['location_1']=="东城"),:]
aa_4 = aa_3.groupby('house_type_1')['price'].mean().sort_values(ascending=False).to_frame().reset_index()
plt.scatter(aa_3["house_type_1"],aa_3["price"],s=50, c='blue', marker='.', alpha=0.3) #图例,颜色,线型,线宽,透明度
plt.xlabel("房型-东城")
plt.xticks(rotation = 45) #rotation = 45旋转45℃
plt.subplot(122)
plt.bar(aa_4["house_type_1"],aa_4["price"])
plt.xlabel("房型-东城")
plt.xticks(rotation = 45) #rotation = 45旋转45℃
plt.show()
以上可视化大致可以看出一些趋势,还有几个变量就不去实验了,自己可以根据上面两个去看看其他的,因为房价预测比较简单,我们凭借经验基本就可以选择出一些参数了,但对于陌生数据集如何把数据更好的可视化是个永恒的课题,不断的学习吧。
最终选择机器学的参数:[“location_1”,“location_11”,“size”,“house_type_1”,“house_type_2”,“floor”,“year”,“price”]
3.房价预测(sklearn):
sklean里面有很多算法,也提供了很多数据处理的方式,因为价格是连续的,所以我们选择随机森林的回归算法来进行预测,随机森林是以决策树为基础的,我试过决策树和随机森林,结果是必然的,随机森林比决策树的准确率高出很多,但是效果还是不太理想,后续随着学习深入,会试一下其他算法是否会有意想不到的结果,话不多说,实战开始:
a.数据读取,字符串数据one_hot编码:
#conding=gb2312
import pandas as pd
import numpy as np
#设置显示窗口数据显示别全是...
pd.set_option('display.max_rows', 500)
pd.set_option('display.max_columns', 100)
pd.set_option('display.width', 1000)
#1..读取excel数据
df = pd.read_excel(r"./ershoufang_sk.xlsx")
#2.字符串数据进行one_hot编码,不然机器学习模型是学习不了的:
df_one = df.loc[:,["location_1","location_11","year"]]
df_one = pd.get_dummies(df_one).reset_index()
df_hot = df.loc[:,["size","house_type_1","house_type_2","floor","price"]]
df = pd.concat([df_hot,df_one],axis=1)
b.随机森林调参(先对树的个数调参,然后对树的深度调参,这里画一个树的深度调参可视化):
可视化调参步骤都是一样的,实例化,循环参数,生成结果加入列表,画图查看趋势:
#3.数据预处理:
'''
随机森林 预测房价:
'''
# 转换训练测试集格式为数组
features = np.array(df.iloc[:,df.columns!="price"])
prices = np.array(df.iloc[:,df.columns=="price"])
# 导入sklearn进行训练测试集划分
from sklearn.model_selection import train_test_split
features_train, features_test, prices_train, prices_test = train_test_split(features, prices, test_size=0.2, random_state=0)
#随机森林预测:
from sklearn.ensemble import RandomForestRegressor
#交叉验证模型训练:
from sklearn.model_selection import cross_val_score
c_mse = []
for i in range(21,31,1):
#a.实例化:
rfc = RandomForestRegressor(random_state=0,n_estimators=40,max_depth=i)#oob_score=True,
#b.普通的模型训练:
rfc = rfc.fit(features_train,prices_train.ravel())
score_r = rfc.score(features_test,prices_test.ravel())
rfc_s = cross_val_score(rfc,features,prices.ravel(),cv=10,scoring="neg_mean_squared_error").mean()i
c_mse.append(rfc_s)
# print(rfc.oob_score_) #用袋外数据测试后的结果
print("Random Forest:{}".format(score_r))
#可视化:
from matplotlib import pyplot as plt
plt.plot(range(1,11),c_mse,label="RandomForest_")
plt.legend()
plt.show()
进行参数确认(把参数固定看和我们可视化时候的是否一致):
#3.数据预处理:
'''
随机森林 预测房价:
'''
# 转换训练测试集格式为数组
features = np.array(df.iloc[:,df.columns!="price"])
prices = np.array(df.iloc[:,df.columns=="price"])
# 导入sklearn进行训练测试集划分
from sklearn.model_selection import train_test_split
features_train, features_test, prices_train, prices_test = train_test_split(features, prices, test_size=0.2, random_state=0)
#随机森林预测:
from sklearn.ensemble import RandomForestRegressor
#a.实例化:
rfc = RandomForestRegressor(random_state=0
,n_estimators=40
,max_depth=29
,oob_score=True
# ,max_features=120
# ,min_samples_leaf=2
)#oob_score=True,
#b.普通的模型训练:
rfc = rfc.fit(features_train,prices_train.ravel())
score_r = rfc.score(features_test,prices_test.ravel())
print(rfc.oob_score_) #用袋外数据测试后的结果
print("Random Forest:{}".format(score_r))
#另一种查看分数的方法:
from sklearn.metrics import r2_score
y_predict = rfc.predict(features_test)
#在sklearn中包含四种评价尺度,分别为mean_squared_error、mean_absolute_error、explained_variance_score 和 r2_score。
#做回归分析,常用的误差主要有均方误差根(RMSE)和R-平方(R2)。
score = r2_score(prices_test, y_predict)
print(score)
print(rfc.estimators_ ) #查看随机森林中树的状况。
# print(rfc.feature_importances_) #查看重要的特征
#3.数据预处理:
'''
随机森林 预测房价:
'''
# 转换训练测试集格式为数组
features = np.array(df.iloc[:,df.columns!="price"])
prices = np.array(df.iloc[:,df.columns=="price"])
# 导入sklearn进行训练测试集划分
from sklearn.model_selection import train_test_split
features_train, features_test, prices_train, prices_test = train_test_split(features, prices, test_size=0.2, random_state=0)
#随机森林预测:
from sklearn.ensemble import RandomForestRegressor
#网格搜索寻找最优参数:
# #a.实例化:
rfc = RandomForestRegressor(random_state=0
,oob_score=True
# ,n_estimators=40
# ,max_depth=29
# ,max_features=120
# ,min_samples_leaf=2
)#oob_score=True,
from sklearn.model_selection import GridSearchCV
param_grid = {"n_estimators":[25,30,40],"max_depth":[25,29,30]}
GS= GridSearchCV(rfc,param_grid=param_grid,cv=10)
GS.fit(features,prices.ravel())
print(GS.best_params_)
print(GS.best_score_)
从上面可以看出来,网格搜索有时候还不一定有我们自己调的参数好,也是为了运行时间少一点,我把参数少选了几个,但是最大深度他没有选到那个最好的;网格搜索是真的费时间,所以不要轻易尝试,随机森林相对来说已经算是运行的比较快的了,但等的还是好着急。
机器学习本就是个不断尝试的过程,我们需要根据经验或者之前的可视化了解一下其中特征-标签的一些关系,然后用选择的特征进行模型训练及预测,效果不好我们可以调参,特征工程,或者回去看看有没有可增加特征,以及构造特征等等。
机器学习真的没那么简单,也不至于放弃,只是一度看着看着就疲惫了,但是还好还在坚持,而且似乎也是有了兴趣了,虽然进度缓慢,但确实还在路上;
当我们有一段时间做一件事情没有成果的时候,总是会有各种焦虑,试着放下出去走走,呼吸新鲜的空气,看看无垠的世界,然后回到自己的小屋继续吧,人生难得找到一条值得坚持的路,在路上我认为就是幸运的。
持续更新,,,,,,,,,,