Edited at

3回目 Kaggle HousePrices


これまでを振り返って

ここまで、ある程度データ分析の流れなどをつかみながら、データ分析がどういうものなのかというものもわかってきた。外れ値の処理や対数変換やFeature Engineering(新たな変数の設計)などはよくできていたと思う。ただ、欠損値処理やエンコーディングの部分はまだ、改善の余地がある。


はじめに

米国アイオワ州のエイムズという都市の物件価格を予測するにあたり、81個の項目数および1460戸の学習データが与えられている。

このデータ分析はアイオワ州のエイムズにある住宅の価格を予測することなので、アメリカの住宅の知識やこの地域についの情報を分析したいと思う。こういった背景を調べることはデータ分析で重要なことだと思う。


日本とアメリカの住宅の違い

 日本の不動産価値は新築時がもっとも高く、築年数に比例して減価していくことが一般とされている。一方、アメリカでは築年数は必ずしも重視されず、「実際に物件が使用できる状態であるかどうか」が特に重視される傾向にある。そのため、築100年以上の物件であっても、修繕を重ね、未だに建物価値がゼロにはならずに、資産として評価されている例も多く見受けられる。日本では、中古物件を壊して建て替えるという「スクラップ・アンド・ビルド」の発想が根本にある為、経年に応じて物件価値が下がっていくが、アメリカでは不動産は「使える間は資産」という捉え方が一般的で建物を大事に使っていく傾向が多い。また、日本では経年による物件価値の減価とともに賃料収入も減額するが、アメリカでは修繕をしっかりと行えば、物件価値を維持したまま賃料収入も得ることができる。このことから、改造日については築年数に上書きする形で前回、変数を作ったことは悪くないと言えるだろう。下の図のように、日本の住宅の中古の新築に対する割合は約56%に対して、アメリカの住宅の中古の新築に対する割合は75%となっており、米国の中古住宅の価値は日本に対して下がらないことがいえる。これはなぜなのか調べてみると日本とアメリカの住宅の価値観の違いにあることが分かった。

      表1. 日米の新築と中古の住宅の戸数と価格

chakkou.gif


日本とアメリカの住宅の価値観の違い

mind.gif

 建物価値に対するマインドの違いにより、短・中期的に居住し転売を繰り返しながらすみかを変えるアメリカをはじめとする欧米では建物メンテナンスを積極的に行う。やはり売却時のことを見据え、資産として建物を捉えている考えが強い。日本では所有したら定住するという考えが一般的で建物に対するメンテナンスを積極的に行っていない。表1の戸数にちゅうもくしてほしい。日本で不動産市場で流通する住宅物件のうち、中古は15%に過ぎず、一方米国では中古が90%以上を占める。いかに日本の住宅市場が新築偏重であり、中古住宅市場が小さいかということが数字として表れている。また、日本の住宅の平均寿命が約26年であるのに対し、アメリカの住宅の平均寿命は約44年。築100年以上できちんとメンテナンスを行い維持されている物件も少なくない。こういったことを知ることで、データ分析の助けになるかもしれない。


エイムズ州の住宅の例

あまり、情報だけではイメージしにくいので実際の住宅を写真などで見たい思う。まずはどういった家を選んで観察するかだが、SalePricesの平均価格に近いほうが良いだろう。まずどういった分布になっているか見てみよう。

saleprices.png

train.loc[:,"SalePrice"].mean()

train.loc[:,"SalePrice"].median()

平均値 $180921

中央値 $163000

分布がかなり左に歪んでおり、平均値と中央値が異なっている。こういった場合は中央値を採用するのが良いと考える。なぜならば、住宅価格が極端に高いものに平均値が引っ張られている可能性があるからだ。このあたりの物件を参照しよう。


物件の情報

$169,900

3 beds 2 baths 1,017 sqft

アメリカでは一般的な家庭でもバスルームが2つあることが多く、3つや4つあるという家も珍しくない。お風呂もトイレ同様にプライベートな空間ととらえられており、部屋ごとに付けることが多い。トイレやお風呂の変数もあったので、後で注目したい。


場所

● 暗い点の場所が今回、参照する物件の位置である。

707B3C92-446D-463E-8F9D-BE9592931562.jpg


外装

ISizytjls8w53v1000000000.jpg


内装

ISivzn4pax9sav1000000000.jpg

ISm2w0ffxf8yov1000000000.jpg

ISuctnl5y8l0bv1000000000.jpg

ISa1j0dc83vepv1000000000.jpg

ISqx2u7zwtvbiv1000000000.jpg


周辺

ISuoecczzmekpv1000000000.jpg

スクリーンショット (9).png


地下室

上の写真でも地下室の写真があったが、地下室もアメリカにある特徴的なようそである。実はこの「地下室」にはきちんとした理由があるらしい。アメリカの特に北部の古い一軒家には暖房器具を置くスペースとして地下室を設けているそうだ。また竜巻がよく発生する地域では家を頑丈にするために半地下にしてあり、避難部屋としての役割がある。

まだいろいろ深く調べたいが、今回はこのあたりでやめようと思う。背景を知ると変数として取り入れたいものがどんどん出てくるので、この作業は重要かもしれない。


ライブラリのインポート


import numpy as np
import pandas as pd
from pandas import Series, DataFrame
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline


データの確認

改めて、データを振り返ろう。

print(train.shape)

print(test.shape)
#出力結果
(1460, 81)
(1459, 80)


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')


欠損値処理とエンコーディング


欠損値の見方

これまで、trainデータだけで、いろいろ見ていたが、説明変数の情報を見るだけだったら、trainデータとtestデータを結合してほうが、データ数も増やせるし、より精度の高い分析が可能になると思った。これは、先日の発表会で誰かが言っていたので参考にさせてもらう。

all = pd.concat([train, test],axis=0,join='inner').reset_index(drop = True)

あくまで補足だが、join='inner' と書くことで、内部結合を実行してくれる。内部結合とは共通列が一致する列のみを取得する方法である。今回は、testデータに"SalePrice"がないので、自動で省かれている。現在、SQLなどを勉強しているのでこういった結合の仕方はよく耳にする。インターン先でもSQLを扱う場所も出てくると思うので、知っておくと後々、理解しやすくなると思った。


欠損率

欠損率の高いものから表示すると下図のようになる。これまで欠損値の高いものに関しては削除してきたが、しっかり性質を考えて、補っていこうと思う。

                  kesson.png


評価する対象が存在しないため、欠損しているもの

データ(変数)には大きく分けて、質的データと量的データの区別があり、データの種類によって分析や処理の手法が異なってくる。


質的データ

質的データは、分類(カテゴリー)として測定できるもので、順序尺度と名義尺度に分けられる。このような場合、欠損値は"None"で埋めるのが良いだろう。カテゴリーデータやカテゴリカルデータともよばれる。特に、会員である、ないといった2種類のカテゴリ値しかとらない値はフラグ値と呼ぶ。

train.loc[:,train.dtypes=="object"].columns

'MSZoning', 'Street', 'Alley', 'LotShape', 'LandContour', 'Utilities',
'LotConfig', 'LandSlope', 'Neighborhood', 'Condition1', 'Condition2',
'BldgType', 'HouseStyle', 'RoofStyle', 'RoofMatl', 'Exterior1st',
'Exterior2nd', 'MasVnrType', 'ExterQual', 'ExterCond', 'Foundation',
'BsmtQual', 'BsmtCond', 'BsmtExposure', 'BsmtFinType1', 'BsmtFinType2',
'Heating', 'HeatingQC', 'CentralAir', 'Electrical', 'KitchenQual',
'Functional', 'FireplaceQu', 'GarageType', 'GarageFinish', 'GarageQual',
'GarageCond', 'PavedDrive', 'PoolQC', 'Fence', 'MiscFeature',
'SaleType', 'SaleCondition'


for col in ("PoolQC", "MiscFeature", 'GarageType',"Alley", "Fence", "FireplaceQu", "MSSubClass", 'GarageFinish', 'GarageQual', 'GarageCond', 'MasVnrType', 'BsmtQual', 'BsmtCond', 'BsmtExposure', 'BsmtFinType1', 'BsmtFinType2'):
all_data[col] = all_data[col].fillna('None')


量的データ

間隔尺度および比尺度のデータ。数量データとも言う。

例えば、GarageArea特徴量は家のガレージの面積を表す変数であり、NAは家にガレージが無いことを示す。家にガレージが無ければ、その家のガレージの面積は0とするのが適当であるから、欠損値は0で埋めた。このように、欠損値を0とするのが妥当なものについては、欠損値を0で埋めた。

for col in ('GarageArea', 'GarageYrBlt', 'GarageCars', 'BsmtFinSF1', 'BsmtFinSF2', 'BsmtUnfSF','TotalBsmtSF', 'BsmtFullBath', 'BsmtHalfBath', "MasVnrArea", ):

all_data[col] = all_data[col].fillna(0)


最頻値


for col in ('MSZoning', 'Electrical', 'KitchenQual', 'Exterior1st', 'Exterior2nd', 'SaleType', 'Functional'):
all_data[col] =all_data[col].fillna(all_data[col].mode()[0])

 岡野君がすべての変数について欠損値処理とエンコーディングをしていたので、参考にさせてもらった。岡野君は、欠損値をNone,0,最頻値で分けたものを順序特徴量と名義特徴量ごとにエンコーディングしていた。僕はここにもう少し、考察とともに工夫を加えたい。

 岡野君の場合、"PoolQC"特徴量の"NA"は家にプールがついていないことを表すため、欠損値は"None"で埋めていた。そして、順序特徴量としてラベルエンコーディングしていた。ただ、僕はここに疑問を感じた。"PoolQC" は確かに順序特徴量[NA, 'Ex', 'Fa', 'Gd']だが、ほとんどが "NA" だった。この場合、順序特徴量としてラベルエンコーディングするよりも、せっかく「あるかないか」で欠損値処理したので{0,1}でOne-Hotエンコーディングしたほうが良いと思った。なぜなら、順序特徴量をあらわす変数の数が少ないと偏った学習をする可能性があるからだ。このように、本来、Qualityなど順序特徴量として扱うものでも、欠損率によって、それがあるかないかで名義特徴量のような扱いをしたほうがいいことがあると考えた。順序特徴量今回は欠損率が約50%以上のもの['PoolQC', 'MiscFeature', 'Alley', 'Fence', 'FireplaceQu']はOne-Hotエンコーディングなど特別な処理を行うことにした。ただ、もうすでに、"FireplaceQu"に関しては既に暖炉の数をあらわす変数"Fireplace"があることに気づき、'FireplaceQu'に関してはOne-Hotエンコーディングはせずに、削除して、暖炉の数をあらわす変数"Fireplace"のみを採用した。さらにもう一つ議論の余地があるのがPoolである。


Pool

Poolというものをあらわす変数は二つあることが分かった。


train.loc[:,train.columns.str.contains("Pool")].columns

Index(['PoolArea', 'PoolQC'], dtype='object')

この二つである。ここで、これまでの観察で面積と品質の積はより特徴量を生成することが分かった。今回もそれを適用しようと思う。

train["PoolQC"]=train["PoolQC"].fillna("None")

from sklearn.preprocessing import LabelEncoder

#マッピングを実行
for col in ["PoolQC"]:
lbl = LabelEncoder()
lbl.fit(train[col])
train[col] = lbl.transform(train[col])

train["Pool"]=train["PoolArea"]*train["PoolQC"]
sns.jointplot(x="Pool",y="SalePrice",data=train)

figure231.png

この二つの変数はプール面積とその質をあらわすので、かけて一つにする。


Idの削除

Idは必要ないので削除する。


train=train.drop(['Id'],axis=1)


MoSoldの削除

基本的に関係ないようなので削除する

figure251.png


YrSoldの削除

figure221.png


Utilitiesの削除

Utilities: Type of utilities available

AllPub All public Utilities (E,G,W,& S)


NoSewr Electricity, Gas, and Water (Septic Tank)

NoSeWa Electricity and Gas Only

ELO Electricity only

all["Utilities"].value_counts()

#出力結果

AllPub 2916
NoSeWa 1

Utilities変数は、Utility(ガス、電気、水道など)の利用可能状況を表しているが、3つのサンプルを除き、すべて"All Pub"の値であるから、これは予測に使えそうにない。よって、この変数は除く。


MiscFeature

MiscFeature: Miscellaneous feature not covered in other categories

   Elev Elevator

Gar2 2nd Garage (if not described in garage section)
Othr Other
Shed Shed (over 100 SF)
TenC Tennis Court
NA None

この変数は上のように、その他の機能としてまとめられている。欠損値は、“None”として扱われているものであるが、欠損率が96パーセントと高い。それもそのはずで、項目を見ればわかると思うが、一般家庭では通常、ついていない機能がみられる。こういった観点から、この変数を取り入れるのはあまりよろしないと思ったため、削除する。


for columns in ("PoolQC", "MiscFeature", 'GarageType',"Alley", "Fence", "FireplaceQu", "MSSubClass", 'GarageFinish', 'GarageQual', 'GarageCond', 'MasVnrType', 'BsmtQual', 'BsmtCond', 'BsmtExposure', 'BsmtFinType1', 'BsmtFinType2'):
all[columns] = all[columns].fillna('None')
for columns in ('GarageArea', 'GarageYrBlt', 'GarageCars', 'BsmtFinSF1', 'BsmtFinSF2', 'BsmtUnfSF','TotalBsmtSF', 'BsmtFullBath', 'BsmtHalfBath', "MasVnrArea", ):
all[columns] = all[columns].fillna(0)


外れ値、Feature Engineering、対数変換

外れ値、Feature Engineering、対数変換はこれまで同様に行う。


外れ値

    figure3.png   → 処理後 →     figure4.png


Feature Engineering

新たな変数を作るときは、基本的にそれに使った変数は取り除くことにする。それは、多重共線性を考慮するためである。


築年数

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

train['YearRemodAdd']= 2011- train['YearRemodAdd']


改造日による築年数の更新

最初に書いたように、アメリカでは住宅を改造したとき新築と同様の扱いをしてもかまわない。これが日本の場合だと、色々考慮する必要があるかもしれない。


train['YearBuilt'][train['YearBuilt']!=train['YearRemodAdd']]
=train['YearRemodAdd']


面積と品質


train["TotalSF"] = train["TotalBsmtSF"] + train["1stFlrSF"] + train["2ndFlrSF"] + train['GarageArea']
train["TotalQual"]=train["TotalSF"] *train['OverallQual']
train["TotalBsmtSFQual"]=train["TotalBsmtSF"] *train['BsmtQual']

              figure10.png

                                   figure11.png

           

このように、面積とその面積当たりの平均品質を掛け算することでよい結果が得られたので、他にも応用が利くと考えた。

例えば、"GarageArea"と"GarageQual"はまとめてもよいだろう。


Garageの設計

train["Garage"]=train["GarageArea"]*train["GarageQual"]


porchの設計

ベランダについては,以下のようにまとめられている。


  • WoodDeckSF: Wood deck area in square feet


  • OpenPorchSF: Open porch area in square feet


  • EnclosedPorch: Enclosed porch area in square feet


  • 3SsnPorch: Three season porch area in square feet


  • ScreenPorch: Screen porch area in square feet


これらは、一つの変数“Porch” としてまとめた。


train["Porch"]=train['OpenPorchSF']+train['EnclosedPorch']+train['3SsnPorch']+train['ScreenPorch']

                figure232.png


対数変換

figure6.png


歪度

分布が正規分布からどれだけ歪んでいるかを表す統計量で、左右対称性を示す指標のことです。サンプルサイズをn、各データの平均値を、標準偏差をsとすると歪度は次の式から求められます。

                    quicklatex.com-ed33dd7b937bc96f531b8d882aa48844_l3.png

「右裾が長い」もしくは「左に偏った」分布のときには正の値を、「左裾が長い」もしくは「右に偏った」分布のときには負の値をとります。左右対称の分布(例えば正規分布)の場合には0になります。それでは、今回の歪度を計算してみよう。

train["SalePrice"].skew()

#計算結果
1.8828757597682129


歪度の解釈

 歪度は、なぜこのように3乗して計算し、0を越えると右の裾が長い分布、0より小さいと左の裾が長い分布を示すことなるのか。まず、歪度がプラスの値なのかマイナスの値なのかは、(xi-μ) / s の値がプラスの値であれば、3乗をしてもプラスの値である。しかし(xi -μ) / σ の値がマイナスであれば3乗をすると、またマイナスの値となる。右の裾が長い分布だと、(xi-μ) / s の平均から遠く離れたデータが右の裾の先にあります。それらの値を3乗すると大きな値となりますから、結果として歪度の値もプラス方面に大きくなりやすい。逆に左の裾が長い分だと、左の裾の先に平均から離れたマイナスのデータがあるから、結果として歪度の値がマイナス方面に数値が出る。データの値が平均値から絶対値1を越えて、平均値から離れるほど、3乗した値は大きくなる。右の裾が長い分布であるほど、歪度も大きな値となる。今回の "SalePrice"は大きく、左に偏っている。そのため、対数変換する。

figure7.png


説明変数の対数変換

これまではすべて対数変換していたが、すべて対数変換することはきけんである。変換するということは空間を変形することであり、元のデータの意味を歪めたり、大切な情報を落としているかもしれない。そのような点に注意して、”SalePrice"と同様に対数変換した。


学習

変数の数が多いのでLasso, ElasticNet, Ridgeを用いたがLassoが過去2回と同様に良かった。


考察

今回は原点に返り、色々と丁寧に調べた。アメリカと日本の文化の違いはきちんと調べると注目すべき変数が色々見つかることが分かった。

前処理はすべての変数についてできなかったが大変な作業だと思った。

 データの期間(~2011)はサブプライム住宅ローン危機という不動産価格に大きく影響する事件があった。サブプライムローンとは、主にアメリカ合衆国において貸し付けられるローンのうち、サブプライム層向けとして位置付けられるローン商品をいう。サブプライムローンは証券化され 、世界各国の投資家へ販売されたが、米国において2001 - 2006年ごろまで続いた住宅価格の上昇を背景に、格付け企業がこれらの証券に高い評価を与えていた。また、この証券は他の金融商品などと組み合わされ世界中に販売されていた。しかし2007年夏ごろから住宅価格が下落し始め、サブプライムローンが不良債権化した(サブプライム住宅ローン危機)。これと共にサブプライムローンに関わる債権が組み込まれた金融商品の信用保証までも信用を失い、市場では投げ売りが相次いだ。この波紋から2008年終盤にはリーマン・ブラザーズ倒産によるリーマン・ショックなどが引き起こされ、高い信用力を持っていたAIG、ファニーメイやフレディマックが国有化される事態にまで至った。そこへ大幅な世界同時株安が度重なった。そして世界中の金融機関で信用収縮が連鎖した。サブプライムローンはクレジット・デフォルト・スワップと共に世界金融危機の原因となった。このようなことを特に考慮することなく今回は分析を行った。上のようなことを考慮するとさらに工夫ができるかもしれない。