Edited at

Kaggle「HousePrices」


はじめに

東大HAITSというデータサイエンスを勉強する団体の活動で、kaggleというデータサイエンスコンペの「HousePrices」というコンペに挑戦しました。まだ、データサイエンスを勉強し始めて1か月ちょっとですので、基本的なことしかしていません。


データの観察


ライブラリのインポート

import pandas as pd

import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
%matplotlib inline
from scipy.stats import skew
from sklearn.linear_model import Lasso
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error


データの読み込み

train = pd.read_csv("train.csv")


データ数の確認

print(train.shape)

train (1460, 81)

変数の数が多く、変数選択が難しいと思う。色々試したが、いらない変数を落としていくほうがいい結果を得られたので、今回はそういった方針で進めていく。


欠損値処理


欠損値の確認

train.isnull().sum()[train.isnull().sum()>0].sort_values(ascending=False)


  • PoolQC 1453

  • MiscFeature 1406

  • Alley 1369

  • Fence 1179

  • FireplaceQu 690

  • LotFrontage 259

  • GarageYrBlt 81

  • GarageType 81

  • GarageFinish 81

  • GarageQual 81

  • GarageCond 81

  • BsmtFinType2 38

  • BsmtExposure 38

  • BsmtFinType1 37

  • BsmtCond 37

  • BsmtQual 37

  • MasVnrArea 8

  • MasVnrType 8

  • Electrical 1

欠損値の多い列は削除することにした。

train = train.drop(columns=["PoolQC","MiscFeature","Alley","Fence" ,"FireplaceQu" ,"LotFrontage"])


残りの欠損値処理

今回は変数の数が多いこともあり、便宜上、欠損値処理としてOne Hot Encoding、補完処理は平均値で補う。

他にもLabelEncoderなどいろいろなやり方がある。変数によって使い分けることで精度が高くなると思う。

train = pd.get_dummies(train)

train = train.fillna(train.mean())


新たな変数の設計

住宅を選ぶときはまず、土地の面積と築年数を調べる人が多いだろう。これをあらわす変数が欲しいという流れは至って自然だろう。また、このデータは2011年のものであると調べると分かるので、2011年における築年数とする。(http://jse.amstat.org/v19n3/decock.pdf

train["TotalSF"] = train["TotalBsmtSF"] + train["1stFlrSF"] + train["2ndFlrSF"] + train['GarageArea']

train['YearBuilt'] = 2011 - train['YearBuilt']


新たな変数を作るときに使った変数は取り除く。

train.drop(['1stFlrSF','GarageArea',"TotalBsmtSF","2ndFlrSF"], axis=1, inplace=True)


可視化


相関係数の大きい順に並べる。

np.abs(train.corr()["SalePrice"]).sort_values(ascending=False).head(10)

['SalePrice', 'TotalSF', 'OverallQual', 'GrLivArea', 'GarageCars', 'ExterQual_TA', 'FullBath', 'BsmtQual_Ex', 'YearBuilt','KitchenQual_TA']


Heatmap


相関係数の大きいもの10個を選んでお互いの相関係数を表示する。

plt.figure(figsize=(12, 9))

sns.heatmap(train.loc[:,['SalePrice', 'OverallQual', 'TotalSF', 'GrLivArea', 'GarageCars',
'ExterQual_TA', 'FullBath', 'BsmtQual_Ex', 'YearBuilt',
'KitchenQual_TA']].corr(), annot=True, square=True, fmt='.2f')
plt.show()

figure.png


Pairplot

sns.pairplot(train.loc[:,[ "TotalSF", "OverallQual", "GrLivArea", 'YearBuilt',"SalePrice"]])

figure2.png


外れ値の処理

上のpairplot図で外れ値になりそうなものがあることが分かるだろう。'GrLivArea'と"SalePrice"のjointlplot図で確認しよう。

sns.jointplot('GrLivArea',"SalePrice",data= train)

figure3.png

右下の2点はおそらく外れ値であるので取り除く。


しっかり取り除けたか確認しよう。

train = train[~((train['GrLivArea'] > 4000) & (train['SalePrice'] < 300000))]

figure4.png


"OverallQual"と"SalePrice"

sns.jointplot("OverallQual","SalePrice",data= train)

figure5.png

このグラフを見る限り、2次関数のような振る舞いをするようにも思える。そこで、"OverallQual"については2次まで検討する。

train["OverallQual_2"]=train["OverallQual"]**2


"SalePrice"

SalePriceについて観察する。

sns.distplot(train["SalePrice"])

plt.show()

figure6.png

グラフが左へ歪んでいるのが分かるだろう。そこで、対数変換を用いる。


対数変換


対数変換をするメリット

小さな数を大きな数で,大きな数を小さな数で扱える.桁の大きい数のスケールを小さく,または桁の小さい数のスケールを大きくできると,感覚的に数を扱いやすくなる。(正規分布に近づく)この考え方は,マグニチュード,pHなど比較するために利用されている。


全体として対数変換を行う。

train = np.log1p(train)


対数変換後のSalePrice

figure7.png

正規分布に近づいているのが分かる。


対数変換後の観察

plt.figure(figsize=(12, 9))

sns.heatmap(train.loc[:,['SalePrice', 'OverallQual',OverallQual_2' 'TotalSF', 'GrLivArea', 'GarageCars',
'ExterQual_TA', 'FullBath', 'BsmtQual_Ex', 'YearBuilt',
'KitchenQual_TA']].corr(), annot=True, square=True, fmt='.2f')
plt.show()

figure8.png

グラフを見るとSalePriceが11あたりより小さい部分が気になる。住宅価格が低いときのデータが少ないことや幽霊物件の可能性も考えて、今回は取り除こうと思う。

train = train[~(train['SalePrice'] < 11.1)]

plt.figure(figsize=(12, 9))

sns.heatmap(train.loc[:,['SalePrice', 'OverallQual',OverallQual_2' 'TotalSF', 'GrLivArea', 'GarageCars',
'ExterQual_TA', 'FullBath', 'BsmtQual_Ex', 'YearBuilt',
'KitchenQual_TA']].corr(), annot=True, square=True, fmt='.2f')
plt.show()

figure9.png


学習

色々、試したが今回はLasso回帰が一番良い結果を得られた。

X = train.loc[:,train.columns != "SalePrice"]

y = train.loc[:, ['SalePrice']].values
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, random_state = 0)
las = Lasso(alpha=0.0005)
las.fit(X_train, y_train)
print("ラッソ回帰でのRMSE:",np.sqrt(mean_squared_error(las.predict(X_test), y_test)))


結果

ラッソ回帰でのRMSE: 0.10114175427614815


Kaggleでの評価

public score : 0.11833

   順位: 228/4649(上位4.9パーセント)


反省

まだまだ、改善すべきことがたくさんあると思う。とくに欠損値の処理や変数の設計をもっと厳密にする必要があると思う。ただ、データサイエンスとはどういうものなのかを知れたのはとても良かったと思う。