アワビの年齢予測モデルの作成

# はじめに
今回は[the UC Irvine Machine Learning Repository](http://archive.ics.uci.edu/ml/index.php)から、アワビのデータを取得し、アワビの年齢予測をするモデルの作成までの道のりを書いていこうと思います。
Jupyter notebookでの実装の確認を行なっていますので、環境によっては違った挙動をするかもしれませんので、注意してください。

# データの準備

とりあえず、使用するmoduleを読み込んでおきます。

```python
import pandas as pd
import sklearn
from pandas import Series,DataFrame
from sklearn import linear_mode
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestRegressor
```

今回の使用するデータは[こちら](http://archive.ics.uci.edu/ml/machine-learning-databases/abalone/abalone.data)でダウンロードし、`abalone.data`というファイルが得られます。

取得したファイルを開きます。

```python
with open('abalone.data') as f:
    abalone_file = f.read()
```
中身を確認して、

```python
abalone_file
```

<img width="1105" alt="スクリーンショット 2020-03-01 12.32.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298735/53898540-ff3b-57d1-2953-289d2c4b8100.png">

このような出力になりました。
`\n`で区切られていそうだったので、以下のようにしてDataFrameの型にして、中身を確認してみました。

```python
abalone_data = []
for line in abalone_file.split('\n'):
    abalone_data.append(line.split(','))

abalone_df = DataFrame(abalone_data)

abalone_df.head()
```

<img width="1063" alt="スクリーンショット 2020-03-01 12.35.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298735/87f9086a-2645-655b-72ae-30be9fbc6814.png">

カラムが数値になっていて何の値かがよくわからなかったので、データを取得した[the UC Irvine Machine Learning Repository](http://archive.ics.uci.edu/ml/datasets/Abalone)の説明欄を見て、カラムの設定をしました。

```python
# 列名の設定
abalone_df.columns = ['Sex', 'Length', 'Diameter', 'Height', 'Whole weight', 'Shucked weight', 'Shell weight', 'Viscera weight', 'Rings']
```

次に、データの中身の型の確認をします。

```python
abalone_df.info()
```
出力結果↓

```
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 4178 entries, 0 to 4177
Data columns (total 9 columns):
 #   Column          Non-Null Count  Dtype 
---  ------          --------------  ----- 
 0   Sex             4178 non-null   object
 1   Length          4177 non-null   object
 2   Diameter        4177 non-null   object
 3   Height          4177 non-null   object
 4   Whole weight    4177 non-null   object
 5   Shucked weight  4177 non-null   object
 6   Shell weight    4177 non-null   object
 7   Viscera weight  4177 non-null   object
 8   Rings           4177 non-null   object
dtypes: object(9)
memory usage: 293.9+ KB
```

全てのDtypeがobjectとなってしまっていたので、適したものに変換します。
この時の変換が少し周りくどい感じがしたので、もっと良い方法があれば教えていただけると嬉しいです。

```python
# abalone_dfのSex列以外を取得し、float型に変換した新しいdataframeを作成
abalone_dframe = abalone_df.iloc[:, 1:9].astype('float')
# 新しく作成したabalone_dframeに元のabalone_dfのSex列のデータを代入
abalone_dframe['Sex'] = abalone_df['Sex']
```

変換が完了しているかどうかの確認をします。

```python
abalone_dframe.info()
```
出力結果↓

```
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 4178 entries, 0 to 4177
Data columns (total 9 columns):
 #   Column          Non-Null Count  Dtype  
---  ------          --------------  -----  
 0   Length          4177 non-null   float64
 1   Diameter        4177 non-null   float64
 2   Height          4177 non-null   float64
 3   Whole weight    4177 non-null   float64
 4   Shucked weight  4177 non-null   float64
 5   Shell weight    4177 non-null   float64
 6   Viscera weight  4177 non-null   float64
 7   Rings           4177 non-null   float64
 8   Sex             4178 non-null   object 
dtypes: float64(8), object(1)
memory usage: 293.9+ KB
```

型の変換は完了しました。
しかし、`Non-Null Count`の部分の値で、`Sex`のみ`4178`と表示されており、違和感を感じたので、データの後ろ5つを確認してみました。

```python
abalone_dframe.tail()
```
<img width="1059" alt="スクリーンショット 2020-03-01 12.52.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298735/ab1fbeca-2639-73ba-950d-d38c8a3a3aed.png">

なるほど、最終行は`Sex`列だけ空白になっています。
最終行は必要なさそうなので削除します。

```python
abalone_dframe = abalone_dframe.drop(4177)
```

確認

```python
abalone_dframe.info()
```

出力結果↓

```
<class 'pandas.core.frame.DataFrame'>
Int64Index: 4177 entries, 0 to 4176
Data columns (total 9 columns):
 #   Column          Non-Null Count  Dtype  
---  ------          --------------  -----  
 0   Length          4177 non-null   float64
 1   Diameter        4177 non-null   float64
 2   Height          4177 non-null   float64
 3   Whole weight    4177 non-null   float64
 4   Shucked weight  4177 non-null   float64
 5   Shell weight    4177 non-null   float64
 6   Viscera weight  4177 non-null   float64
 7   Rings           4177 non-null   float64
 8   Sex             4177 non-null   object 
dtypes: float64(8), object(1)
memory usage: 326.3+ KB
```

よさそうです。
`Rings`のデータは年齢の識別のみに使用するので、削除しておきます。

```python
abalone_dframe =  abalone_dframe.drop('Rings', axis=1)
```

今回、性別のデータがM,F,Iというようにカテゴリになっているので、説明変数として使用できるようにダミー変数に変換します。

```python
abalone_dframe = pd.get_dummies(abalone_dframe, drop_first=True, columns=['Sex'])
```

確認

```python
abalone_dframe.head()
```

<img width="1067" alt="スクリーンショット 2020-03-01 15.22.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298735/17688cd5-c9aa-8871-6c8d-a04ef40ad0b0.png">

`Rings`が消え、`Sex_I`と`Sex_M`のダミーデータが作成されているのが確認できました。

とりあえずデータの準備は完了、ということにします。

# モデルの作成

今回は重回帰分析とランダムフォレスト回帰を行ってみます。

### 重回帰分析

```python
X = abalone_dframe.drop('Age', axis=1)
Y = abalone_dframe['Age']

l_model = linear_model.LinearRegression()

# 学習データとしてデータの70%を使用
X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.3, random_state=0)

clf = l_model.fit(X_train, y_train)

print('重回帰分析：', clf.score(X_test, y_test))
```

出力結果↓

```
重回帰分析： 0.5253868694528048
```

各列のデータの値が統一性に欠けるため、学習データを標準化してみました。

```python
sc = StandardScaler()
sc.fit(X_train)
X_train_std = sc.transform(X_train)
X_test_std = sc.transform(X_test)

clf = l_model.fit(X_train_std, y_train)

print('重回帰分析:', clf.score(X_test_std, y_test))
```

出力結果↓

```
重回帰分析: 0.5253868694528043
```

あれ、結果は変わらないんだ、、
ここでの標準化した場合としなかった場合の違いを教えていただけると嬉しいです、、、


### ランダムフォレスト回帰

```python
estimator = RandomForestRegressor()

clf = estimator.fit(X_train_std, y_train)
print('ランダムフォレスト回帰:', clf.score(X_test_std, y_test))
```

出力結果↓

```
ランダムフォレスト回帰: 0.5437448805034208
```

ランダムフォレスト回帰の方が重回帰分析よりも精度の高い結果となりました。


# おまけ

### グリッドサーチを使ってみる


```python
param_grid = {'criterion':['mse','friedman_mse','mae'],
              'max_depth': [2,3,4,5,6,7,8,9],
              'n_estimators': [1,10,50,100]}

# グリッドサーチ
gs = GridSearchCV(estimator=estimator, param_grid=param_grid, scoring='r2', n_jobs=-1)
gs = gs.fit(X_train, y_train)

# score、ベストパラメータを表示
print('ランダムフォレスト回帰：',gs.score(X_test,y_test))
print('ベストパラメータ：',gs.best_params_)
```

出力結果↓

```
ランダムフォレスト回帰： 0.5438998974098809
ベストパラメータ： {'criterion': 'friedman_mse', 'max_depth': 7, 'n_estimators': 100}
```

少しだけ精度が高くなりました。


# おわりに
まだまだ知識が乏しいなりに、頑張って実装してみました。
改善点や精度を高める方法など、どんなことでも構いませんので、ご指摘していただけると嬉しいです。
最後まで読んでいただきありがとうございます。