Kaggle房价预测:数据预处理——练习

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qilixuening/article/details/75153131

本篇主要借鉴了Kaggle基础问题——房价预测的两篇教程Comprehensive data exploration with PythonHouse Prices EDA并进行总结。

基于上一篇数据探索,我们可以对整个数据集的基本特征进行大致了解,并同时学习到了PandasSeaborn的一些操作技巧。接下来,我们以此为基础,进行数据的预处理

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler, OneHotEncoder
import seaborn as sns
from scipy import stats
%matplotlib inline

缺失值处理

缺失值处理有两种方案,一种是分析含缺失值的特征对任务有没有用,没用的特征直接删除,有用的特征依据缺失量,少则删除样本,多则用mean,median或mod补全;另一种方案是分析这些缺失值缺失的原因,并用一定方法将其转换为一类数据(成为类型变量的一个类型)。

1.缺失值含义分析与删除

依然是先导入数据:

df_train = pd.read_csv(r'E:\kaggle\house_price_regression\train.csv')
进行数据分析,我们首先要对缺失值进行处理,因此,首先要看一下缺失值具体出现在那些特征中,缺失信息是否对整个特征有较大影响,缺失信息是否有其他特殊意义? 先进行缺失值的缺失情况统计:
na_count = df_train.isnull().sum().sort_values(ascending=False)
na_rate = na_count / len(df_train)
na_data = pd.concat([na_count,na_rate],axis=1,keys=['count','ratio'])
na_data.head(20)
count ratio
PoolQC 1453 0.995205
MiscFeature 1406 0.963014
Alley 1369 0.937671
Fence 1179 0.807534
FireplaceQu 690 0.472603
LotFrontage 259 0.177397
GarageCond 81 0.055479
GarageType 81 0.055479
GarageYrBlt 81 0.055479
GarageFinish 81 0.055479
GarageQual 81 0.055479
BsmtExposure 38 0.026027
BsmtFinType2 38 0.026027
BsmtFinType1 37 0.025342
BsmtCond 37 0.025342
BsmtQual 37 0.025342
MasVnrArea 8 0.005479
MasVnrType 8 0.005479
Electrical 1 0.000685
Utilities 0 0.000000

统计发现,整个数据集中有19个特征存在不同程度的信息缺失,让我们逐一分析:

首先,如果某一特征的数据缺失量达到15%以上,那这项特征应该予以删除并认为数据集中不存在这样的特征(也就是说我们并不会设法去填补这些特征的缺失值,因为假定它是不存在的),因此删除数据的’PoolQC’, ‘MiscFeature’, ‘Alley’这几列(这应该不会导致数据的有效信息量下降,因为这些特征的字面含义似乎根本与房价无关,难怪会有这么多缺失值XD,而且这些特征的有效数据具有各种离群值)。

然后,在剩下的含缺失值变量中,以Garage开头的5个GarageX特征具有相同数量的缺失值,据此推测他们可能代表的是同一组观测值,而关于Garage的信息,’GarageCars’已经能够很好地表征了,因此删除这几个特征,对BsmtX也可以进行同样的操作。

对于MasVnrAreaMasVnrType,根据其字面意思我们认为它们并不重要,而且它们与YearBuiltOverallQual有较强的相关性。因此,我们删除这两个特征也不会丢失任何信息。

总的来看,除了Electrical,其他含缺失值的变量我们都已经删除了,Electrical这个变量下只有一个样本带有缺失值,因此我们不妨删除带有这个缺失值的那各样本。

df_train = df_train.drop(na_data[na_data['count']>1].index, axis=1)  # 删除上述前18个特征 
df_train = df_train.drop(df_train.loc[df_train['Electrical'].isnull()].index)  # 删除 Electrical 取值丢失的样本
df_train.shape  # 缺失值处理后的数据大小:1459个样本,63个特征

(1459, 63)

2.缺失值补全与变换

依然先导入数据,然后进行变量类型的处理

df_tr = pd.read_csv(r'E:\kaggle\house_price_regression\train.csv').drop('Id',axis=1)
df_X = df_tr.drop('SalePrice',axis=1)
df_y = df_tr['SalePrice']
quantity = [attr for attr in df_X.columns if df_X.dtypes[attr] != 'object']  # 数值变量集合
quality = [attr for attr in df_X.columns if df_X.dtypes[attr] == 'object']  # 类型变量集合

for c in quality:  # 类型变量缺失值补全
    df_tr[c] = df_tr[c].astype('category')
    if df_tr[c].isnull().any():
        df_tr[c] = df_tr[c].cat.add_categories(['MISSING'])
        df_tr[c] = df_tr[c].fillna('MISSING')

# 连续变量缺失值补全 
quantity_miss_cal = df_tr[quantity].isnull().sum().sort_values(ascending=False)  # 缺失量均在总数据量的10%以下
missing_cols = quantity_miss_cal[quantity_miss_cal>0].index
df_tr[missing_cols] = df_tr[missing_cols].fillna(0.)  # 从这些变量的意义来看,缺失值很可能是取 0
df_tr[missing_cols].isnull().sum()  # 验证缺失值是否都已补全
LotFrontage    0
GarageYrBlt    0
MasVnrArea     0
dtype: int64

对于离散变量,我们可以进行一元方差分析,获得各个离散变量对房价方差的影响:

# 一元方差分析(类型变量)
def anova(frame, qualitative):
    anv = pd.DataFrame()
    anv['feature'] = qualitative
    pvals = []
    for c in qualitative:
        samples = []
        for cls in frame[c].unique():
            s = frame[frame[c] == cls]['SalePrice'].values
            samples.append(s)  # 某特征下不同取值对应的房价组合形成二维列表
        pval = stats.f_oneway(*samples)[1]  # 一元方差分析得到 F,P,要的是 P,P越小,对方差的影响越大。
        pvals.append(pval)
    anv['pval'] = pvals
    return anv.sort_values('pval')

a = anova(df_tr,quality)
a['disparity'] = np.log(1./a['pval'].values)  # 悬殊度
fig, ax = plt.subplots(figsize=(16,8))
sns.barplot(data=a, x='feature', y='disparity')
x=plt.xticks(rotation=90)
plt.show()

output_14_0.png
由上图示分析可见,不少离散变量的具体取值对最终房价会产生较大影响(例如Neighborhood这个变量,实际上暗含了地段这个影响房价的重要因素),因此,我们可以按照各离散变量相应取值下房价的均值来给各个取值划定一个1,2,3,4来定量描述他们对房价的影响,也就是将离散变量转化为数值型的有序变量:

def encode(frame, feature):
    '''
    对所有类型变量,依照各个类型变量的不同取值对应的样本集内房价的均值,按照房价均值高低
    对此变量的当前取值确定其相对数值1,2,3,4等等,相当于对类型变量赋值使其成为连续变量。
    此方法采用了与One-Hot编码不同的方法来处理离散数据,值得学习
    注意:此函数会直接在原frame的DataFrame内创建新的一列来存放feature编码后的值。
    '''
    ordering = pd.DataFrame()
    ordering['val'] = frame[feature].unique()
    ordering.index = ordering.val
    ordering['price_mean'] = frame[[feature, 'SalePrice']].groupby(feature).mean()['SalePrice']
    # 上述 groupby()操作可以将某一feature下同一取值的数据整个到一起,结合mean()可以直接得到该特征不同取值的房价均值
    ordering = ordering.sort_values('price_mean')
    ordering['order'] = range(1, ordering.shape[0]+1)
    ordering = ordering['order'].to_dict()
    for attr_v, score in ordering.items():
        # e.g. qualitative[2]: {'Grvl': 1, 'MISSING': 3, 'Pave': 2}
        frame.loc[frame[feature] == attr_v, feature+'_E'] = score

quality_encoded = []
# 由于qualitative集合中包含了非数值型变量和伪数值型变量(多为评分、等级等,其取值为1,2,3,4等等)两类
# 因此只需要对非数值型变量进行encode()处理。
# 如果采用One-Hot编码,则整个qualitative的特征都要进行pd,get_dummies()处理
for q in quality:
    encode(df_tr, q)
    quality_encoded.append(q+'_E')
df_tr.drop(quality, axis=1, inplace=True)  # 离散变量已经有了编码后的新变量,因此删去原变量
# df_tr.shape = (1460, 80)
print(quality_encoded, '\n{} qualitative attributes have been encoded.'.format(len(quality_encoded)))

['MSZoning_E', 'Street_E', 'Alley_E', 'LotShape_E', 'LandContour_E', 'Utilities_E', 'LotConfig_E', 'LandSlope_E', 'Neighborhood_E', 'Condition1_E', 'Condition2_E', 'BldgType_E', 'HouseStyle_E', 'RoofStyle_E', 'RoofMatl_E', 'Exterior1st_E', 'Exterior2nd_E', 'MasVnrType_E', 'ExterQual_E', 'ExterCond_E', 'Foundation_E', 'BsmtQual_E', 'BsmtCond_E', 'BsmtExposure_E', 'BsmtFinType1_E', 'BsmtFinType2_E', 'Heating_E', 'HeatingQC_E', 'CentralAir_E', 'Electrical_E', 'KitchenQual_E', 'Functional_E', 'FireplaceQu_E', 'GarageType_E', 'GarageFinish_E', 'GarageQual_E', 'GarageCond_E', 'PavedDrive_E', 'PoolQC_E', 'Fence_E', 'MiscFeature_E', 'SaleType_E', 'SaleCondition_E']
43 qualitative attributes have been encoded.

特征互相关分析与特征选取

对比上述两种不同的缺失值处理手段可以发现,最终,方法1是保留了无缺失值的特征,但其中有大量离散变量,我们无法对其进行特征互相关分析,从而特征的选取与剔除都只能在数值型变量上进行;而方法2则由于进行了一系列操作使得类型变量变为了数值型有序变量,可以进行相关分析。因此,以下操作主要针对方法2进行。

首先,对于有序变量,不能采用常规的相关系数进行计算,而应该采用斯皮尔曼等级相关系数,它不但能处理一般的连续性变量,同时还能很好地表征顺序变量的相关性。

下面先分析各特征与房价的相关性:

def spearman(frame, features):
    '''
    采用“斯皮尔曼等级相关”来计算变量与房价的相关性(可查阅百科)
    此相关系数简单来说,可以对上述encoder()处理后的等级变量及其它与房价的相关性进行更好的评价(特别是对于非线性关系)
    '''
    spr = pd.DataFrame()
    spr['feature'] = features
    spr['corr'] = [frame[f].corr(frame['SalePrice'], 'spearman') for f in features]
    spr = spr.sort_values('corr')
    plt.figure(figsize=(6, 0.25*len(features)))
    sns.barplot(data=spr, y='feature', x='corr', orient='h')    
features = quantity + quality_encoded
spearman(df_tr, features)

output_18_0.png
由上图可见,OverallQual,neighborhood_E等参数与房价呈正相关,而EnclosedPorch等参数与房价呈负相关,这些重要的特征在特征选取时应予以保留。

再来利用上一篇文章中做相关分析的heatmap来分析一下数值型变量间、有序变量间、两种变量间的相关性:

plt.figure(1,figsize=(12,9))  # 连续型变量相关图
corr = df_tr[quantity+['SalePrice']].corr()
sns.heatmap(corr)

plt.figure(2,figsize=(12,9))  # 等级型变量相关图(离散型和伪数值型变量均已被概括为等级型变量)
corr = df_tr[quality_encoded+['SalePrice']].corr('spearman')
sns.heatmap(corr)

plt.figure(3,figsize=(12,9)) # 连续型变量-等级型变量相关图
corr = pd.DataFrame(np.zeros([len(quantity)+1, len(quality_encoded)+1]), 
                    index=quantity+['SalePrice'], columns=quality_encoded+['SalePrice'])
for q1 in quantity+['SalePrice']:
    for q2 in quality_encoded+['SalePrice']:
        corr.loc[q1, q2] = df_tr[q1].corr(df_tr[q2], 'spearman')
sns.heatmap(corr)

<matplotlib.axes._subplots.AxesSubplot at 0x2e6906b5470>
output_20_1.png
output_20_2.png
output_20_3.png
由上图分析,离散变量间、连续变量间、两种变量间某些变量存在互相关型,在特征选取时,应在这些互相关的特征中n选1

在这一步后,我们为了更深入地分析特征与房价的关系,可以查看各个连续变量与不同价格段房价的关系:

# 给房价分段,并由此查看各段房价内那些特征的取值会出现悬殊
poor = df_tr[df_tr['SalePrice'] < 200000][quantity].mean()
pricey = df_tr[df_tr['SalePrice'] >= 200000][quantity].mean()
diff = pd.DataFrame()
diff['attr'] = quantity
diff['difference'] = ((pricey-poor)/poor).values
plt.figure(figsize=(10,4))
sns.barplot(data=diff, x='attr', y='difference')
plt.xticks(rotation=90)
plt.show()

output_22_0.png
由上图可见,高价房的MasVnrAreaPoolArea有较大变化,而廉价房的MiscVal等特征会出现较大悬殊

数据变换与归一化

经过上述分析与处理后,我们还需要对数据进行处理,使其能够按照学习器的特性进行学习,这其中最重要的便是调整数据的分布为正态分布

根据前一篇文章中房价的直方图我们可知,很多实际数据并不是一般的正态分布,假定我们对这样的数据直接进行标准化:

saleprice_scaled = StandardScaler().fit_transform(df_train['SalePrice'][:,np.newaxis])
low_range = np.sort(saleprice_scaled,axis=0)[:10,0]
high_range = np.sort(saleprice_scaled,axis=0)[-10:,0]
high_range

array([ 3.82758058, 4.0395221 , 4.49473628, 4.70872962, 4.728631 , 5.06034585, 5.42191907, 5.58987866, 7.10041987, 7.22629831])
由于原始数据并不为正态分布,标准化达不到理想效果,因此需要对原始数据进行变换

我们先来看一下房价与面积、地下室的散点图:

output,var,var1 = 'SalePrice', 'GrLivArea', 'TotalBsmtSF'
fig, axes = plt.subplots(nrows=1,ncols=2,figsize=(12,6))
df_train.plot.scatter(x=var,y=output,ylim=(0,800000),ax=axes[0])
df_train.plot.scatter(x=var1,y=output,ylim=(0,800000),ax=axes[1])

<matplotlib.axes._subplots.AxesSubplot at 0x2e68fc3d2e8>
output_27_1.png
由此可见,数据存在两个问题:1、存在离群点(居住面积、地下室特别大然而房价低),这样的点显然应该去掉;2、数据整体在左下角密集而右上角稀疏,呈圆锥状,这样的数据具有同方差性(homoscedasticity),需要进行处理。

df_train.sort_values(by = 'GrLivArea', ascending = False)[:2]  # 查找离群点
Id MSSubClass MSZoning LotArea Street LotShape LandContour Utilities LotConfig LandSlope EnclosedPorch 3SsnPorch ScreenPorch PoolArea MiscVal MoSold YrSold SaleType SaleCondition SalePrice
1298 1299 60 RL 63887 Pave IR3 Bnk AllPub Corner Gtl 0 0 0 480 0 1 2008 New Partial 160000
523 524 60 RL 40094 Pave IR1 Bnk AllPub Inside Gtl 0 0 0 0 0 10 2007 New Partial 184750

2 rows × 63 columns

# 删除离群点
df_train = df_train.drop(df_train[df_train['Id'] == 1299].index)
df_train = df_train.drop(df_train[df_train['Id'] == 524].index)
fig = plt.figure(figsize=(12,5))
plt.subplot(121)
sns.distplot(df_train[output],fit=norm)
plt.subplot(122)
res = stats.probplot(df_train[output], plot=plt)
plt.show()

output_31_0.png
观察直方图和概率图可以发现,数据具有明显的正偏性,因此可采用对数来缓解这种趋势

def log_transform(feature):
    # np.log1p(x) = log(1+x),这样就可以对0值求对数(针对 `TotalBsmtSF` 这样含有0的特征)
    df_train[feature] = np.log1p(df_train[feature].values)  

log_transform(output)
log_transform(var)
log_transform(var1)
fig = plt.figure(figsize=(12,15))
plt.subplot(321)
sns.distplot(df_train[output],fit=norm)
plt.subplot(322)
res = stats.probplot(df_train[output], plot=plt)
plt.subplot(323)
sns.distplot(df_train[var],fit=norm)
plt.subplot(324)
res = stats.probplot(df_train[var], plot=plt)
plt.subplot(325)
sns.distplot(df_train[var1],fit=norm)
plt.subplot(326)
res = stats.probplot(df_train[var1], plot=plt)
plt.show()

output_33_0.png
从上图可见,部分特征还是存在问题,因此可以考虑构建新的特征来解决上述问题,并为这些连续变量提供新的连续或离散特征

df_tr['HasBasement'] = df_tr['TotalBsmtSF'].apply(lambda x: 1 if x > 0 else 0)
df_tr['HasGarage'] = df_tr['GarageArea'].apply(lambda x: 1 if x > 0 else 0)
df_tr['Has2ndFloor'] = df_tr['2ndFlrSF'].apply(lambda x: 1 if x > 0 else 0)
df_tr['HasMasVnr'] = df_tr['MasVnrArea'].apply(lambda x: 1 if x > 0 else 0)
df_tr['HasWoodDeck'] = df_tr['WoodDeckSF'].apply(lambda x: 1 if x > 0 else 0)
df_tr['HasPorch'] = df_tr['OpenPorchSF'].apply(lambda x: 1 if x > 0 else 0)
df_tr['HasPool'] = df_tr['PoolArea'].apply(lambda x: 1 if x > 0 else 0)
df_tr['IsNew'] = df_tr['YearBuilt'].apply(lambda x: 1 if x > 2000 else 0)
boolean = ['HasBasement', 'HasGarage', 'Has2ndFloor', 'HasMasVnr', 
           'HasWoodDeck', 'HasPorch', 'HasPool', 'IsNew']

def quadratic(feature):
    df_tr[feature] = df_tr[feature[:-1]]**2

qdr = ['OverallQual2', 'YearBuilt2', 'YearRemodAdd2', 'TotalBsmtSF2',
        '2ndFlrSF2', 'Neighborhood_E2', 'RoofMatl_E2', 'GrLivArea2']
for feature in qdr:
    quadratic(feature)

除了上述连续变量的处理,对于此时的数据集中的类型变量(缺失值处理采用方法1的情况)应该进行“哑变量”处理,此步采用pd.get_dummies()与使用sklearn下的OneHotEncoder()作用是相同的。

df_train = pd.get_dummies(df_train)
df_train.shape   # 未考虑上述增加特征时的运行结果

(1457, 221)

猜你喜欢

转载自blog.csdn.net/qilixuening/article/details/75153131