見出し画像

機械学習(回帰)⑴〜データ取込・前処理〜

BRIXIT Journal note第2弾ということで、今回は社長酒巻の弟であり、
データサイエンティストをしている裕也がお送りします。
よろしくお願いします。

今回は私が最初に勉強がてら使っていたKaggleのチュートリアルであるHouse Price (住宅販売価格予測)を簡単な実例と共にご紹介致します。
大まかな流れをご紹介するので、数式等は記述しませんのでご了承下さい。

因みに、Pythonのバージョンは3.6.3を使用しています。

機械学習の基本的な流れ

データ取込 :DBやCSV・EXCELファイルなど 
前処理   :特徴量として使える形に加工
学習・予測 :データによりモデルを選定
精度検証  :回帰→RMSE, 分類→accuracy等で精度確認

今回行ったKaggleのチュートリアルでは予測対象の正解データがないので、
Kaggleで表示されるScoreを基に検証をしていきます。

【データ取込】

■基本的なライブラリのインポート

import pandas as pd
import numpy as np
# データ可視化
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns

■データ読み込み

Kaggleに会員登録し、House Priceのページに行くとデータセットをダウンロードできます。ディレクトリ構成は単一ディレクトリのみで行います。

train_df = pd.read_csv('train.csv')
test_df = pd.read_csv('test.csv')

【前処理】

■データ量と基本統計量の確認

train_df.shape
(1460, 81)
test_df.shape
(1459, 80)

train_dfには1460行、81列のデータが確認できました。
では実際にどのようなデータがあるのか見ていきます。

# リストだと長いのでarrayで表示
np.array(train_df.columns)

array(['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)

敷地面積や屋根の形状、車庫の有無など様々な条件があることが
分かりました。
予測対象の目的変数は最後の「SalePrice」にあたります。
次にSalePriceの基本統計量を見ていきます。
Pythonではdescribe()を使うことで簡単に確認できます。

train_df['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

最大値と最小値に大きな差がありますね。
グラフでも確認してみましょう。

plt.style.use('ggplot')
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
ax.hist(train_df['SalePrice'], bins=50)
ax.set_ylim(0,200)
fig.show()

画像1

可視化することで、データに偏りが確認できました。
このままでは外れ値が大きく影響してしまう為、
データを正規化していきます。

train_id = train_df['Id']
test_id = test_df['Id']
y_train = train_df['SalePrice']
x_train = train_df.drop(['Id'], axis=1)
x_test = test_df.drop(['Id'], axis=1)
X_all = pd.concat((x_train, x_test))
y_train = np.log(y_train)
sns.distplot(y_train)

画像2

■データ欠損値の処理

実務でも多々ありますが、データの欠損値処理って言っちゃ悪いが
結構面倒くさい作業です。(笑)
データとしての「0」と「無し」では意味が大きく変わってしまうので、
この欠損値処理をどうするかによって精度にも影響してくる大事な作業です。
欠損値を確認していきましょう。

# 欠損値の個数を確認
X_all.isnull().sum()[X_all.isnull().sum()!=0].sort_values(ascending=False)

PoolQC          2909
MiscFeature     2814
Alley           2721
Fence           2348
SalePrice       1459
FireplaceQu     1420
LotFrontage      486
GarageQual       159
GarageCond       159
GarageFinish     159
GarageYrBlt      159
GarageType       157
BsmtExposure      82
BsmtCond          82
BsmtQual          81
BsmtFinType2      80
BsmtFinType1      79
MasVnrType        24
MasVnrArea        23
MSZoning           4
BsmtFullBath       2
BsmtHalfBath       2
Utilities          2
Functional         2
Electrical         1
BsmtUnfSF          1
Exterior1st        1
Exterior2nd        1
TotalBsmtSF        1
GarageCars         1
BsmtFinSF2         1
BsmtFinSF1         1
KitchenQual        1
SaleType           1
GarageArea         1
dtype: int64
# heatmapで欠損箇所を確認
sns.heatmap(train_df.isnull(), cbar=False)

画像3

データの欠損はSalePrice合わせて35項目となりました。多い……
では補完していきます。今回は4種類に分けて補完していきます。

### 欠損値の補完
# 0で補完
X_all['LotFrontage'].fillna(0, inplace=True)
X_all['GarageYrBlt'].fillna(0, inplace=True)
X_all['MasVnrArea'].fillna(0, inplace=True)
X_all['BsmtFullBath'].fillna(0, inplace=True)
X_all['BsmtHalfBath'].fillna(0, inplace=True)
X_all['BsmtFinSF1'].fillna(0, inplace=True)
X_all['BsmtFinSF2'].fillna(0, inplace=True)
X_all['BsmtUnfSF'].fillna(0, inplace=True)
X_all['GarageCars'].fillna(0, inplace=True)
X_all['GarageArea'].fillna(0, inplace=True)
X_all['TotalBsmtSF'].fillna(0, inplace=True)
# NAという文字列で補完
X_all['PoolQC'].fillna('NA', inplace=True)
X_all['Alley'].fillna('NA', inplace=True)
X_all['Fence'].fillna('NA', inplace=True)
X_all['FireplaceQu'].fillna('NA', inplace=True)
X_all['GarageFinish'].fillna('NA', inplace=True)
X_all['GarageQual'].fillna('NA', inplace=True)
X_all['GarageCond'].fillna('NA', inplace=True)
X_all['GarageType'].fillna('NA', inplace=True)
X_all['BsmtExposure'].fillna('NA', inplace=True)
X_all['BsmtCond'].fillna('NA', inplace=True)
X_all['BsmtQual'].fillna('NA', inplace=True)
X_all['BsmtFinType2'].fillna('NA', inplace=True)
X_all['BsmtFinType1'].fillna('NA', inplace=True)
X_all['MasVnrType'].fillna('NA', inplace=True)
# Noneという文字列で補完
X_all['MiscFeature'].fillna('None', inplace=True)
# 最頻値で補完
X_all['MSZoning'] = X_all['MSZoning'].fillna(X_all['MSZoning'].mode()[0])
X_all['Utilities'] = X_all['Utilities'].fillna(X_all['Utilities'].mode()[0])
X_all['Functional'] = X_all['Functional'].fillna(X_all['Functional'].mode()[0])
X_all['Exterior2nd'] = X_all['Exterior2nd'].fillna(X_all['Exterior2nd'].mode()[0])
X_all['Exterior1st'] = X_all['Exterior1st'].fillna(X_all['Exterior1st'].mode()[0])
X_all['SaleType'] = X_all['SaleType'].fillna(X_all['SaleType'].mode()[0])
X_all['Electrical'] = X_all['Electrical'].fillna(X_all['Electrical'].mode()[0])
X_all['KitchenQual'] = X_all['KitchenQual'].fillna(X_all['KitchenQual'].mode()[0])

ちゃんと補完できたか再度可視化して確認。

画像4

SalePrice以外が補完できました。

■特徴量の加工

文字列データはそのままでは特徴量として使うことができない為、
数値化しないといけません。

まず確認データ型を確認します。

X_all.dtypes

1stFlrSF           int64
2ndFlrSF           int64
3SsnPorch          int64
Alley             object
BedroomAbvGr       int64
BldgType          object
BsmtCond          object
BsmtExposure      object
BsmtFinSF1       float64
BsmtFinSF2       float64
BsmtFinType1      object
BsmtFinType2      object
BsmtFullBath     float64
BsmtHalfBath     float64
BsmtQual          object
BsmtUnfSF        float64
CentralAir        object
Condition1        object
Condition2        object
Electrical        object
EnclosedPorch      int64
ExterCond         object
ExterQual         object
Exterior1st       object
Exterior2nd       object
Fence             object
FireplaceQu       object
Fireplaces         int64
Foundation        object
FullBath           int64
                 ...   
LotShape          object
LowQualFinSF       int64
MSSubClass         int64
MSZoning          object
MasVnrArea       float64
MasVnrType        object
MiscFeature       object
MiscVal            int64
MoSold             int64
Neighborhood      object
OpenPorchSF        int64
OverallCond        int64
OverallQual        int64
PavedDrive        object
PoolArea           int64
PoolQC            object
RoofMatl          object
RoofStyle         object
SaleCondition     object
SalePrice        float64
SaleType          object
ScreenPorch        int64
Street            object
TotRmsAbvGrd       int64
TotalBsmtSF      float64
Utilities         object
WoodDeckSF         int64
YearBuilt          int64
YearRemodAdd       int64
YrSold             int64
Length: 80, dtype: object

dtypeを入力すると、各カラムのデータ型が確認できます。
objectと書いてある箇所が今回変換したい項目です。
Pythonでは便利なライブラリ、その名も「ラベルエンコーダー」なるものがあります。
早速使ってみましょう。

# ラベルエンコーダーでSTRを数値化
from sklearn.preprocessing import LabelEncoder

for i in range(X_all.shape[1]):
   # columnsがobjectのものだけ変換する
   if X_all.iloc[:,i].dtypes == object:
       lbl = LabelEncoder()
       lbl.fit(list(X_all.iloc[:,i].values))
       X_all.iloc[:,i] = lbl.transform(list(X_all.iloc[:,i].values))

ちゃんと変換出来たか確認↓

X_all.dtypes

1stFlrSF           int64
2ndFlrSF           int64
3SsnPorch          int64
Alley              int64
BedroomAbvGr       int64
BldgType           int64
BsmtCond           int64
BsmtExposure       int64
BsmtFinSF1       float64
BsmtFinSF2       float64
BsmtFinType1       int64
BsmtFinType2       int64
BsmtFullBath     float64
BsmtHalfBath     float64
BsmtQual           int64
BsmtUnfSF        float64
CentralAir         int64
Condition1         int64
Condition2         int64
Electrical         int64
EnclosedPorch      int64
ExterCond          int64
ExterQual          int64
Exterior1st        int64
Exterior2nd        int64
Fence              int64
FireplaceQu        int64
Fireplaces         int64
Foundation         int64
FullBath           int64
                 ...   
LotShape           int64
LowQualFinSF       int64
MSSubClass         int64
MSZoning           int64
MasVnrArea       float64
MasVnrType         int64
MiscFeature        int64
MiscVal            int64
MoSold             int64
Neighborhood       int64
OpenPorchSF        int64
OverallCond        int64
OverallQual        int64
PavedDrive         int64
PoolArea           int64
PoolQC             int64
RoofMatl           int64
RoofStyle          int64
SaleCondition      int64
SalePrice        float64
SaleType           int64
ScreenPorch        int64
Street             int64
TotRmsAbvGrd       int64
TotalBsmtSF      float64
Utilities          int64
WoodDeckSF         int64
YearBuilt          int64
YearRemodAdd       int64
YrSold             int64
Length: 80, dtype: object

PoolQL等を見てみると、objectからint64になりました。
これで特徴量として使えるようになりました。
ここからtrain, test用に再度データを分割します。

n_train = train_df.shape[0]
train = X_all[:n_train]
x_test = X_all[n_train:]
y = y_train
X = train.drop(['SalePrice'],axis=1)

ライブラリの中にはRandomForestやLightGBMのように、
どの特徴量がモデルとして重要度が高いのか確認することも出来ます。

# ランダムフォレストで特徴量を調べる
from sklearn.ensemble import RandomForestRegressor
rf = RandomForestRegressor(n_estimators=20, max_features='auto')
rf.fit(X, y)

ranking = np.argsort(-rf.feature_importances_)
f, ax = plt.subplots(figsize=(11,9))
sns.barplot(x=rf.feature_importances_[ranking], 
           y=X.columns.values[ranking],
           orient='h')
ax.set_xlabel("feature importance")
plt.tight_layout()
plt.show()

画像5

■外れ値の確認

データには凡そ平均から大きく離れた外れ値というものがあります。
重要度の高いTOP3を見てみましょう。

cols_list = ['OverallQual', 'TotalBsmtSF', 'GrLivArea']
for cols in cols_list:
   fig = plt.figure(figsize=(12,7))
   sns.regplot(x=X[cols], y=y)
   plt.tight_layout()
   plt.show()

画像6

画像7

画像8

OverallQualは特別外れ値がある訳ではなさそう。。。
TotalBsmtSFとGrLivAreaには近似線から大きく外れた値が見られますね。
今回はこちらの外れ値を除いて前処理を終えたいと思います。

# 外れ値を取り除く
X_mat = X
X_mat = pd.concat([X_mat,y], axis=1)
X_mat = X_mat.drop(X_mat[(X_mat['TotalBsmtSF'] > 6000)].index)
X_mat = X_mat.drop(X_mat[(X_mat['GrLivArea']>4600)].index)

X_train = X_mat.drop(['SalePrice'], axis=1)
y_train = X_mat['SalePrice']

取り除けたか再可視化して確認します。

cols_list = ['TotalBsmtSF', 'GrLivArea']
for cols in cols_list:
   fig = plt.figure(figsize=(12,7))
   sns.regplot(x=X_train[cols], y=y_train)
   plt.tight_layout()
   plt.show()

画像9

画像10

外れ値がしっかり無くなっていることが確認出来ました!
実務ではこの数十倍くらいの分析〜前処理をしますが、
今回は分析も程々に簡単な前処理を行ってきました。
次回(2)では、作成したデータを使って実際に機械学習モデルへ
適用していきたいと思います!

この記事が気に入ったらサポートをしてみませんか?