zillow房价预测比赛_数据分析实战——美国King County房价预测

本文介绍了美国King County房价预测比赛的数据分析过程,包括数据介绍、特征选择和模型选择。通过对卧室数、浴室数、房屋面积等特征的分析,选择随机森林算法进行房价预测,结果显示其性能优于线性回归。
摘要由CSDN通过智能技术生成

数据介绍

该项目是Data Castle上的美国King County房价预测训练赛,用到的数据取自于kaggle datasets,由@harlfoxem提供并分享,但是只选取了其中的子集,并对数据做了一些预处理使数据更加符合回归分析比赛的要求。

数据主要包括2014年5月至2015年5月美国King County的房屋销售价格以及房屋的基本信息。 数据分为训练数据和测试数据,分别保存在kc_train.csv和kc_test.csv两个文件中。 其中训练数据主要包括10000条记录,14个字段,主要字段说明如下:

  • 第一列“销售日期”("SaleDate"):2014年5月到2015年5月房屋出售时的日期
  • 第二列“销售价格”("SalePrice"):房屋交易价格,单位为美元,是目标预测值
  • 第三列“卧室数”("BedroomNum"):房屋中的卧室数目
  • 第四列“浴室数”("BathroomNum"):房屋中的浴室数目
  • 第五列“房屋面积”("LivingArea"):房屋里的生活面积
  • 第六列“停车面积”("ParkingArea"):停车坪的面积
  • 第七列“楼层数”("Floor"):房屋的楼层数
  • 第八列“房屋评分”("Rating"):King County房屋评分系统对房屋的总体评分
  • 第九列“建筑面积”("BuildingArea"):除了地下室之外的房屋建筑面积
  • 第十列“地下室面积”("BasementArea"):地下室的面积
  • 第十一列“建筑年份”("BuildingYear"):房屋建成的年份
  • 第十二列“修复年份”("RepairYear"):房屋上次修复的年份
  • 第十三列"纬度"("Latitude"):房屋所在纬度
  • 第十四列“经度”("Longitude"):房屋所在经度

测试数据主要包括3000条记录,13个字段,跟训练数据的不同是测试数据并不包括房屋销售价格,需要通过由训练数据所建立的模型以及所给的测试数据,得出测试数据相应的房屋销售价格预测值。

# 导入数据分析需要的包
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
plt.style.use('ggplot')

# 分别导入训练数据和测试数据,并按照数据介绍中的内容给每一列加上列名
trainDf = pd.read_csv('kc_train.csv', header = None, 
                  names = ['SaleDate', 'SalePrice', 'BedroomNum', 'BathroomNum', 'LivingArea',
                           'ParkingArea', 'Floor', 'Rating', 'BuildingArea', 'BasementArea',
                           'BuildingYear', 'RepairYear', 'Latitude', 'Longitude'])
testDf = pd.read_csv('kc_test.csv', header = None, 
                  names = ['SaleDate', 'BedroomNum', 'BathroomNum', 'LivingArea',
                           'ParkingArea', 'Floor', 'Rating', 'BuildingArea', 'BasementArea',
                           'BuildingYear', 'RepairYear', 'Latitude', 'Longitude'])

# 将训练集和测试集合并,便于对数据进行统一处理
fullDf = trainDf.append(testDf, ignore_index = True )

# 查看数据信息
trainDf.info()
# 以下为输出结果
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10000 entries, 0 to 9999
Data columns (total 14 columns):
SaleDate        10000 non-null int64
SalePrice       10000 non-null int64
BedroomNum      10000 non-null int64
BathroomNum     10000 non-null float64
LivingArea      10000 non-null int64
ParkingArea     10000 non-null int64
Floor           10000 non-null float64
Rating          10000 non-null int64
BuildingArea    10000 non-null int64
BasementArea    10000 non-null int64
BuildingYear    10000 non-null int64
RepairYear      10000 non-null int64
Latitude        10000 non-null float64
Longitude       10000 non-null float64
dtypes: float64(4), int64(10)
memory usage: 1.1 MB

训练集一共有10000条数据,且数据集中没有缺失值。可以看到数据类型全是数值型。

下面对某些数据进行处理:将销售日期转换为日期格式,并提取出年份和月份;将建筑年份分区,转化为分类变量。

# 将销售日期转换为日期格式,并提取出年份和月份
fullDf['SaleDate_year'] = pd.to_datetime(fullDf['SaleDate'], format='%Y%m%d').dt.year
fullDf['SaleDate_month'] = pd.to_datetime(fullDf['SaleDate'], format='%Y%m%d').dt.month

# 建筑年份从1900年到2015年,每隔15年分一组
temp_list = [i for i in range(1900, 2021)]
bins = temp_list[::15]
fullDf['BuildingYearBins'] = pd.cut(fullDf['BuildingYear'], bins=bins)

# 使用pandas的get_dummies方法进行one-hot编码,得到虚拟变量,添加前缀‘Year’
BuildingYearBinsDf = pd.get_dummies(fullDf['BuildingYearBins'], prefix='Year')

# 添加虚拟变量到数据集中
fullDf = pd.concat([fullDf, BuildingYearBinsDf], axis=1)

# 将建筑年份和销售日期两列删除
fullDf.drop(['BuildingYear', 'SaleDate'], axis=1, inplace=True)

# 查看房价的分布情况
%matplotlib notebook
trainDf['SalePrice'].hist(bins=30)

eedaf37ec632d16898bbe5601075d7a8.png
房价分布情况
# 查看房价数据的描述统计量
trainDf.SalePrice.describe()
# 以下为输出结果
count    1.000000e+04
mean     5.428749e+05
std      3.729258e+05
min      7.500000e+04
25%      3.225000e+05
50%      4.507000e+05
75%      6.450000e+05
max      6.885000e+06
Name: SalePrice, dtype: float64

可以看到房价呈现明显的右偏态分布,大部分房价位于0-100万美元的区间,但房价最高接近700万美元。从该列的描述统计量中也能发现,房价平均值大于房价中位数,表现出右偏态的特征。

由于要分析影响房价的因素,所以作出每个变量与房价之间的关系图

plotDf = fullDf.iloc[0:10000, :]    # 提取出总数据集的前10000行,即训练集的内容,用该DataFrame作图
sns.barplot(x='SaleDate_year', y='SalePrice', data=plotDf)

1b3f29f674fb2b7ea459bb2b5561124b.png
销售年份和房价的关系图
sns.barplot(x='SaleDate_month', y='SalePrice', data=plotDf)

ed4549ef63aad24863a71e5206014ccb.png
销售月份和房价的关系图

2014年和2015年的房屋销售价格比较接近。每个月的房价都会有小幅波动,4、5、6、10、11月的房价处在较高水平。因此将销售月份这一变量转化为虚拟变量加入总数据集中,删除原来的销售月份列。由于销售年份与房价相关性不大,将该列也删去。

SaleDateMonthDf = pd.get_dummies(fullDf['SaleDate_month'], prefix='Month')
fullDf = pd.concat([fullDf, SaleDateMonthDf], axis=1)
fullDf.drop(['SaleDate_year', 'SaleDate_month'], axis=1, inplace=True)

sns.boxplot(x='BedroomNum', y='SalePrice', data=plotDf)

af73be75a26f26393a2df70949c94251.png
卧室数和房价的关系图
sns.boxplot(x='BathroomNum', y='SalePrice', data=plotDf)

07daf55ee61c3a385cd82c0c8ae70fa0.png
浴室数和房价的关系图
plt.scatter(plotDf['LivingArea'], plotDf['SalePrice'])

773d410486ad7fbbd610c67c6c42de9f.png
房屋面积和房价的关系图

卧室数、浴室数为离散型变量,用箱形图;房屋面积为连续型变量,用散点图。可以看出这三者与房价均成正相关,原因也很好理解,这三个变量越大意味着房子越大,越大的房子一般都会越贵。

sns.scatterplot(x='ParkingArea', y='SalePrice', data=plotDf)

f72987226d25fe7251a7be67eeffbac9.png
停车面积和房价的关系图
sns.boxplot(x='Floor', y='SalePrice', data=plotDf)

66fbeb2a33bf7ecdc5db938a59e1f775.png
楼层和房价的关系图

停车面积和楼层数与房屋价格的相关性不大。从图中可以看出有很多房屋停车面积很小但是价格却很贵。而房屋楼层高可能意味着每一层的面积小,比如同样都是300平方米的房子,三层楼的占地只要100平方米,而一层楼的占地就要300平方米,很多情况下是后者的地价更贵。将这两个变量从数据集中删除。

fullDf.drop(['ParkingArea', 'Floor'], axis=1, inplace=True)

sns.boxplot(x='Rating', y='SalePrice', data=plotDf)

b880c40b141c38d5ce0167b85a5536b4.png
房屋评分和房价的关系图
sns.scatterplot(x='BuildingArea', y='SalePrice', data=plotDf)

0a14a4407c4be9439a567e9443514119.png
建筑面积和房价的关系图

从图中可以看出房屋评分对房价起着相当重要的作用,基本上评分越高的房子房价也越贵。建筑面积这个变量与房屋面积类似,建筑面积越大一般都会导致房价越高。

sns.scatterplot(x='BasementArea', y='SalePrice', data=plotDf)

ee40359b3f9d4ccc9904a73b62fe330f.png
地下室面积和房价的关系图

可以看到地下室面积这一列数据中有很多值为0,新建一个分类变量‘HasBasement’:如果BasementArea为0则该变量为0,否则该变量为1

fullDf['HasBasement'] = 0
fullDf.at[fullDf['BasementArea'] > 0, 'HasBasement'] = 1

sns.barplot(x='BuildingYearBins', y='SalePrice', data=plotDf)

4eef24e65c42969c85c0dfbaf42c9620.png
建筑年份和房价的关系图
sns.boxplot(x='RepairYear', y='SalePrice', data=plotDf)

1744c373e1234e8c3edfaf4f51f56d22.png
修复年份和房价的关系图

可以看到年代很久远的房子和最近的新房子售价较高,说明建筑年份与房价有一定的相关性。年代久远的房子可能是因为其历史价值而被追捧,新房子的价格高也说明了相比旧房子人们更喜欢新建的房子。而修复年份与房价的相关性不大,很多房子没有修复,但是它们的价格差距很大,修复过的房子其价格也没有明显的规律,因此将修复年份列从数据集中删除。建筑年份已转化为虚拟变量,将建筑年份列也删除。

fullDf.drop(['RepairYear', 'BuildingYearBins'], axis=1, inplace=True)

sns.scatterplot(x='Longitude', y='Latitude', hue='SalePrice', data=plotDf)

e1e5776b81cfaef58f6c16b3a6027dfd.png
房屋的经纬度分布

将房屋的经纬度作为坐标点画出散点图,并用颜色表示房价的高低。

利用matplotlib的Basemap模块可以在地图上根据经纬度画出点

from mpl_toolkits.basemap import Basemap

Bm = Basemap(llcrnrlon=-122.6, llcrnrlat=47.1, urcrnrlon=-121.5, urcrnrlat=47.8, resolution='h')
# Basemap前四个参数是画出的经纬度的范围,最后一个参数是分辨率
Bm.drawmapboundary(fill_color='aqua')    #将地图填充为蓝色
Bm.drawcoastlines()    # 画出海岸线
lons = plotDf.Longitude
lats = plotDf.Latitude
x, y = Bm(lons, lats)    # x、y为房屋的经纬度
Bm.scatter(x, y, marker='o', color='m')    #用散点图画出每个房屋的位置

78382d1d3b2fa50eda1467126a54755a.png
地图上房屋的分布

图中黑线是陆地和海洋的分隔线,黑线中间的部位是海洋河流,黑线外边的区域是陆地。可以看到在陆地上样本点的分布很密集。

分别选出房价最高和最低的十个点,在地图上画出它们的位置。

max_lons = plotDf.sort_values(by='SalePrice', ascending=False).Longitude[:10]
max_lats = plotDf.sort_values(by='SalePrice', ascending=False).Latitude[:10]
min_lons = plotDf.sort_values(by='SalePrice', ascending=False).Longitude[-10:]
min_lats = plotDf.sort_values(by='SalePrice', ascending=False).Latitude[-10:]

Bm1 = Basemap(llcrnrlon=-122.6, llcrnrlat=47.1, urcrnrlon=-121.5, urcrnrlat=47.8, resolution='h')
Bm1.drawmapboundary(fill_color='aqua')
Bm1.drawcoastlines()
x1, y1 = Bm1(max_lons, max_lats)
Bm1.scatter(x1, y1, marker='o', color='m')

7a3c49e7058107103135777e67ef7d12.png
房价前十的房屋在地图上的分布
Bm2 = Basemap(llcrnrlon=-122.6, llcrnrlat=47.1, urcrnrlon=-121.5, urcrnrlat=47.8, resolution='h')
Bm2.drawmapboundary(fill_color='aqua')
Bm2.drawcoastlines()
x2, y2 = Bm2(min_lons, min_lats)
Bm2.scatter(x2, y2, marker='o', color='m')

5855260fa486dedffcd5e1e0b62117a2.png
房价后十的房屋在地图上的分布

可以看到房价最高的十个点基本都分布在湖边,而房价最低的十个点基本都分布在内陆,这也验证了seaborn画出的散点图,房价高的点基本上都位于水畔,风景好的地方房价自然也高

特征选择

根据数据可视化的结果,与房价比较相关的几个特征为卧室数、浴室数、房屋面积、房屋评价、销售月份、建筑年份。

下面使用几种不同的方法来选取我们训练模型时需要的特征

1. 相关系数

求出每两个特征之间的相关系数,并用热力图画出。由于建筑年份和销售月份是虚拟变量,不便于单独拿出来和其他特征进行相关性比较,也会使热力图显得很乱,这里没有显示这两个特征与其他特征的相关系数。

sns.heatmap(fullDf.iloc[0:10000, [0,1,2,3,4,5,6,7,8,29]
                       ].corr(), annot = True, vmin = 0, vmax = 1)

1a94576df7bdb5823530be10f700d50c.png
特征之间的相关系数热力图

可以看到与房价的相关系数在0.1以上的几个特征为:卧室数、浴室数、房屋面积、建筑面积、房屋评价、地下室面积、是否有地下室。

2. 迭代特征选择

假设最后选择的特征集合为X',所有特征的集合为X,辅助特征集合为X”。

  1. 初始化X’=空集,X”=X;
  2. 对于X”中的所有特征,每次取出一个进行训练,选出一轮中得分最高的那个特征加入X',同时在X"中删除该特征;
  3. 重复第二步,直到新加入任何特征模型性能都无提升
# 引入线性回归模型和交叉检验
from sklearn import linear_model
from sklearn.model_selection import cross_val_score

lm = linear_model.LinearRegression()
df = fullDf[0:10000]
features = ['BasementArea', 'BathroomNum', 'BedroomNum', 'BuildingArea', 
            'Latitude', 'LivingArea', 'Longitude', 'Rating',
            'Year_(1900, 1915]', 'Year_(1915, 1930]', 'Year_(1930, 1945]',
            'Year_(1945, 1960]', 'Year_(1960, 1975]', 'Year_(1975, 1990]',
            'Year_(1990, 2005]', 'Year_(2005, 2020]', 'Month_1', 'Month_2',
            'Month_3', 'Month_4', 'Month_5', 'Month_6', 'Month_7', 'Month_8',
            'Month_9', 'Month_10', 'Month_11', 'Month_12', 'HasBasement']
y = df['SalePrice']

selected_features = []
rest_features = features[:]
best_score = -1e+12
'''
交叉检验的评分标准选择'neg_mean_squared_error',即均方误差,在这里为负数,绝对值越小表示误差越小,
因此初始的best_score设置为一个绝对值很大的负值
'''
while len(rest_features)>0:
    temp_best_i = ''
    temp_best_score = -1e+12
    for feature_i in rest_features:
        temp_features = selected_features + [feature_i]
        X = df[temp_features]
        scores = cross_val_score(lm, X, y, cv=5, scoring='neg_mean_squared_error')
        score = np.mean(scores)
        if score > temp_best_score:
            temp_best_score = score
            temp_best_i = feature_i
    print("select",temp_best_i,"acc:",temp_best_score)
    if temp_best_score > best_score:
        best_score = temp_best_score
        selected_features += [temp_best_i]
        rest_features.remove(temp_best_i)
    else: 
        break
print("best feature set: ",selected_features,"score: ",best_score)
# 以下为输出结果
select LivingArea acc: -71349166475.62779
select Latitude acc: -61308736701.538574
select Rating acc: -57228772728.23087
select Longitude acc: -55217175788.554344
select Year_(1900, 1915] acc: -54266373901.291626
select Year_(1915, 1930] acc: -53265361117.21611
select Year_(1930, 1945] acc: -52314470279.37959
select Year_(1945, 1960] acc: -51128635898.9767
select BedroomNum acc: -50497821841.750175
select Year_(1960, 1975] acc: -49859660488.61521
select BathroomNum acc: -49271438161.992
select Month_4 acc: -49167258638.85094
select Month_3 acc: -49102030213.97478
select Year_(1990, 2005] acc: -49067174888.75468
select Month_12 acc: -49049580993.40038
select Year_(2005, 2020] acc: -49034235274.66737
select Year_(1975, 1990] acc: -48798669332.794945
select HasBasement acc: -48793266204.60174
select Month_7 acc: -48789312424.92695
select Month_9 acc: -48788246493.10238
select Month_10 acc: -48793653843.01216
best feature set:  ['LivingArea', 'Latitude', 'Rating', 'Longitude', 'Year_(1900, 1915]', 'Year_(1915, 1930]', 'Year_(1930, 1945]', 'Year_(1945, 1960]', 'BedroomNum', 'Year_(1960, 1975]', 'BathroomNum', 'Month_4', 'Month_3', 'Year_(1990, 2005]', 'Month_12', 'Year_(2005, 2020]', 'Year_(1975, 1990]', 'HasBasement', 'Month_7', 'Month_9'] score:  -48788246493.10238

迭代法选择出的特征为房屋面积、经纬度、房屋评价、建筑年份、卧室数、浴室数、销售月份、是否有地下室。

3. 随机森林特征选择法——Gini Importance

使用Gini指数表示节点的纯度,Gini指数越大纯度越低。然后计算每个节点的Gini指数 - 子节点的Gini指数之和,记为Gini decrease。最后将所有树上相同特征节点的Gini decrease加权的和记为Gini importance,该数值会在0-1之间,该数值越大即代表该节点(特征)重要性越大。

from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor

# X为数据集的特征,y为数据集的标签
X = fullDf[:][0:10000]
X.drop('SalePrice', axis=1, inplace=True)
y = trainDf['SalePrice']
# 将数据集分为训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

rfReg = RandomForestRegressor(n_estimators=50)
rfReg.fit(X_train, y_train)

combine_lists = lambda item: [item[0], item[1]]
feature_importances = list(map(combine_lists, zip(X_train.columns, rfReg.feature_importances_)))
feature_importances = pd.DataFrame(feature_importances, columns=['feature', 'importance']).sort_values(by='importance', ascending=False)
feature_importances    #输出每个特征的Gini Importance,并按从大到小的顺序排序

84cb138e85de8e0e43758f21764da31a.png
每个特征的Gini Importance大小

根据以上三种特征选择的结果,最后选取地下室面积、浴室数、卧室数、建筑面积、经纬度、房屋面积、房屋评价、建筑年份、销售月份这几个特征,删除是否有地下室的特征。

fullDf.drop('HasBasement', axis=1, inplace=True)

算法选择

选择线性回归算法和随机森林算法,评价指标选择决定系数(R Squared)

from sklearn.metrics import r2_score

X = fullDf[:][0:10000]
X.drop('SalePrice', axis=1, inplace=True)
y = trainDf['SalePrice']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

rfReg = RandomForestRegressor(n_estimators=50)
lReg = linear_model.LinearRegression()
rfReg.fit(X_train, y_train)
lReg.fit(X_train, y_train)
print('RandomForest_R2: {}nLinearRegression_R2: {}'.format(r2_score(y_test, rfReg.predict(X_test)),
                                                            r2_score(y_test, lReg.predict(X_test))))
# 以下为输出结果
RandomForest_R2: 0.8329728730385538
LinearRegression_R2: 0.6319265123160838

从两种模型的决定系数可以看出,随机森林算法比线性回归算法的效果要好。因此选择随机森林算法对测试集进行预测。

房价预测

使用随机森林算法对测试集进行预测。由于选择的某些特征量纲不一样,它们的单位不在一个数量级上,可能会导致有的特征被忽略,为了消除量纲的影响,对特征进行归一化。另外,由于房屋价格的分布呈右偏态,对其取对数,使其分布接近正态分布。

from sklearn.preprocessing import MinMaxScaler

tempDf = fullDf[:]
tempDf.drop('SalePrice', axis=1, inplace=True)    # tempDf为测试集和训练集中不包含房价的所有特征

minmax_scaler = MinMaxScaler()
minmax_scaler.fit(tempDf)
X = minmax_scaler.transform(tempDf)
X = pd.DataFrame(X, columns=tempDf.columns)      # X为测试集和训练集归一化后的所有特征
new_X = X[:][0:10000]    # new_X为训练集的所有特征
y = np.log(trainDf['SalePrice'])    # y为取对数后的房价
predict_X = X[:][10000:]    # predict_X为测试集的所有特征

rfReg = RandomForestRegressor(n_estimators=50)
rfReg.fit(new_X, y)
predict_y = rfReg.predict(predict_X)
predict_y = np.exp(predict_y)    # 将房价由对数形式还原

predictDf = pd.DataFrame(predict_y, columns=['price'])    
predictDf.to_csv('predict.csv', index = False)    # 将结果转化成规定的形式输出到csv文件中
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值