自動車の燃費を予測してみた。【signate練習問題】その２



　こちらの記事をご覧いただきありがとうございます。
　↓の続きです。

https://qiita.com/tomyu/items/96546fbf173fa07c9dca

　前回の簡単なまとめをしておきます。
* 燃費、シリンダー、排気量、馬力、重さの5つは密接な関係にある。多分、車の目的に応じて設計されるためだと思われる。
* 年を経るにつれて燃費が向上する。特に70年以降は低燃費化が進められていた背景がある。
* `origin`はメーカーの国。`origin`ごと燃費の差はありそう。メーカーごとの燃費の差は微妙。



## クラスタリングを使ってデータの性質を探る。

　ここから機械学習のモデルを使用した分析を行います。
　回帰予測の問題なので回帰モデルを…の前に、クラスタリングでデータの性質を探してみます。この目的は、先に行ったデータの可視化とおおよそ同じで、データの持つ性質がわかることで後の回帰分析に役立つ可能性があるからです。
※個人的には予測がどうこうよりデータの法則性みたいなものが知れる方が嬉しかったりします。

　モデルによってどういう分類がされるのか検証したかったので、様々なモデルを試します。

### kmeans法

　クラスタリングでは基本的（だと個人的に思っている）なkmeans法から試します。
　と思ったところで、kmeans法だとカテゴリ変数を扱えないことに気が付きました。`cylinders`も`origin`もint型で入っている（そうでなくてもlabel encodingでint型に変換できる）ので実行できなくはないですが、分析結果の解釈が出来なくなります。カテゴリ型を含めて分類できるkmeansの親戚みたいなモデルがあるらしいので、それを後ほど試します。先に、量的変数だけ残してkmeans法を試してみます。

#### 前処理

##### 欠損値処理

　モデルがうまく機能するように前処理します。
　まず、可視化の時点ではなかったことにしていた`horsepower`の`?`の埋め合わせをします。`cylinders`と`model year`が`mpg`に大きく関係ありそうなことから、`cylinders`と`model year`でgroupbyした後の平均値で埋めます。埋めた後に、データ型をfloatに変換します。

```python: 欠損値を埋める
mean_group_cy_ye = df_train_nn.groupby(['cylinders','model year']).mean()
df_train_cy_ye = copy.copy(df_train)
df_train_cy_ye.loc[df_train_cy_ye['horsepower']=='?','horsepower'] = df_train[df_train['horsepower']=='?'].apply(lambda x: mean_group_cy_ye.loc[x.cylinders,x.model_year]['horsepower'], axis=1)
df_train_cy_ye.horsepower = df_train_cy_ye.horsepower.astype(float)
```

```python: 埋めた場所の確認
df_train_cy_ye[df_train['horsepower']=='?'][['cylinders','model_year','horsepower']]
```

__出力結果__
<img width=500 alt='horsepower_cy_ye.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/a9d31d57-d99c-ccc9-9f9e-3861e07662bd.png'>

　※出力結果がpythonのDataFrame出力と異なりますが、画像出力が楽な手段があったのでそれを使いました。画面をスクショしてトリミングして保存して…だと毎回そこそこ手間がかかっていたので、DataFrameを画像として出力する手段を探していたらありました。
　以下で関数として利用できます。
```python:DataFrameを画像にする
# dfは出力したいDataFrame
# wは横幅、hは縦幅
# outputPathは出力先のパス

def TablePlot(df,w,h,outputPath):
    fig, ax = plt.subplots(figsize=(w,h))
    ax.axis('off')
    ax.table(
        df.values,
        colLabels = df.columns,
        loc = 'center',
        bbox=[0,0,1,1]
    )
    plt.savefig(outputPath)
    plt.show()
```

##### 不要な列を削除

　kmeansで利用できる量的変数のみ残るように、不要な列を削除します。`model year`のような重要な変数も落としてしまうので、このkmeansにどれだけの意味があるか怪しくなってきました。

```python:不要な列を削除
feature_names = ['mpg', 'displacement', 'horsepower', 'weight','acceleration']
df_train_km = df_train_cy_ye[feature_names]
```

##### 標準化

　kmeansが適用できるように標準化します。
```python:標準化
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
km_train_ss = ss.fit_transform(df_train_km)
```

　標準化をして、前処理は完了です。


#### モデルで学習

　kmeansでグループ分けをしてみます。グループの数はとりあえず3つにして後で数を変えて試します。

```python:kmeans法
from sklearn.cluster import KMeans
# クラスタ分け
KM = KMeans(n_clusters=3, random_state=0)
KM.fit(km_train_ss)
cluster = KM.predict(km_train_ss)
# dfにクラスタを付け足す
df_train_km3 = copy.copy(df_train_cy_ye)
df_train_km3['cluster'] = cluster
# 散布図を出力
sns.relplot(data=df_train_km3, x='displacement', y='mpg', hue='cluster')
plt.show()
```
__displacement,mpgとクラスター分け__
<img width=300 alt='km3-displacement-cluster.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/72af0a49-b2f6-45f4-4386-2a63ad453513.png'>

　クラスターの違いで色分けをしています。これを見ると、`displacement`の大きさで3つに分けているような印象がありますね。
　ヒストグラムで見ると、より`displacement`の大きさで分けた感が感じられます。

__displacementのヒストグラムとクラスター分け__
<img width=300 alt='km3-displacement-cluster-hist.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/fee672aa-6ad2-c698-cbdf-ae9dcc7ae134.png'>

　`displacement`のところを`horsepower`や`weight`で置き換えてもおおよそ同じ結果が得られました。似たようなデータなんだなーと改めて感じます。

　たしかシリンダーも同じような傾向になるよな？と思ってクラスターと`cylinders`の関係を確認します。
```python:積み上げ棒グラフ
crosstable = pd.crosstab(df_train_km3.cylinders, df_train_km3.cluster, normalize='index')
crosstable.plot.bar(stacked=True)
plt.legend(bbox_to_anchor=(1,1))
plt.show()
```
__cylindersとクラスター__
<img width=400  alt='stackedbar-cylinders-cluster.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/9a494eaf-be9d-711a-856d-58a96093c9ff.png'>

　おおよそシリンダーとクラスター分けが同じになるようですね。kmeansにシリンダーの情報を入れていないので、データの特徴は大雑把に`cylinders`で説明できそうです。


　他にも見てみましたが、可視化で得た情報以上に有益なデータは得られませんでしたので、割愛します。

　クラスタ数を5にして試しました。同じような感じで5層に分かれるんかな…？

__displacement.mpgとクラスター5__
<img width=300 alt='km5-displacement-cluster.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/6ef2b4b4-83e6-64f4-a0ee-52d7150cbeb8.png'>

　うーん、kmeans法がうまく行っていない印象がありますね。5分割もする必要ないんでしょうか？
　と思っていたところで、クラスタ数を考えるための指標になるものを作れることを思い出したのでやってみます。エルボー図というものを出力します。

```python:エルボー図の作成
inertia = []
for i in range(2,22):
    KM = KMeans(n_clusters=i, random_state=0)
    KM.fit(km_train_ss)
    inertia.append(KM.inertia_)
    
plt.plot(inertia)
plt.savefig('./img/kmeans_elbow.png')
plt.show()
```

__エルボー図__

<img width=400 alt='kmeans_elbow.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/53e4fbf9-695a-796f-a257-a3027df93a6c.png'>

　(;^ω^)
　真面目にこんな顔してたと思います。
　エルボー図というのをザックリ説明すると、曲線がカクっと折れているところがクラスタ数としてちょうどよいことを表してくれます。このグラフは滑らかに曲がっているので、どこがちょうどよいのかわからないってことですね。
　言われてみれば確かに、量的変数は(`acceleration`以外)だいたい同じような性質で直線でも表せそうな変化をしていたので、わからんでもないような気がしてきました。
　たぶん、このデータセットにkmeansは適してないのでしょう。


### カテゴリ変数が使える教師なし分類

　kmeansにカテゴリ変数が使えないことに気が付いてから、教師なし分類でカテゴリ変数を利用できる方法を調べました。ネットで紹介されていた手法があったので、それを試します。
　紹介元は↓をご覧ください。

https://www.salesanalytics.co.jp/datascience/datascience067/


　まずデータサンプルを __Gower距離__ 行列に変換します。Gower距離行列とはカテゴリ変数を含めてデータ点間の距離を計算できるものだそうです。話が長くなるので説明は割愛します。私は↓を見て確認したので、良ければそちらもご覧ください。

https://elsur.jpn.org/mt/2019/12/002808.html

　Gower距離行列に変換した後は、ward法などのクラスタリング手法で分類してもらうって感じです。

#### Gower距離行列に変換

##### 前処理

　うまくGower距離行列に変換できるように前処理をします。量的変数は標準化、質的変数はカテゴリ型に変換します。（カテゴリ型は数字でもカテゴリとして扱うらしいです。）

```python:Gower距離変換の前処理
# 量的変数を標準化
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()

df_train_ss = copy.copy(df_train_cy_ye)
quantity_features = ['mpg', 'displacement', 'horsepower', 'weight','acceleration']
df_train_ss[quantity_features] = ss.fit_transform(df_train_ss[quantity_features])
df_train_ss

# 質的変数をカテゴリ型に変換
quality_features = ['cylinders', 'model_year', 'origin', 'car_name']
df_train_ss[quality_features] = df_train_ss[quality_features].astype('category')
```

##### Gower距離に変換

　前処理が済んだので、Gower距離行列に変換します。
```python:
# Gower距離行列に変換
import gower

df_train_gw = copy.copy(df_train_ss)
df_train_gw .drop(['id','car_name'],axis=1)
df_train_gw = gower.gower_matrix(df_train_gw)
```

__Category型は不可__
<img width= alt='カテゴリ変数は対応不可.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/e4cf44f7-fa24-df48-3313-8caba688c2d3.png'>

　カテゴリ型のデータは扱えないらしいです。Gower距離ってカテゴリを扱うためのものではなかったのか…？
　カテゴリ変数をobject型に変換してからGower距離に変換します。

```python:カテゴリ変数はobject
# 質的変数をobject型に変換
quality_features = ['cylinders', 'model_year', 'origin', 'car_name']
df_train_ss[quality_features] = df_train_ss[quality_features].astype('object')

# Gower距離行列に変換
import gower

df_train_gw = copy.copy(df_train_ss)
df_train_gw .drop(['id','car_name'],axis=1)
df_train_gw = gower.gower_matrix(df_train_gw)

```
__変換に成功__
<img width=500 alt='Gower距離行列.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/de3057e1-88be-07e2-5f2b-ba477b81d615.png'>

　いかにも距離行列っぽくなりましたね。これを使ってクラスタリングを試します。


#### Gower距離行列をkmeansする

　距離行列をそのままKMeansモデルにぶち込みます。
```python:Gowerでkmeans
# Gower距離行列でkmeans
from sklearn.cluster import KMeans
KM = KMeans(n_clusters=3,random_state=0)
KM.fit(df_train_gw)
cluster = KM.predict(df_train_gw)
df_train_cy_ye['cluster'] = cluster
```

　これも散布図を出力して性質を確認します。

__displacement,mpgとcluster__
<img width=400 alt='gower_displacement_cluster.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/5e3e8797-f5fa-d2b7-e577-32945ead2fc8.png'>

　…見覚えがありますね。量的変数だけでkmeansしたときとかなり似ています。画像の貼り間違えを3回確認しました。
　確認したら、displacementが低くてmpgが高いデータの数が増えて、中間のデータの数が減っていました。

　どういう性質を持っているかよりわかりやすい可視化があるので、それを貼ります。

__cylindersとcluster__
<img width= alt='gower_cylinders_cluster.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/78d97f0a-ded4-9c47-b46c-fa1d19e23c1e.png'>

　クラスタ分けはほぼほぼシリンダーの分類と同等らしいですね。車の性質はシリンダを見ればだいたいわかる、くらい言えるかもしれません。


　もう一つ、`model_year`と`cluster`の関係を載せます。

__model_yearとcluster__
<img width=400 alt='gower_model_year_cluster.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/c3f7902a-09d4-83ae-0fa4-70b81e7eca20.png'>

　clusterが1のデータは80年以降に存在しないようですね。clusterが1のデータはほぼcylinderが8のデータで、燃費が低い代わりに重くてパワーが高い車です。時代が流れてパワーを出す必要がなくなったということでしょうか。需要の移り変わりを感じますね。

　他の可視化したものも確認しましたが、やはりというべきか量的変数だけでkmeansしたパターンとあまり変わらなかったので割愛します。

##### エルボー図

　ちょうどよいクラスタ数を確認するためにエルボー図を出力します。

<img width=400 alt='gower_kmeans_elbow.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/03c8fb99-6728-5e76-9c34-dc52ad909cd8.png'>

　割となめらかなのはそうだろうなーと思っていましたが、4のところだけ若干折れている感が見て取れます。クラスタ数4がちょうどよさそうです。

#### クラスタ数4でkmeans

　早速図を見てみましょう。見やすい配色に変えられる手法を発見したので、色が変わっています。

__displacement,mpgとcluster__
<img width= alt='gower4_displacement_mpg_cluster.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/6fb828c4-f31b-8787-d9a7-689b33dc4fab.png'>
　クラスタ数3の時に`displacement`が低かったグループが2つに分かれた印象があります。ただ、その別れ方は`displacement`や`mpg`とはあまり関係なさそうです。
　他の図も確認します。

__horsepower,mpgとcluster__
<img width=400 alt='gower4_horsepower_mpg_cluster.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/20f52ad1-05e1-06f1-3c06-19915ebd3767.png'>

__weight,mpgとcluster__
<img width=400 alt='gower4_weight_mpg_cluster.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/1e4094ce-dba2-2e8b-f8aa-f1915731b179.png'>
　`horsepower`や`weight`は`displacement`と同じような傾向になりますね。これらもあまり関係なさそうです。

__acceleration,mpgとcluster__
<img width=400 alt='gower4_acceleration_mpg_cluster.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/788bc44f-bc3c-d7d5-1fa8-e9d0d80cfeac.png'>
　あまり注目してこなかった`accerelation`ですが、ここでもあまり関係はなさそうです。

__cylindersとcluster__
<img width=400 alt='gower4_cylinders_cluster.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/f1b8f48e-f7e8-c89d-74ed-d2ac7ee77655.png'>

　`cylinders`が4のデータにしか新しいクラスタはいないようです。燃費の良いグループが分かれていたので、`cylinders`が4しかいないのもわかります。

__model_yearとcluster__
<img width=400 alt='gower4_model_year_cluster.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/0bcf7771-abed-333f-93cb-d3e1030fdd86.png'>
　新しいクラスタは71年から少しづつ存在していて、82年にかなり数を増やしたみたいです。しかしこれだけだと年代と関係が深いとは言いにくい気がします。

__originとcluster__
<img width=400 alt='gower4_origin_cluster.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/fd43d5bc-9436-f533-47c3-17dae88e5f9e.png'>
　新しいクラスタが何なのかこれでよくわかりますね。`cylinders`が4でかつ`origin`が1なら新しいクラスタに分類されているのでしょう。言い換えれば、パワーが控え目な代わりに燃費が良く、かつアメリカ産の車ということです。なんでアメリカだけ切り分けたのか？という疑問が浮かびましたが、アメリカは燃費の良い悪いの幅が広いからじゃないでしょうか。多分。
　それと、アメリカは82年（81?）に燃費の良い車が多く生産されたようなことも感じます。時代の流れですね。

 `cylinders`と`origin`で切り分け始めたので、クラスタ数を増やしたら日本やヨーロッパも分かれるのか？と思ってクラスタ数を増やして試しました。

__originと5つのcluster__
<img width=400 alt='gower_km5_origin_cluster.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/c514bdee-64d3-6bbc-445d-a9bafdd94bc2.png'>

　クラスタ数4つの時と比較して、`origin`の2と3が色分けされたようです。想定通り、日本とヨーロッパを区別してくれました。

　…ところで、`mpg`に関係なく`origin`で区別するなら`mpg`の予測にあまり関係ないのでは？と思い始めてしまったので、kmeansはこの辺にしておきます。


# 次回に続きます。

　今回はクラスタリング全般を載せて次回から回帰モデルで予測しようと思っていましたが、kmeansだけで結構長くなってしまったのでここまでにします。次こそクラスタリングを終わりにします。
　ここまでご覧いただいた皆様、ありがとうございました。

　→次回が完成しました。ぜひこちらもご覧ください。

https://qiita.com/tomyu/items/7776eaccb39659d644b1