45Kaggle 数据分析项目入门实战--波士顿房价数据分析预测

房价预测简介

本次实验主要来源 Kaggle 上的一个入门挑战 房价预测。房价预测也是 Kaggle 上经典的数据分析入门项目之一。本次实验就是通过该项目来带领你入门数据分析。

image.png

我们都知道,房价一般会与房间面积的大小、房子所在的城市、房子的空间布局等因素有关。而房价预测的任务就是给定与房价相关因素的数据,通过这些数据预测出房子的价格。

数据预览

这里使用的是 Kaggle 房价预测 提供的数据,其提供的数据集是 csv 格式的文件,我们可以使用 * Pandas* 对其进行直接的读取。
首先,实验加载所需数据。

import pandas as pd
import warnings
warnings.filterwarnings("ignore")

train = pd.read_csv(
    'https://labfile.oss.aliyuncs.com/courses/1363/HousePrice.csv')
train

使用 Pandas 读取得到的数据是 Pandas 特有的 DataFrame 数据格式,我们可以使用 .head() 来查看数据的前 5 份。

train.head()

同理使用 .tail 方法来查看最后 5 份数据。

train.tail()

使用 .shape 方法查看数据的形状。

train.shape

从上面的显示结果可以看到,总共含有 1460 份数据,每份数据含有 81 列。现在查看数据中都含有哪些列

train.columns

最后一列 SalePrice 表示房子的价格,而前面的 80 列表示与房价相关的因素,通常也称为特征列。例如几个特征列如下:
YearBuilt: 建筑年份
GarageCars:车库的容量
HouseStyle:房子的风格

初识数据

在数据集中 GrLivArea 表示占地面积,现在来看一下房子占地面积与房价的关系,这里通过画图来直观的判断。这里我们使用的绘图工具是 MatplotlibSeaborn 。先导入相关的库。

import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
color = sns.color_palette()
sns.set_style('darkgrid')

需要注意的是这里占地面积的单位是平方英尺而不是平方米,所以对于房子占地面积为 2000 或 3000 的数据不必惊讶。

fig, ax = plt.subplots()
# 绘制散点图
ax.scatter(x=train['GrLivArea'], y=train['SalePrice'])
plt.ylabel('SalePrice', fontsize=13)
plt.xlabel('GrLivArea', fontsize=13)
plt.show()

从图显示的结果可以看出,占地面积与房价大致呈线性相关关系。也就是说,面积越大,房价越高。此外,细心观察可以发现,上图中右下角有两个数据点有点不正常,通常将这类点称之为异常值点。现在将其删除。

# 删除异常值点
train_drop = train.drop(
    train[(train['GrLivArea'] > 4000) & (train['SalePrice'] < 300000)].index)

# 重新绘制图
fig, ax = plt.subplots()
ax.scatter(train_drop['GrLivArea'], train_drop['SalePrice'])
plt.ylabel('SalePrice', fontsize=13)
plt.xlabel('GrLivArea', fontsize=13)
plt.show()

上面我们主要画出的是房子占地面积与房价的关系,而占地面积和房价都是连续的数值,因此可以直接画出它们的关系。而在数据集中还存在另一种类别型特征,对于这类数据,可以通过 * 箱线图* 进行画出。例如,在数据集中 OverallQual 表示房子的材料和成品的质量,是一个类别型特征,现在画出该特征与房价的关系。

var = 'OverallQual'
data = pd.concat([train_drop['SalePrice'], train_drop[var]], axis=1)
# 画出箱线图
f, ax = plt.subplots(figsize=(8, 6))
fig = sns.boxplot(x=var, y="SalePrice", data=data)
fig.axis(ymin=0, ymax=800000)

从上图中可以看出, OverallQual 的等级越高,也就是房子的材料和质量越好,房价越高。
上面分析了单个特征与房价的关系,现在可以通过热图来分析所有特征之间的相关性以及与房价的关系。这里为了便于查看只取了前 10 个相关度最高的特征。

import numpy as np

k = 10
corrmat = train_drop.corr()  # 获得相关性矩阵
# 获得相关性最高的 K 个特征
cols = corrmat.nlargest(k, 'SalePrice')['SalePrice'].index
# 获得相关性最高的 K 个特征组成的子数据集
cm = np.corrcoef(train_drop[cols].values.T)
# 绘制热图
sns.set(font_scale=1.25)
hm = sns.heatmap(cm, cbar=True, annot=True, square=True, fmt='.2f', annot_kws={
                 'size': 10}, yticklabels=cols.values, xticklabels=cols.values)
plt.show()

从上面结果可以看到,房价大致与占地面积和房子质量相关度最高,这也很符合事实。下面画出这些特征之间的关系。

# 绘制散点图
sns.set()
cols = ['SalePrice', 'OverallQual', 'GrLivArea',
        'GarageCars', 'TotalBsmtSF', 'FullBath', 'YearBuilt']
sns.pairplot(train_drop[cols], size=2.5)
plt.show()

数据预处理

上面只是使用可视化的方法来初步查看数据,让我们先对数据有一个初步的认识。现在来对数据进行简单的预处理。在前面的数据预览时,可以看出第一列为 ID ,也就是说该列对房价没有影响,因此这里先把该列删除。删除之后的列数为 80 列。

train_drop1 = train_drop.drop("Id", axis=1)
train_drop1.head()

SalePrice 列为房价,也即是所要预测的列,这里先对其进行分析。使用 describe 方法查看数据的基本情况。

train_drop1['SalePrice'].describe()

画出其分布图。这里使用 SciPy 提供的接口来进行相关的计算

from scipy.stats import norm, skew

sns.distplot(train_drop1['SalePrice'], fit=norm)

# 获得均值和方差
(mu, sigma) = norm.fit(train_drop1['SalePrice'])
print('\n mu = {:.2f} and sigma = {:.2f}\n'.format(mu, sigma))

# 画出数据分布图
plt.legend(['Normal dist. ($\mu=$ {:.2f} and $\sigma=$ {:.2f} )'.format(mu, sigma)],
           loc='best')
plt.ylabel('Frequency')
# 设置标题
plt.title('SalePrice distribution')

可以看到,该数据集貌似不是常见的正态分布,即高斯分布。现在画出其 Q-Q 图

from scipy import stats

fig = plt.figure()
res = stats.probplot(train_drop1['SalePrice'], plot=plt)
plt.show()

一般预测模型都会选用机器学习算法,而许多机器学习算法都是基于数据是高斯分布的条件下推导出来的,因此,这里先把房价处理成为高斯分布的形式。这里直接使用 NumPy 提供的数据平滑接口来实现。

# 平滑数据
train_drop1["SalePrice"] = np.log1p(train_drop1["SalePrice"])

# 重新画出数据分布图
sns.distplot(train_drop1['SalePrice'], fit=norm)

# 重新计算平滑后的均值和方差
(mu, sigma) = norm.fit(train_drop1['SalePrice'])
print('\n mu = {:.2f} and sigma = {:.2f}\n'.format(mu, sigma))

plt.legend(['Normal dist. ($\mu=$ {:.2f} and $\sigma=$ {:.2f} )'.format(mu, sigma)],
           loc='best')
plt.ylabel('Frequency')
plt.title('SalePrice distribution')

# 画出 Q-Q 图

fig = plt.figure()
res = stats.probplot(train_drop1['SalePrice'], plot=plt)
plt.show()

经过平滑之后,数据已经大致呈高斯分布的形状。

特征工程

因为数据集可能会含有一些缺失值,我们通过 isnull 方法来查看。

train_drop1.isnull().sum().sort_values(ascending=False)[:20]  # 取前 20 个数据

这里为了便于观察,可以求出其缺失率。

train_na = (train_drop1.isnull().sum() / len(train)) * 100
train_na = train_na.drop(
    train_na[train_na == 0].index).sort_values(ascending=False)[:30]
missing_data = pd.DataFrame({'Missing Ratio': train_na})
missing_data.head(20)

从上面的结果,可以看出,在数据集中 PoolQC 列的数据缺失达到 99.45% ,MiscFeature 列的数据缺失达到 96.16%。为了更加直观的观察,对其进行可视化。

f, ax = plt.subplots(figsize=(15, 6))
plt.xticks(rotation='90')
sns.barplot(x=train_na.index, y=train_na)
plt.xlabel('Features', fontsize=15)
plt.ylabel('Percent of missing values', fontsize=15)
plt.title('Percent missing data by feature', fontsize=15)

从上面的分析中,我们可以看到,大约有 20 列的数据都存在缺失值,在构建预测模型之前需要对其进行填充。
在数据描述中,PoolQC 表示游泳池的质量,缺失了则代表没有游泳池。从上面的分析结果,该列的缺失值最多,这也就意味着许多房子都是没有游泳池的,与事实也比较相符。
除了 PoolQC 列,还有很多情况类似的列,例如房子贴砖的类型等。因此,对这些类别特征的列都填充 None。

feature = ['PoolQC', 'MiscFeature', 'Alley', 'Fence',
           'FireplaceQu', 'GarageType', 'GarageFinish',
           'GarageQual', 'GarageCond', 'BsmtQual',
           'BsmtCond', 'BsmtExposure', 'BsmtFinType1',
           'BsmtFinType2', 'MasVnrType', 'MSSubClass']
for col in feature:
    train_drop1[col] = train_drop1[col].fillna('None')

对这些类似于车库的面积和地下室面积相关数值型特征的列填充 0 ,表示没有车库和地下室。

feature = ['GarageYrBlt', 'GarageArea', 'GarageCars',
           'BsmtFinSF1', 'BsmtFinSF2', 'BsmtUnfSF',
           'TotalBsmtSF', 'BsmtFullBath', 'BsmtHalfBath',
           'MasVnrArea', 'Electrical']
for col in feature:
    train_drop1[col] = train_drop1[col].fillna(0)

LotFrontage 表示与街道的距离,每个房子到街道的距离可能会很相似,因此这里采用附近房子到街道距离的中值来进行填充。

train_drop1["LotFrontage"] = train_drop1.groupby("Neighborhood")["LotFrontage"].transform(
    lambda x: x.fillna(x.median()))

MSZoning 表示分区分类,这里使用众数来填充。

feature = []
train_drop1['MSZoning'] = train_drop1['MSZoning'].fillna(
    train_drop1['MSZoning'].mode()[0])

Utilities 列与所要预测的 SalePrice 列不怎么相关,这里直接删除该列。

train_drop2 = train_drop1.drop(['Utilities'], axis=1)

Functional 表示功能,数据描述里说缺失值代表房子具有基本的功能。因此对其进行常值填充。

train_drop1["Functional"] = train_drop1["Functional"].fillna("Typ")

train_drop2.isnull().sum().sort_values(ascending=False)[:20]

可以看到,进过数据填充之后,已经没有了缺失值。
在数据集中,特征主要分为两种,分别是数值型特征和类别型特征。数值型特征就是连续数值组成的特征,例如房子的面积;而类别型特征则是由两类或两类以上类别组成的特征,例如房子是否带游泳池,即包含是和否两个类别。
在数据集中有一些特征属于类别型特征,但却用数值来表示,例如销售月份。因此,要转换其成为类别型特征。

feature = ['MSSubClass', 'OverallCond', 'YrSold', 'MoSold']
for col in feature:
    train_drop2[col] = train_drop2[col].apply(str)

对一些类别型的特征列进行编码。将其转换成为用数值来表示的类别型特征。

from sklearn.preprocessing import LabelEncoder

cols = ['FireplaceQu', 'BsmtQual', 'BsmtCond', 'GarageQual', 'GarageCond',
        'ExterQual', 'ExterCond', 'HeatingQC', 'PoolQC', 'KitchenQual', 'BsmtFinType1',
        'BsmtFinType2', 'Functional', 'Fence', 'BsmtExposure', 'GarageFinish', 'LandSlope',
        'LotShape', 'PavedDrive', 'Street', 'Alley', 'CentralAir', 'MSSubClass', 'OverallCond',
        'YrSold', 'MoSold']
for c in cols:
    lbl = LabelEncoder()
    lbl.fit(list(train_drop2[c].values))
    train_drop2[c] = lbl.transform(list(train_drop2[c].values))
train_drop2[cols].head()

因为数据没有给出房子的总面积,也就是说没有统计出一楼、二楼以及地下室的总面积。不过我们可以通过数据集来手动提取这一特征。

train_drop2['TotalSF'] = train_drop2['TotalBsmtSF'] + \
    train_drop2['1stFlrSF'] + train_drop2['2ndFlrSF']

在前文分析房子价格 SalePrice 时,由于其不服从正态分布,因此使用平滑的方法让其服从正态分布。这里这对数据集中的数值特征列进行同样的分析。先通过 SciPy 提供的接口 scipy.stats.skew 来判断其 偏度

numeric_feats = train_drop2.dtypes[train_drop2.dtypes != "object"].index

# 检测特征值
skewed_feats = train_drop2[numeric_feats].apply(
    lambda x: skew(x.dropna())).sort_values(ascending=False)
print("\nSkew in numerical features: \n")
skewness = pd.DataFrame({'Skew': skewed_feats})
skewness.head(10)

从上面的结果可知,列 MiscVal 的偏度最大,偏度越大也就意味着该列的数据分布越偏离高斯分布。
现在通过 BoxCox 方法「矫正」这些特征列。

from scipy.special import boxcox1p
skewness = skewness[abs(skewness) > 0.75]


skewed_features = skewness.index
lam = 0.15
for feat in skewed_features:
    train_drop2[feat] = boxcox1p(train_drop2[feat], lam)

对那些用符号表示的类别型特征用 One-Hot 来进行编码。

data_y = train_drop2['SalePrice']
data_X = train_drop2.drop(['SalePrice'], axis=1)

data_X_oh = pd.get_dummies(data_X)
print(data_X_oh.shape)

预测模型

上面主要完成了对数据的预处理和特征工程,现在需要建立预测模型来对所处理的模型进行预测。

from sklearn.linear_model import Lasso
from sklearn.metrics import mean_squared_error

现在对数据进行划分,选用 70% 的数据来训练,选用 30% 的数据来测试。

data_y_v = data_y.values  # 转换为 NumPy 数组
data_X_v = data_X_oh.values
length = int(len(data_y)*0.7)

# 划分数据集
train_y = data_y_v[:length]
train_X = data_X_v[:length]
test_y = data_y_v[length:]
test_X = data_X_v[length:]

构建模型,并进行训练。这里使用的是 Lasso 模型,其是线性回归的一种改进版本。

model = Lasso()
model.fit(train_X, train_y)

使用训练好的模型进行预测。并使用均方差来衡量预测结果的好坏。

y_pred = model.predict(test_X)
mean_squared_error(test_y, y_pred)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容