淘新聞

詳解 Kaggle 房價預測競賽優勝方案:用 Python 進行全面資料探索

[導讀]

Kaggle 的房價預測競賽從 2016 年 8 月開始,到 2017 年 2 月結束。這段時間內,超過 2000 多人參與比賽,選手採用高級回歸技術,基於我們給出的 79 個特徵,對房屋的售價進行了準確的預測。今天我們介紹的是目前得票數最高的優勝方案:《用 Python 進行全面資料探索》,該方案在資料探索,特徵工程上都有十分出色的表現。

作者 Pedro Marcelino 在競賽中使用的主要方法是關注資料科學處理方法,以及尋找能夠指導工作的有力文獻資料。作者主要參考《多中繼資料分析》(Multivariate Data Analysis, Hair et al., 2014)中的第三章 “檢查你的資料”。作者將自己研究的方法歸為以下三步:

定義要解決的問題;

查閱相關文獻;

對他們進行修改以適合自己的要求。

“不過是站在巨人的肩膀上。”—— Pedro Marcelino 

下面我們就一起來看看作者是如何對資料進行分析的。

瞭解你的資料

方法框架:

理解問題:查看每個變數並且根據他們的意義和對問題的重要性進行哲學分析。

單因素研究:只關注因變數( SalePrice),並且進行更深入的瞭解。

多因素研究:分析因變數和引數之間的關係。

基礎清洗:清洗資料集並且對缺失資料,異常值和分類資料進行一些處理。

檢驗假設:檢查資料是否和多元分析方法的假設達到一致。

開始之前:

import pandas as pd

import matplotlib.pyplot as plt

import seaborn as sns

import numpy as npfrom scipy.stats

import normfrom sklearn.preprocessing

import StandardScalerfrom scipy

import stats

import warnings

warnings.filterwarnings('ignore')

%matplotlib inline

#bring in the six packs

df_train = pd.read_csv('../input/train.csv')

#check the decoration

df_train.columns

Index(['Id', 'MSSubClass', 'MSZoning','LotFrontage', 'LotArea', 'Street',

'Alley', 'LotShape', 'LandContour', 'Utilities','LotConfig',

'LandSlope', 'Neighborhood', 'Condition1','Condition2', 'BldgType',

'HouseStyle', 'OverallQual', 'OverallCond','YearBuilt', 'YearRemodAdd',

'RoofStyle', 'RoofMatl', 'Exterior1st','Exterior2nd', 'MasVnrType',

'MasVnrArea', 'ExterQual', 'ExterCond','Foundation', 'BsmtQual',

'BsmtCond', 'BsmtExposure', 'BsmtFinType1','BsmtFinSF1',

'BsmtFinType2', 'BsmtFinSF2', 'BsmtUnfSF','TotalBsmtSF', 'Heating',

'HeatingQC', 'CentralAir', 'Electrical', '1stFlrSF','2ndFlrSF',

'LowQualFinSF', 'GrLivArea', 'BsmtFullBath','BsmtHalfBath', 'FullBath',

'HalfBath', 'BedroomAbvGr', 'KitchenAbvGr','KitchenQual',

'TotRmsAbvGrd', 'Functional', 'Fireplaces','FireplaceQu', 'GarageType',

'GarageYrBlt', 'GarageFinish', 'GarageCars','GarageArea', 'GarageQual',

'GarageCond', 'PavedDrive', 'WoodDeckSF','OpenPorchSF',

'EnclosedPorch', '3SsnPorch', 'ScreenPorch','PoolArea', 'PoolQC',

'Fence', 'MiscFeature', 'MiscVal', 'MoSold','YrSold', 'SaleType',

'SaleCondition', 'SalePrice'],

dtype='object')

準備工作——我們可以期望什麼?

為了瞭解我們的資料,我們可以分析每個變數並且嘗試理解他們的意義和與該問題的相關程度。

首先建立一個 Excel 試算表,有如下目錄:

變數

 – 變數名。

類型

 – 該變數的類型。這一欄只有兩個可能值,“資料” 或 “類別”。 “資料” 是指該變數的值是數位,“類別” 指該變數的值是類別標籤。

劃分

 – 指示變數劃分. 我們定義了三種劃分:建築,空間,位置。

期望

 – 我們希望該變數對房價的影響程度。我們使用類別標籤 “高”,“中” 和 “低” 作為可能值。

結論

 – 我們得出的該變數的重要性的結論。在大概流覽資料之後,我們認為這一欄和 “期望” 的值基本一致。

評論

 – 我們看到的所有一般性評論。

我們首先閱讀了每一個變數的描述檔,同時思考這三個問題:

我們買房子的時候會考慮這個因素嗎?

如果考慮的話,這個因素的重要程度如何?

這個因素帶來的資訊在其他因素中出現過嗎?

我們根據以上內容填好了試算表,並且仔細觀察了 “高期望” 的變數。然後繪製了這些變數和房價之間的散點圖,填在了 “結論” 那一欄,也正巧就是對我們的期望值的校正。

我們總結出了四個對該問題起到至關重要的作用的變數:

OverallQual

YearBuilt.

TotalBsmtSF.

GrLivArea.

最重要的事情——分析 “房價”

描述性資料總結:

df_train['SalePrice'].describe()

count 1460.000000

mean 180921.195890

std 79442.502883

min 34900.000000

25% 129975.000000

50% 163000.000000

75% 214000.000000

max 755000.000000

Name: SalePrice, dtype: float64

繪製長條圖

sns.distplot(df_train['SalePrice']);

從長條圖中可以看出:

偏離正態分佈

數據正偏

有峰值

數據偏度和峰度度量:

print("Skewness: %f" % df_train['SalePrice'].skew())

print("Kurtosis: %f" % df_train['SalePrice'].kurt())

Skewness: 1.882876

Kurtosis: 6.536282

“房價” 的相關變數分析

與數字型變數的關係:

1. Grlivarea 與 SalePrice 散點圖

var = 'GrLivArea'

data = pd.concat([df_train['SalePrice'], df_train[var]], axis=1)

data.plot.scatter(x=var, y='SalePrice', ylim=(0,800000));

可以看出 SalePrice 和 GrLivArea 關係很密切,並且基本呈線性關係。

2. TotalBsmtSF 與 SalePrice 散點圖

var = 'TotalBsmtSF'

data = pd.concat([df_train['SalePrice'], df_train[var]], axis=1)

data.plot.scatter(x=var, y='SalePrice', ylim=(0,800000));

TotalBsmtSF 和 SalePrice 關係也很密切,從圖中可以看出基本呈指數分佈,但從最左側的點可以看出特定情況下 TotalBsmtSF 對 SalePrice 沒有產生影響。

與類別型變數的關係

1.‘OverallQual’與‘SalePrice’箱型圖

var = 'OverallQual'

data = pd.concat([df_train['SalePrice'], df_train[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);

可以看出 SalePrice 與 OverallQual 分佈趨勢相同。

2. YearBuilt 與 SalePrice 箱型圖

var = 'YearBuilt'

data = pd.concat([df_train['SalePrice'], df_train[var]], axis=1)

f, ax = plt.subplots(figsize=(16, 8))

fig = sns.boxplot(x=var, y="SalePrice", data=data)

fig.axis(ymin=0, ymax=800000);plt.xticks(rotation=90);

兩個變數之間的關係沒有很強的趨勢性,但是可以看出建築時間較短的房屋價格更高。

總結:

GrLivArea 和 TotalBsmtSF 與 SalePrice 似乎線性相關,並且都是正相關。 對於 TotalBsmtSF 線性關係的斜率十分的高。

OverallQual 和 YearBuilt 與 SalePrice 也有關係。OverallQual 的相關性更強, 箱型圖顯示了隨著整體品質的增長,房價的增長趨勢。

我們只分析了四個變數,但是還有許多其他變數我們也應該分析,這裡的技巧在於選擇正確的特徵(特徵選擇)而不是定義他們之間的複雜關係(特徵工程)。

客觀分析

1. 相關係數矩陣

corrmat = df_train.corr()

f, ax = plt.subplots(figsize=(12, 9))

sns.heatmap(corrmat, vmax=.8, square=True);

首先兩個紅色的方塊吸引到了我,第一個是 TotalBsmtSF 和 1stFlrSF 變數的相關係數,第二個是 GarageX 變數群。這兩個示例都顯示了這些變數之間很強的相關性。實際上,相關性的程度達到了一種多重共線性的情況。我們可以總結出這些變數幾乎包含相同的資訊,所以確實出現了多重共線性。

另一個引起注意的地方是 SalePrice 的相關性。我們可以看到我們之前分析的 GrLivArea,TotalBsmtSF和 OverallQual 的相關性很強,除此之外也有很多其他的變數應該進行考慮,這也是我們下一步的內容。

2. SalePrice 相關係數矩陣

k = 10 #number ofvariables for heatmap

cols = corrmat.nlargest(k, 'SalePrice')['SalePrice'].index

cm = np.corrcoef(df_train[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()

從圖中可以看出:

OverallQual,GrLivArea 以及 TotalBsmtSF 與 SalePrice 有很強的相關性。

GarageCars 和 GarageArea 也是相關性比較強的變數. 車庫中存儲的車的數量是由車庫的面積決定的,它們就像雙胞胎,所以不需要專門區分 GarageCars 和 GarageAre,所以我們只需要其中的一個變數。這裡我們選擇了 GarageCars,因為它與 SalePrice 的相關性更高一些。

TotalBsmtSF 和 1stFloor 與上述情況相同,我們選擇 TotalBsmtS 。

FullBath 幾乎不需要考慮。

TotRmsAbvGrd 和 GrLivArea 也是變數中的雙胞胎。

YearBuilt 和 SalePrice 相關性似乎不強。

3. SalePrice 和相關變數之間的散點圖

sns.set()

cols = ['SalePrice', 'OverallQual', 'GrLivArea','GarageCars', 'TotalBsmtSF', 'FullBath', 'YearBuilt']

sns.pairplot(df_train[cols], size = 2.5)

plt.show();

儘管我們已經知道了一些主要特徵,這一豐富的散點圖給了我們一個關於變數關係的合理想法。

其中,TotalBsmtSF 和 GrLiveArea 之間的散點圖是很有意思的。我們可以看出這幅圖中,一些點組成了線,就像邊界一樣。大部分點都分佈在那條線下面,這也是可以解釋的。地下室面積和地上居住面積可以相等,但是一般情況下不會希望有一個比地上居住面積還大的地下室。

SalePrice 和 YearBuilt 之間的散點圖也值得我們思考。在 “點雲” 的底部,我們可以觀察到一個幾乎呈指數函數的分佈。我們也可以看到 “點雲” 的上端也基本呈同樣的分佈趨勢。並且可以注意到,近幾年的點有超過這個上端的趨勢。

缺失資料

關於缺失資料需要思考的重要問題:

這一缺失資料的普遍性如何?

缺失資料是隨機的還是有律可循?

這些問題的答案是很重要的,因為缺失資料意味著樣本大小的縮減,這會阻止我們的分析進程。除此之外,以實質性的角度來說,我們需要保證對缺失資料的處理不會出現偏離或隱藏任何難以忽視的真相。

total= df_train.isnull().sum().sort_values(ascending=False)

percent = (df_train.isnull().sum()/df_train.isnull().count()).sort_values(ascending=False)

missing_data = pd.concat([total, percent], axis=1, keys=['Total','Percent'])

missing_data.head(20)

當超過 15% 的資料都缺失的時候,我們應該刪掉相關變數且假設該變數並不存在。

根據這一條,一系列變數都應該刪掉,例如 PoolQC,MiscFeature,Alley 等等,這些變數都不是很重要,因為他們基本都不是我們買房子時會考慮的因素。

GarageX 變數群的缺失資料量都相同,由於關於車庫的最重要的資訊都可以由 GarageCars 表達,並且這些資料只占缺失資料的 5%,我們也會刪除上述的 GarageX 變數群。同樣的邏輯也適用於 BsmtX 變數群。

對於 MasVnrArea 和 MasVnrType,我們可以認為這些因素並不重要。除此之外,他們和 YearBuilt 以及 OverallQual 都有很強的關聯性,而這兩個變數我們已經考慮過了。所以刪除 MasVnrArea 和 MasVnrType 並不會丟失資訊。

最後,由於 Electrical 中只有一個損失的觀察值,所以我們刪除這個觀察值,但是保留這一變數。

df_train= df_train.drop((missing_data[missing_data['Total'] > 1]).index,1)

df_train= df_train.drop(df_train.loc[df_train['Electrical'].isnull()].index)

df_train.isnull().sum().max() #justchecking that there's no missing data missing...

異常值

單因素分析

這裡的關鍵在於如何建立閾值,定義一個觀察值為異常值。我們對資料進行正態化,意味著把資料值轉換成均值為 0,方差為 1 的資料。

saleprice_scaled= StandardScaler().fit_transform(df_train['SalePrice'][:,np.newaxis]);

low_range = saleprice_scaled[saleprice_scaled[:,0].argsort()][:10]

high_range= saleprice_scaled[saleprice_scaled[:,0].argsort()][-10:]

print('outer range (low) of the distribution:')

print(low_range)

print(' outer range (high) of thedistribution:')

print(high_range)

進行正態化後,可以看出:

低範圍的值都比較相似並且在 0 附近分佈。

高範圍的 值離 0 很遠,並且七點幾的值遠在正常範圍之外。

雙變數分析

1. GrLivArea 和 SalePrice 雙變數分析

var = 'GrLivArea'

data = pd.concat([df_train['SalePrice'], df_train[var]], axis=1)

data.plot.scatter(x=var, y='SalePrice', ylim=(0,800000));

從圖中可以看出:

有兩個離群的 GrLivArea 值很高的資料,我們可以推測出現這種情況的原因。或許他們代表了農業地區,也就解釋了低價。 這兩個點很明顯不能代表典型樣例,所以我們將它們定義為異常值並刪除。

圖中頂部的兩個點是七點幾的觀測值,他們雖然看起來像特殊情況,但是他們依然符合整體趨勢,所以我們將其保留下來。

刪除點

df_train.sort_values(by = 'GrLivArea',ascending = False)[:2]

df_train = df_train.drop(df_train[df_train['Id'] == 1299].index)

df_train = df_train.drop(df_train[df_train['Id'] == 524].index)

2. TotalBsmtSF 和 SalePrice 雙變數分析

var = 'TotalBsmtSF'

data = pd.concat([df_train['SalePrice'],df_train[var]], axis=1)

data.plot.scatter(x=var, y='SalePrice',ylim=(0,800000));

核心部分

“房價” 到底是誰?

這個問題的答案,需要我們驗證根據資料基礎進行多元分析的假設。

我們已經進行了資料清洗,並且發現了 SalePrice 的很多資訊,現在我們要更進一步理解 SalePrice 如何遵循統計假設,可以讓我們應用多元技術。

應該測量 4 個假設量:

正態性

同方差性

線性

相關錯誤缺失

正態性:

應主要關注以下兩點:

長條圖 – 峰度和偏度。

正態概率圖 – 資料分佈應緊密跟隨代表正態分佈的對角線。

1. SalePrice

繪製長條圖和正態概率圖:

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

fig = plt.figure()

res = stats.probplot(df_train['SalePrice'], plot=plt)

可以看出,房價分佈不是正態的,顯示了峰值,正偏度,但是並不跟隨對角線。

可以用對數變換來解決這個問題

進行對數變換:

df_train['SalePrice']= np.log(df_train['SalePrice'])

繪製變換後的長條圖和正態概率圖:

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

fig = plt.figure()

res = stats.probplot(df_train['SalePrice'], plot=plt)

2. GrLivArea

繪製長條圖和正態概率曲線圖:

sns.distplot(df_train['GrLivArea'], fit=norm);

fig = plt.figure()

res = stats.probplot(df_train['GrLivArea'], plot=plt)

進行對數變換:

df_train['GrLivArea']= np.log(df_train['GrLivArea'])

繪製變換後的長條圖和正態概率圖:

sns.distplot(df_train['GrLivArea'], fit=norm);

fig = plt.figure()

res = stats.probplot(df_train['GrLivArea'], plot=plt)

3. TotalBsmtSF

繪製長條圖和正態概率曲線圖:

sns.distplot(df_train['TotalBsmtSF'],fit=norm);

fig = plt.figure()

res = stats.probplot(df_train['TotalBsmtSF'],plot=plt)

從圖中可以看出:

顯示出了偏度

大量為 0 的觀察值(沒有地下室的房屋)

含 0 的資料無法進行對數變換

我們建立了一個變數,可以得到有沒有地下室的影響值(二值變數),我們選擇忽略零值,只對非零值進行對數變換。這樣我們既可以變換資料,也不會損失有沒有地下室的影響。

df_train['HasBsmt']= pd.Series(len(df_train['TotalBsmtSF']), index=df_train.index)

df_train['HasBsmt'] = 0 

df_train.loc[df_train['TotalBsmtSF']>0,'HasBsmt'] = 1

進行對數變換:

df_train['TotalBsmtSF']= np.log(df_train['TotalBsmtSF'])

繪製變換後的長條圖和正態概率圖:

sns.distplot(df_train['TotalBsmtSF'], fit=norm);

fig = plt.figure()

res = stats.probplot(df_train['TotalBsmtSF'], plot=plt)

同方差性:

最好的測量兩個變數的同方差性的方法就是圖像。

1. SalePrice 和 GrLivArea 同方差性

繪製散點圖:

plt.scatter(df_train['GrLivArea'],df_train['SalePrice']);

2. SalePrice with TotalBsmtSF 同方差性

繪製散點圖:

plt.scatter(df_train[df_train['TotalBsmtSF']>0]['TotalBsmtSF'], df_train[df_train['TotalBsmtSF']>0]['SalePrice']);

可以看出 SalePrice 在整個 TotalBsmtSF 變數範圍內顯示出了同等級別的變化。

虛擬變數

將類別變數轉換為虛擬變數:

df_train = pd.get_dummies(df_train)

結論

整個方案中,我們使用了很多《多中繼資料分析》中提出的方法。我們對變數進行了哲學分析,不僅對 SalePrice 進行了單獨分析,還結合了相關程度最高的變數進行分析。我們處理了缺失資料和異常值,我們驗證了一些基礎統計假設,並且將類別變數轉換為虛擬變數。

但問題還沒有結束,我們還需要預測房價的變化趨勢,房價預測是否適合線性回歸正則化的方法?是否適合組合方法?或者一些其他的方法?

希望你可以進行自己的探索發現。

雷鋒網按:本文原載於

36大數據