[kaggle / python] 回帰問題（house prices）の超初歩（２）

前回の続きです。
前回の記事はこちら。

https://qiita.com/siruku6/items/97fdd1b12e6b18da103f

# Summary of the previous article（前回のあらまし）

前回は、kaggle の House Prices の課題に対して、
「`xgboost`の`model.fit`にそのままでは渡せない列を、**全て削除する**」
という雑な仕事をやってのけました。

そんな雑な仕事で上位68%になんか入ってしまった、、、というところで終わりました。
（せいぜい90%くらいだと思ってました）

# Today's result

今回は、`LabelEncoder`を使って、
「前回削除していた列を、全て使える状態へと置換した上で、`xgboost`のモデル作成をする」
ここまでやってみました。

その結果、上位 68% から 64% へと上昇しました！(((o(\*ﾟ▽ﾟ\*)o)))
（総プレイヤー数が増えているので、順位は落ちている）

- 前回  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327407/2d584816-ebb3-ea2c-04cf-8e52a5e4c4ae.png)
- 今回  
![2021-06-06_16h28_32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327407/e8b41ab4-a4bd-f705-ce8f-a30686e6a418.png)


さて、具体的に何をしたのか見ていきましょう～


# Second step
## 1. source cord

### 1-0. same as the previous cord

前回と同じソースは、この「1-0.」にまとめておきます(^ワ^\*)
やっていることは、

1. モジュール読み込み
1. データ読み込み
1. 説明変数と目的変数の分割
1. 学習データとテストデータの分割

です。

```python
# import modules
import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
from xgboost import XGBClassifier, XGBRegressor

# load data
import os
for dirname, _, filenames in os.walk('/kaggle/input'):
    for filename in filenames:
        print(os.path.join(dirname, filename))
train = pd.read_csv('/kaggle/input/house-prices-advanced-regression-techniques/train.csv')
test = pd.read_csv('/kaggle/input/house-prices-advanced-regression-techniques/test.csv')

# split data into explanatory variable(説明変数) and response variable(目的変数)
tmp_train_x = train.drop('SalePrice', axis=1)
tmp_train_y = train['SalePrice']

# split data into training data and test data
x_train, x_test, y_train, y_test = \
    train_test_split(tmp_train_x, tmp_train_y, test_size=0.20, random_state=0)

```

### 1-1. replace the variable not used in first step (by LabelEncoder)

前回の記事では、以下の`model.fit`に弾かれたカラムを全て学習データから削除していました。
削除したカラムはこれら。

```python
not_expected_type_column_names = train.columns[train.dtypes == 'object']
not_expected_type_column_names

# 表示結果
[
    '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'
]
```

今回は、これらのカラムも全て学習データとして扱えるように置換してみます。

何も考えなくてもできそうな方法を参考書籍から探してみると、`label encoding`なるものを発見。
まずはこれから試していこう。 [^1]

なんとなく気になったので、どんな文字列データが入っているのか見てみました。（1行目だけ）

```python
print(x_train[not_expected_type_column_names].iloc[0].values)

['RL' 'Pave' nan 'Reg' 'Lvl' 'AllPub' 'Inside' 'Gtl' 'NridgHt' 'Norm'
 'Norm' '1Fam' '1Story' 'Hip' 'CompShg' 'CemntBd' 'CmentBd' 'BrkFace' 'Ex'
 'TA' 'PConc' 'Ex' 'TA' 'Av' 'GLQ' 'Unf' 'GasA' 'Ex' 'Y' 'SBrkr' 'Gd'
 'Typ' 'Gd' 'Attchd' 'Unf' 'TA' 'TA' 'Y' nan nan nan 'New' 'Partial']
```

うむ、どのカラムも文字列が入っていると思っていたのですが...
`nan`がありますね...(∩´﹏`∩)

実は最初、`nan`が学習データに入ったままの状態で`label encoding`にかけようとしたのですが、以下のエラーが発生しました。

```python
---------------------------------------------------------------------------
...(略)...


During handling of the above exception, another exception occurred:

TypeError                                 Traceback (most recent call last)
<ipython-input-97-dd5b0af638b6> in <module>
     11     target_all_data_column = df_all_data[col_name]
     12     le = LabelEncoder()
---> 13     le.fit(target_all_data_column)
     14     if col_name == 'MSZoning': print(target_all_data_column.unique())
     15 

...(略)...

/opt/conda/lib/python3.7/site-packages/sklearn/preprocessing/_label.py in _encode(values, uniques, encode, check_unknown)
    112             res = _encode_python(values, uniques, encode)
    113         except TypeError:
--> 114             raise TypeError("argument must be a string or number")
    115         return res
    116     else:

TypeError: argument must be a string or number
```

`LabelEncoder()`インスタンスの`fit`関数に、`nan`入りのデータを食わせたところで、
`TypeError: argument must be a string or number`が発生してます。

これを避けるために、`nan`を文字列に置換してから`label encoder`にかけます。 [^2]

```python
from sklearn.preprocessing import LabelEncoder

x_train_label_encoded = x_train.copy()
x_test_label_encoded = x_test.copy()

# ※注意
df_all_data = pd.concat([x_train, x_test, test])

for col_name in not_expected_type_column_names:
    # nanを'NaN'という文字列に置換してます
    target_all_data_column = df_all_data[col_name].fillna('NaN')
    le = LabelEncoder()
    le.fit(target_all_data_column)
    if col_name == 'MSZoning': print(target_all_data_column.unique())

    target_train_column = x_train[col_name].fillna('NaN')
    target_test_column = x_test[col_name].fillna('NaN')
    x_train_label_encoded[col_name] = le.transform(target_train_column)
    x_test_label_encoded[col_name] = le.transform(target_test_column)

x_train_label_encoded.head()

      Id  MSSubClass  MSZoning  LotFrontage  LotArea  Street  Alley  LotShape  ...  MiscFeature  MiscVal  MoSold  YrSold  SaleType  SaleCondition
618  619          20         4         90.0    11694       1      1         3  ...            1        0       7    2007         7              5  
870  871          20         4         60.0     6600       1      1         3  ...            1        0       8    2009         9              4 
92    93          30         4         80.0    13360       1      0         0  ...            1        0       8    2009         9              4  
817  818          20         4          NaN    13265       1      1         0  ...            1        0       7    2008         9              4  
302  303          20         4        118.0    13704       1      1         0  ...            1        0       1    2006         9              4  
```

#### メモ ~Label Encodingを行う上での注意~

「※注意」と書いたところには少し手こずりました。
問題は、x_testやtestに入っているラベルが、必ずしもx_trainに入っているわけではないということでした。

`label encoding`を行う上では、存在する全ての種類のラベルをエンコーディングできないと困ります。
もし、x_trainに入っているラベルだけをfitに食べさせてしまうと、x_trainには存在しないが、x_testやtestには存在するラベルが認識できない、というエラーが起きます。

こんなエラーです。
`ValueError: y contains previously unseen labels: 'RRAn'`

というわけで、`label encoder`の`fit`関数には、学習データもテストデータも込みで、全ラベルを食べさせてあげる必要がありました。
落ち着いて考えれば当たり前なのですが、データ分析の経験が浅いこともあり、こんなことにも気付かなかったです(_ _;)

### 1-2. create model and try prediction !

あとは、1-1でlabel encodingにかけたデータで`XGBRegressor`を学習させてあげるだけです。
ここは変数名以外は前回と一緒です。

```python
# create model（学習）
model = XGBRegressor(n_estimators=20, random_state=0)
model.fit(x_train_label_encoded, y_train)

# prediction（推論）
predict_result_for_tr_data = model.predict(x_train_label_encoded)
predict_result = model.predict(x_test_label_encoded)
```

さて、prediction結果を点数にしてみましょう！

```python
# 学習データをpredictした結果
rmse_of_train = np.sqrt(mean_squared_error(predict_result_for_tr_data, y_train))
rmse_of_train

8146.100714171564
```

```python
# テストデータをpredictした結果
rmse_of_test = np.sqrt(mean_squared_error(predict_result, y_test))
rmse_of_test

33584.266159693645
```

前回は`9283.22...`と`37010.13...`だったので、いずれも誤差の値(RMSE)が小さくなっていていい感じです^^*

### 1-3 make submission data

最後に、kaggleに提出するデータを作ります。

```python
test_encoded = test.copy()

for col_name in not_expected_type_column_names:
    # ここでも、全データをlabel encoderのfitに食べさせるのを忘れずに！
    target_all_data_column = df_all_data[col_name].fillna('NaN')
    le = LabelEncoder()
    le.fit(target_all_data_column)

    target_test_column = test[col_name].fillna('NaN')
    test_encoded[col_name] = le.transform(target_test_column)

predict_submission = model.predict(test_encoded)
submission = pd.DataFrame(
    {'Id': test['Id'], 'SalePrice': predict_submission}
)
submission.to_csv('submission_new.csv', index=False)
```

この結果が上位64%でした～(((o(\*ﾟ▽ﾟ\*)o)))
大して上位に行ったわけではないですが、目に見える結果が出ると嬉しいですね！


# Next step

次やりたいことはこの辺の記事の中から選んでいこうと思っています。（適当）

- https://medium-s.jp/kaggle-house-prices/
- https://qiita.com/yyokii/items/a5a772bf4f32183a3501

まずは、部屋の総面積を表すカラムを作成したらどうなるか試してみたい。
その次に、重要度の低いカラムを削除した場合に結果がどう変わるかを見ていきたい。

# References

- [統計WEB / 1-5. 説明変数と目的変数](https://bellcurve.jp/statistics/course/1590.html)  
**説明変数**と**目的変数**の英語訳は、この記事を見て真似しました。
    - explanatory variable: 説明変数
    - response variable: 目的変数

こういう単語がわかってると、英語の関連記事が読みやすくなるはず(｀･ω･´)ｷﾘｯ

- 参考文献  
[Kaggleで勝つデータ分析の技術](https://www.amazon.co.jp/dp/4297108437)

[^1]: `one hot encoding`が使えることは知っていたのですが、列数が増えてしまって手がかかりそうなのと、xgboostしか使わないのであれば、`one hot encoding`する必要はないと聞いたので、今回は却下  
[^2]: 次の二つの記事を見て、`nan`を文字列に置換することにしました [①【Teratail】sklearn の Label Encoderでカテゴリカル変数の前処理をしたい](https://teratail.com/questions/220690)、[②【stackoverflow】LabelEncoder — TypeError: argument must be a string or number](https://stackoverflow.com/questions/61996710/labelencoder-typeerror-argument-must-be-a-string-or-number)

# シリーズ一覧

|No|New Trial            |Score  |Link|Note|
|--|---------------------|-------|----|----|
| 1|xgboost              |0.15663|[こちら](https://qiita.com/siruku6/items/97fdd1b12e6b18da103f)||
| 2|Label Encoding       |0.15160|-   |本記事|
| 3|Add and delete column|0.15140|[こちら](https://qiita.com/siruku6/items/7b693568e6b2535e0c2a)||
| 4|Make integer into categorical|0.14987|[こちら](https://qiita.com/siruku6/items/7f3dc5a9932cd82ab860)||
| 5|One hot Encoding             |0.14835|[こちら](https://qiita.com/siruku6/items/48e70607dc16746ec280)||
| 6|Hyper parameters tuning      |0.13948|[こちら](https://qiita.com/siruku6/items/6dd4903f2ae68533fee6)||
| 7|Logarithimic transformation	 |0.13347||記事未作成|