每日一課 Kaggle 練習講解:House Prices(上)

每天一道Kaggle題,學習機器學習!

今天給大家來講講《House Prices: Advanced Regression Techniques》(房價預測模型)的思路:

  • (1) 數據可視化和數據分佈變換
  • (2) 缺省值處理
  • (3) 數據特徵變換
  • (4) 數據建模及交叉檢驗
  • (5) 模型組合

import numpy as np

import pandas as pd

import matplotlib.pyplot as plt

import seaborn as sns

from scipy import stats

from scipy.stats import norm, skew

from scipy.special import boxcox1p

from scipy.stats import boxcox_normmax

from sklearn.model_selection import KFold, cross_val_score

from sklearn.preprocessing import LabelEncoder

%matplotlib inline

加載數據

去除ID

In [2]:

train_path = "http://kaggle.shikanon.com/house-prices-advanced-regression-techniques/train.csv"
test_path = "http://kaggle.shikanon.com/house-prices-advanced-regression-techniques/test.csv"
train_df = pd.read_csv(train_path)
test_df = pd.read_csv(test_path)

我們在分析之前要先了解個字段的意思:

  • 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: 交易條件,類型:類別型

數據處理和特徵分析

In [4]:

#Saving Ids
train_ID = train_df['Id']
test_ID = test_df['Id']

#Dropping Ids
train_df.drop("Id", axis = 1, inplace = True)
test_df.drop("Id", axis = 1, inplace = True)

數據觀察和可視化

更加常識,一般和房價最相關的是居住面積,也就是GrLivArea,我們查看下GrLivArea和SalePrice的關係

In [5]:

fig, ax = plt.subplots()
ax.scatter(x = train_df['GrLivArea'], y = train_df['SalePrice'], c = "skyblue")
plt.ylabel('SalePrice', fontsize=8)
plt.xlabel('GrLivArea', fontsize=8)
plt.show()
每日一課 Kaggle 練習講解:House Prices(上)

我們發現有個別值特別的偏離,GrLivArea有兩個點在4000以上,但其價格不到200000,首先這種點特別少(不到總數的3%),我們把他作為異常值去掉(其實是否去掉我們可以多做幾次實驗來驗證)

In [6]:

train_df.drop(train_df[(train_df['GrLivArea']>4000)&(train_df['GrLivArea']<30000)].index,inplace=True)

In [7]:

fig, ax = plt.subplots()
ax.scatter(x = train_df['GrLivArea'], y = train_df['SalePrice'], c = "skyblue")
plt.ylabel('SalePrice', fontsize=8)
plt.xlabel('GrLivArea', fontsize=8)
plt.show()
每日一課 Kaggle 練習講解:House Prices(上)

  • 最後一行是 SalePrice, 我們可以看到她跟各變量的關係,還有各變量相互之間的關係

觀察數據分佈

在機器學習中,對數據的認識是很重要的,他會影響我們的特徵構建和建模,特別對於偏態分佈,我們要做一些變換

In [8]:

# 統計表述
train_df['SalePrice'].describe()

count 1456.000000

mean 180151.233516

std 76696.592530

min 34900.000000

25% 129900.000000

50% 163000.000000

75% 214000.000000

max 625000.000000

Name: SalePrice, dtype: float64

# 繪製分佈圖
sns.distplot(train_df['SalePrice'],
kde_kws={"color": "coral", "lw": 1, "label": "KDE"},
hist_kws={"histtype": "stepfilled", "linewidth": 3, "alpha": 1, "color": "skyblue"});
每日一課 Kaggle 練習講解:House Prices(上)

Q-Q圖,全稱 Quantile Quantile Plot,中文名叫分位數圖,Q-Q圖是一個概率圖,用於比較觀測與預測值之間的概率分佈差異,這裡的比較對象一般採用正態分佈,Q-Q圖可以用於檢驗數據分佈的相似性,而P-P圖是根據變量的累積概率對應於所指定的理論分佈累積概率繪製的散點圖,兩者基本一樣

In [10]:

# 繪製P-P圖
fig = plt.figure()
res = stats.probplot(train_df['SalePrice'], dist="norm", plot=plt)
plt.show()

每日一課 Kaggle 練習講解:House Prices(上)

紅色線是正態分佈,藍色線是我們的數據,可以看出,我們的數據頭尾都嚴重偏離了正太分佈,我們嘗試對數據做變換,常用的變換有指數變換、對數變換、冪函數等。

In [11]:

# 對數變換

train_df['SalePrice_Log'] = np.log(train_df['SalePrice'])

sns.distplot(train_df['SalePrice_Log'],
kde_kws={"color": "coral", "lw": 1, "label": "KDE"},
hist_kws={"histtype": "stepfilled", "linewidth": 3, "alpha": 1, "color": "skyblue"});


# 偏度與峰值(skewness and kurtosis)
print("Skewness: %f" % train_df['SalePrice_Log'].skew())
print("Kurtosis: %f" % train_df['SalePrice_Log'].kurt())

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

Skewness: 0.065449

Kurtosis: 0.666438

每日一課 Kaggle 練習講解:House Prices(上)

每日一課 Kaggle 練習講解:House Prices(上)

In [12]:

# 指數變換
train_df['SalePrice_Exp'] = np.exp(train_df['SalePrice']/train_df['SalePrice'].mean())

sns.distplot(train_df['SalePrice_Exp'],
kde_kws={"color": "coral", "lw": 1, "label": "KDE"},
hist_kws={"histtype": "stepfilled", "linewidth": 3, "alpha": 1, "color": "skyblue"});

# 偏度與峰值(skewness and kurtosis)
print("Skewness: %f" % train_df['SalePrice_Exp'].skew())
print("Kurtosis: %f" % train_df['SalePrice_Exp'].kurt())

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

Skewness: 6.060076

Kurtosis: 56.822460

每日一課 Kaggle 練習講解:House Prices(上)

每日一課 Kaggle 練習講解:House Prices(上)

In [13]:

# 冪函數變換
train_df['SalePrice_Square'] = train_df['SalePrice']**0.5

sns.distplot(train_df['SalePrice_Square'],
kde_kws={"color": "coral", "lw": 1, "label": "KDE"},
hist_kws={"histtype": "stepfilled", "linewidth": 3, "alpha": 1, "color": "skyblue"});

# 偏度與峰值(skewness and kurtosis)
print("Skewness: %f" % train_df['SalePrice_Square'].skew())
print("Kurtosis: %f" % train_df['SalePrice_Square'].kurt())

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

Skewness: 0.810797

Kurtosis: 1.245798

每日一課 Kaggle 練習講解:House Prices(上)

每日一課 Kaggle 練習講解:House Prices(上)

三個函數擬合對比,對數轉換最吻合,但是我們知道對數意味著小於1的時候為負數,這明顯和認知不符合,應該採用log(1+x),也就是log1p,保證了x數據的有效性,當x很小時,如: 10^{-16} ,由於太小超過數值有效性,用log(x+1)計算得到結果為0

In [14]:

# 對數變換

train_df['SalePrice_Log1p'] = np.log1p(train_df['SalePrice'])

sns.distplot(train_df['SalePrice_Log1p'],
kde_kws={"color": "coral", "lw": 1, "label": "KDE"},
hist_kws={"histtype": "stepfilled", "linewidth": 3, "alpha": 1, "color": "skyblue"});

# 偏度與峰值(skewness and kurtosis)
print("Skewness: %f" % train_df['SalePrice_Log1p'].skew())
print("Kurtosis: %f" % train_df['SalePrice_Log1p'].kurt())

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

Skewness: 0.065460

Kurtosis: 0.666423

每日一課 Kaggle 練習講解:House Prices(上)

每日一課 Kaggle 練習講解:House Prices(上)

In [15]:

del train_df['SalePrice_Square']
del train_df["SalePrice_Exp"]
del train_df['SalePrice_Log']

In [16]:

del train_df["SalePrice"]

將測試數據和訓練數據聯合一起進行特徵分析

In [17]:

size_train_df = train_df.shape[0]
size_test_df = test_df.shape[0]
target_variable = train_df['SalePrice_Log1p'].values
data = pd.concat((train_df, test_df),sort=False).reset_index(drop=True)
data.drop(['SalePrice_Log1p'], axis=1, inplace=True)

缺失值處理

缺失值是實際數據分析很重要的一塊,在實際生產中一直都會有大量的缺失值存在,如何處理好缺失值是很關鍵也很重要的一步。

常見的缺失值處理有:

  • (1)把缺失值單獨作為一類,比如對類別型用none。
  • (2)採用平均數、中值、眾數等特定統計值來填充缺失值。
  • (3)採用函數預測等方法填充缺失值。

In [18]:

data.count().sort_values().head(20) # 通過 count 可以找出有缺失值的數據
Out[18]:

PoolQC 8

MiscFeature 105

Alley 198

Fence 570

FireplaceQu 1495

LotFrontage 2429

GarageCond 2756

GarageYrBlt 2756

GarageFinish 2756

GarageQual 2756

GarageType 2758

BsmtExposure 2833

BsmtCond 2833

BsmtQual 2834

BsmtFinType2 2835

BsmtFinType1 2836

MasVnrType 2891

MasVnrArea 2892

MSZoning 2911

BsmtFullBath 2913

dtype: int64

In [19]:

data_na = (data.isnull().sum() / len(data)) * 100
data_na.drop(data_na[data_na==0].index,inplace=True)
data_na = data_na.sort_values(ascending=False)
f, ax = plt.subplots(figsize=(10, 10))
plt.xticks(rotation='90')
sns.barplot(x=data_na.index, y=data_na)
plt.xlabel('Features', fontsize=10)
plt.ylabel('Percent of missing values', fontsize=10)
plt.title('Percent missing data by feature', fontsize=10)

Out[19]:

Text(0.5, 1.0, 'Percent missing data by feature')

每日一課 Kaggle 練習講解:House Prices(上)

In [20]:

var = 'Utilities'
train_var_count = train_df[var].value_counts()
fig = sns.barplot(x=train_var_count.index, y=train_var_count)
plt.xticks(rotation=90);
每日一課 Kaggle 練習講解:House Prices(上)

In [21]:

var = 'MSZoning'
train_var_count = train_df[var].value_counts()
fig = sns.barplot(x=train_var_count.index, y=train_var_count)
plt.xticks(rotation=90);
每日一課 Kaggle 練習講解:House Prices(上)

In [22]:

# 填充nil
features_fill_na_none = ['PoolQC','MiscFeature','Alley','Fence','FireplaceQu',
'GarageQual','GarageCond','GarageFinish','GarageType',
'BsmtExposure','BsmtCond','BsmtQual','BsmtFinType1','BsmtFinType2',
'MasVnrType']

# 填充0
features_fill_na_0 = ['GarageYrBlt', 'GarageArea', 'GarageCars', 'MasVnrArea',

'BsmtFullBath','BsmtHalfBath', 'BsmtFinSF1', 'BsmtFinSF2',
'BsmtUnfSF', 'TotalBsmtSF']

# 填眾數
features_fill_na_mode = ["Functional", "MSZoning", "SaleType", "Electrical",
"KitchenQual", "Exterior2nd", "Exterior1st"]

for feature_none in features_fill_na_none:
data[feature_none].fillna('None',inplace=True)

for feature_0 in features_fill_na_0:
data[feature_0].fillna(0,inplace=True)

for feature_mode in features_fill_na_mode:
mode_value = data[feature_mode].value_counts().sort_values(ascending=False).index[0]
data[features_fill_na_mode] = data[features_fill_na_mode].fillna(mode_value)

# 用中值代替
data["LotFrontage"] = data.groupby("Neighborhood")["LotFrontage"].transform(
lambda x: x.fillna(x.median()))

# 像 Utilities 這種總共才兩個值,同時有一個值是作為主要的,這種字段是無意義的,應該刪除
data.drop(['Utilities'], axis=1,inplace=True)

In [23]:

data_na = (data.isnull().sum() / len(data)) * 100
data_na.drop(data_na[data_na==0].index,inplace=True)
data_na = data_na.sort_values(ascending=False)
data_na # data_na 為空

Out[23]:

Series([], dtype: float64)

類型轉換,將某些實際是類別類型但用數字表示的強制轉換成文本,比如有些調查男表示1,女表示0,在這種情況下,如果我們直接通過dataframe類型判斷會導致錯誤,我們要根據實際情況做轉換

In [24]:

#MSSubClass=The building class
data['MSSubClass'] = data['MSSubClass'].apply(str)


#Changing OverallCond into a categorical variable
data['OverallCond'] = data['OverallCond'].astype(str)


#Year and month sold are transformed into categorical features.
data['YrSold'] = data['YrSold'].astype(str)
data['MoSold'] = data['MoSold'].astype(str)

繪製關係矩陣

關係矩陣可以很直觀的告訴我們那些變量之間相關,哪些變量並不相關

In [25]:

# 關係矩陣
corrmat = train_df.corr()
corrmat

In [26]:

mask = np.zeros_like(corrmat) # 返回相同大小的0矩陣
mask[np.triu_indices_from(mask)] = True # triu_indices_from: 函數的上三角矩陣
mask

Out[26]:

array([[1., 1., 1., ..., 1., 1., 1.],

[0., 1., 1., ..., 1., 1., 1.],

[0., 0., 1., ..., 1., 1., 1.],

...,

[0., 0., 0., ..., 1., 1., 1.],

[0., 0., 0., ..., 0., 1., 1.],

[0., 0., 0., ..., 0., 0., 1.]])

In [27]:

# 繪製熱力圖
plt.subplots(figsize=(12,9))
sns.heatmap(corrmat, mask=mask, linewidths=.5, vmax=0.9, square=True, cmap="YlGnBu")

Out[27]:

<matplotlib.axes.>

每日一課 Kaggle 練習講解:House Prices(上)


分享到:


相關文章: