主要来源 Kaggle 上的一个入门挑战 房价预测 房价预测
https://www.kaggle.com/c/house-prices-advanced-regression-techniques
房价预测
数据导入—— 了解特征列 ——影响最大的列与目标列y作图 —— 相关性最大的10个特征并做热图 —— 特征间的散点图
import pandas as pd
import warnings
warnings.filterwarnings("ignore")
#控制警告器,忽略警告
train = pd.read_csv('https://labfile.oss.aliyuncs.com/courses/1363/HousePrice.csv')
train
#读取数据
train.head() #前5条数据
train.tail() #最后5份数据
train.shape#查看数据的形状
train.columns #查看列名
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
#将matplotlib的图表直接嵌入到Notebook之中
color = sns.color_palette()
#调色盘,设置默认颜色
sns.set_style('darkgrid')
#seaborn.set_style(style=None, rc=None)
#这里style可选参数值有:darkgrid,whitegrid,dark,white,ticks
#设置不同的背景风格
下面做占地面积与房价的散点图
fig, ax = plt.subplots()
#绘制散点图
#plt.subplots()是一个返回包含图形和轴对象的元组的函数
ax.scatter(x=train['GrLivArea'], y=train['SalePrice'])
#给出x,y轴数据
plt.ylabel('SalePrice', fontsize=13)
plt.xlabel('GrLivArea', fontsize=13)
#x,y轴标签
#散点图其他语句:legend添加图例,xlim设置x轴刻度范围,title标题
plt.show() #画出图像
散点图发现存在异常值,做异常值处理,直接drop删掉
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)#fontsize是标签大小
plt.xlabel('GrLivArea', fontsize=13)
plt.show()
做完图,给一个结论:从图显示的结果可以看出,占地面积与房价大致呈线性相关关系。也就是说,面积越大,房价越高。
分析重要指标xi对y的影响,数值x则做x与y的散点图,类别x则做与y的箱线图。所以下面做质量指标’OverallQual’与房价的箱线图
data = pd.concat([train_drop['SalePrice'], train_drop['OverallQual']], axis=1)
#pd.concat指将数据拼接到一起,将列SalePrice与列OverallQual拼接到一起
#axis=0 表拼接方式是上下堆叠,当axis=1表示左右拼接,默认值是0
# 画出箱线图sns.boxplot
f, ax = plt.subplots(figsize=(8, 6))
#plt.subplots()是一个返回包含图形和轴对象的元组的函数,figsize给出了图像长宽
fig = sns.boxplot(x='OverallQual', y="SalePrice", data=data)
#seaborn.boxplot做箱线图,给xy命名,给数据集
fig.axis(ymin=0, ymax=800000)
#设置纵坐标刻度范围
结论: OverallQual 的等级越高,也就是房子的材料和质量越好,房价越高。
上面先挑了两个重要的x,分别研究了与y的关系图后,后面通过热图来分析所有特征之间的相关性以及与房价的关系
统计跟我目标变量最有联系的前10个指标,统计热图sns.heatmap
import numpy as np
k = 10
corrmat = train_drop.corr()
# 获得相关性矩阵corr
# 获得相关性最高的 K 个特征
#nlargest()的优点就是能一次看到最大的几行,而且不需要排序
#nlargest()的第一个参数就是截取的行数。第二个参数就是依据的列名。
cols = corrmat.nlargest(k, 'SalePrice')['SalePrice'].index
# cols获得相关性最高的 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)
#sns.heatmap中参数annot=True, # 注入数字 square=True, # 单元格为正方形 fmt='.2f', # 字符串格式代码yticklabels=cols.values, # 列标签
plt.show()
绘制相关性强的前几个指标的散点图
sns.set()
cols = ['SalePrice', 'OverallQual', 'GrLivArea',
'GarageCars', 'TotalBsmtSF', 'FullBath', 'YearBuilt']
sns.pairplot(train_drop[cols], size=2.5)
plt.show()
#sns.pairplot用来展示两两特征之间的散点图
上面只是使用可视化的方法来初步查看数据,让我们先对数据有一个初步的认识。
在前面的数据预览时,可以看出第一列为 ID ,也就是说该列对房价没有影响,因此这里先把该列删除。删除之后的列数为 80 列。
train_drop1 = train_drop.drop("Id", axis=1)
#drop删除某几个值,或者列
train_drop1.head()
SalePrice 列为房价,也即是所要预测的列,这里先对其进行分析。使用 describe 方法查看数据的基本情况。
train_drop1['SalePrice'].describe()
下面画出房价列的分布图。这里使用 SciPy 提供的接口来进行相关的计算
from scipy.stats import norm, skew
sns.distplot(train_drop1['SalePrice'], fit=norm)
#sns.distplot显示直方图以及核密度估计曲线
# 获得均值和方差
(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')
SalePrice列画QQ图,检验是否正态分布
from scipy import stats
fig = plt.figure()
res = stats.probplot(train_drop1['SalePrice'], plot=plt)
plt.show()
#检验样本数据概率分布是否为正态分布
#越靠近红色线,越为正态分布
由分布图和QQ图可以看到,该数据集貌似不是常见的正态分布,即高斯分布。
一般预测模型都会选用机器学习算法,而许多机器学习算法都是基于数据是高斯分布的条件下推导出来的,因此,这里先把房价处理成为高斯分布的形式。这里直接使用 NumPy 提供的数据平滑接口来实现。
下面用np.log1p(x),即取对数,这样可以使得数据在一定程度上符合正态分布的特征。
数据平滑处理 – log1p( ) 和 exmp1( )
数据预处理时首先可以对偏度比较大的数据用og1p函数进行转化,使其更加服从高斯分布,此步处理可能会使我们后续的分类结果得到一个好的结果。
平滑问题很容易处理掉,导致模型的结果达不到一定的标准,log1p( )能够避免复值得问题 — 复值指一个自变量对应多个因变量
log1p( ) 的使用就像是一个数据压缩到了一个区间,与数据的标准类似。其逆运算就是expm1的函数
log1p = log(x+1)
数据平滑处理+重画平滑后的分布图及QQ图:
# 平滑数据,即取对数
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]
#isnull判断,sum求和,sort_values排序,有80列先只显示前20
#pandas中的sort_values()函数原理类似于SQL中的order by
#求缺失率
train_na = (train_drop1.isnull().sum() / len(train)) * 100
#删除无缺失的列名,一共80列太多了,只显示出前30个
train_na = train_na.drop(
train_na[train_na == 0].index).sort_values(ascending=False)[:30]
#pd.DataFrame
missing_data = pd.DataFrame({'Missing Ratio': train_na})
missing_data.head(20)
将缺失情况可视化,缺失率
f, ax = plt.subplots(figsize=(15, 6))
plt.xticks(rotation='90')
#xticks()函数用来设置X轴--刻度、标签
#plt.xticks(x, calendar.month_name[1:13],color='blue',rotation=60) #参数x空值X轴的间隔,第二个参数控制每个间隔显示的文本,后面两个参数控制标签的颜色和旋转角度
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')
#data.fillna()填充空值
#对这些类似于车库的面积和地下室面积相关数值型特征的列填充 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()))
#对同一Neighborhood下的均值进行填充
#MSZoning 表示分区分类,这里使用众数来填充。
feature = []
train_drop1['MSZoning'] = train_drop1['MSZoning'].fillna(
train_drop1['MSZoning'].mode()[0])
#采用众数填充:df.fillna(df.mode().iloc[0])用mode得到众数,因为可能不止一个,取第一个
#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]
空缺值处理的方法:
直接删除整列,填充None、填充0、填充均值、中位数、众数、常值填充
上面介绍了不同列的缺失情况的处理方法:
进过数据填充之后,已经没有了缺失值。
在数据集中,特征主要分为两种,分别是数值型特征和类别型特征。数值型特征就是连续数值组成的特征,例如房子的面积;而类别型特征则是由两类或两类以上类别组成的特征,例如房子是否带游泳池,即包含是和否两个类别。
类别列编码
在数据集中有一些特征属于类别型特征,但却用数值来表示,例如销售月份。因此,要转换其成为类别型特征。
#将类别数据的类型改为str
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']
先通过 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 的偏度最大,偏度越大也就意味着该列的数据分布越偏离高斯分布。
除了 log 变换,还可以使用 Box-Cox 转换来对数据分布纠偏。
现在通过 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)
后面BoxCox 方法和One-Hot的代码,我还有点看不懂。。。。数学理论待补
对数据进行划分,选用 70% 的数据来训练,选用 30% 的数据来测试。
from sklearn.linear_model import Lasso
from sklearn.metrics import mean_squared_error
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)
整个流程:首先对数据进行可视化,然后对数据进行分析,填充缺失值,然后手工提取特征,最后构建预测模型。这一流程通常为数据分析的基本流程。
看来,数据清洗部分真的是最费时间的,后面套模型可能直接几行代码就解决了