Python 機械学習で中古車価格予測作ってみた

####１.はじめに
自動車整備士から、データサイエンティストへの転職を目指し勉強中のサラリーマンです。
今回はAidemyのデータ分析講座（６ヶ月コース）で学んだことの集大成として、本記事を投稿したいと思います。
改善点等多数あると思います。コメントでご教授いただけると幸いです:relaxed:

####2.モデルテーマを『中古車価格予測』に選んだ理由
コロナウイルス流行の影響で、密を避けられる自動車の需要が増えています。
しかし、現在世界的な半導体不足で新車の供給が遅れているのが現状であり、すぐに入手できる中古車の価格が高騰しています。
そこで、中古車価格を決定する特徴が分かればお得な中古車が見つけやすいと思ったからです。

ここで、自動車業界で得た知識から、仮説を立ててみたいと思います。

仮説：自動車の価格は、年式、走行距離、排気量、モデルの人気度によって決まる。

※もっと細かく言えば、傷の有無や残車検年数なども関係してきますが今回使用したデータセットの関係上、上記の仮説とします。

####３.データ分析
ここから実際にモデルの作成〜データの分析を行なっていきます。
全体の流れ
⑴データの取得
⑵モデル作成
⑶作成したモデルを使用し簡易的な価格予測
言語　Python 環境　Google Colaboratory

⑴データの取得
今回使用するデータセットはKaggleから引用しました。
Kaggle:https://www.kaggle.com/adityadesai13/used-car-dataset-ford-and-mercedes?select=vw.csv
活用したデータはイギリス市場で、ブランドはAudi、TOYOTA
、Hyundi、Fordの4つに絞っています。

⑵モデル作成#ローカル環境からデータをアップロードできるようにする

```python
#ローカル環境からデータをアップロードできるようにする
from google.colab import files
uploaded = files.upload()
```
上記のライブラリを活用することでデスクトップなどのローカル環境からのデータ使用できるようになります。
必要なライブラリをインポートします。

```python
#使用するライブラリのインポート
import pandas as pd #データ処理
import numpy as np #数列計算
import matplotlib.pyplot as plt #データの可視化
import seaborn as sns #データの可視化
import io #データのインポート
```
pandasを使いデータを成形します。

```python
df_car = pd.read_csv(io.StringIO(uploaded['used_car.csv'].decode('utf-8')), header=1)
df_car.columns = ['brand', 'model', 'year', 'price',
                   'transmission', 'kilometers', 'fuelType',
                   'tax', 'fuelEconomy', 'engineSize']
                   
#データ内容の確認
df_car.info()
# データ形状の確認
print('データ形状：', df_car.shape)
# 欠損値の確認
print('欠損値の数：{}\n'.format(df_car.isnull().sum().sum()))
#統計量の表示
df_car.describe()
```

![スクリーンショット 2022-01-19 7.55.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2511224/baf595cc-dfad-01b8-a5c8-9c5f835a8ec6.png)

![スクリーンショット 2022-01-19 7.56.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2511224/cfc7507e-ed84-1883-c3ce-c59fa9f4ab5c.png)
columnsの解説 
kilometers:走行距離（マイル表記を×1.6でkm換算しています）
fuelEconomy:燃費（１マイル/１ガロンを×0.42でkm/Lに換算しています）
engineSize:排気量

欠損値はないですが、気になる値がいくつかあるので確認していきます。

#####燃費107km/L

```python
#燃費(fuelEconomy)が１０７が正しいのか確認する
df_car[df_car.fuelEconomy >= 100]
```
![スクリーンショット 2022-01-19 8.06.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2511224/b10c613c-14d3-93fd-b47d-8deb3787e26d.png)
fuelType(燃料）がハイブリッド（調べてみると実際は電気自動車？）なのであり得なくはないため今回はそのままにしておきます。

#####年式2060年

```python
#外れ値2060年式のインデックスを取得
df_car[df_car.year >= 2021]
```
![スクリーンショット 2022-01-19 8.08.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2511224/019f37ef-5362-8a29-00f5-59ce371daa86.png)
2022年より先の車は外れ値として削除する。

```python
#外れ値の削除と確認
df_car2=df_car.drop(39991)
df_car2.query('year == 2060')
```
![スクリーンショット 2022-01-19 8.19.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2511224/e93cc023-f127-7d5e-77d5-091cb6c3fa4c.png)
削除できました。
#####排気量0

```python
#エンジンサイズ=0の車を確認する
df_car2[df_car2.engineSize == 0]
```
![スクリーンショット 2022-01-20 8.58.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2511224/d902590e-e9f7-ff43-de67-c628ff724a9a.png)
電気自動車ではない車が排気量０はおかしいと判断して削除する。

```python
#エンジンサイズ＝０のデータの削除
df_car3 =df_car2.drop(df_car2.index[df_car2['engineSize'] == 0])
df_car3.query('engineSize == 0')
```
![スクリーンショット 2022-01-19 8.08.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2511224/156e469f-7ed9-d3cb-a854-cb679c82b51a.png)
削除できました。

データの成形が完了したので各値の相関関係を確認していきます。
![ヒートマップ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2511224/7db1d253-a4ea-2e79-bdb8-c8f67989f548.png)
今回は結果が一目でわかりやすいヒートマップを使用しました。
相関係数が黒色に近づくほど負の相関、ベージュに近づくほど正の相関が強いと言えます。

今回price(価格）との相関係数が±０．４以下のtax(税金）は今回除外します。
※走行距離は例外とします。

```python
#priceとの相関係数の絶対値が0.4以下であるtaxは今回除外する（kilomatersは例外とする）
df_car4 = df_car3.drop('tax', axis=1)
```
次に各値の相関関係を可視化します。

```python
#各値の相関関係をグラフで確認
sns.pairplot(df_car4) 
```
![散布図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2511224/b8cd0fd4-804a-37be-71a1-53f98f8d59db.png)
グラフより言えることは
・年式が新しい車ほど価格が高い
・走行距離の少ない車ほど価格が高い
・燃費の良い車ほど価格が高い？
（２０〜３０km/L付近の価格が高く、実際にも燃費が良いと言われる数字なので問題なさそう）
・排気量の大きい車ほど価格が高い

ここから実際に予測モデルの作成に入っていきます。
今回は重回帰分析、ランダムフォレスト、LightGBMの３つでモデルを作成し実際に１番精度の良かったものを最終的なモデルとします。

文字列はモデル作成にそのまま使えないのでダミー変数に変換します。

```python
#文字列をダミー変数に変換
df_car5 = pd.get_dummies(df_car4, columns = ['brand', 'model', 'transmission', 'fuelType'])
df_car5
```
![スクリーンショット 2022-01-20 9.23.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2511224/e6a71902-00ce-5372-60a0-34e2bb293f95.png)
目的変数：y、説明変数：xを設定します。

```python
#目的変数(price(y))と説明変数(year,kilometers等(x))を設定
y = df_car5['price']
x = df_car5.drop(['price'], axis =1)
```

ホールドアウト法を用いて訓練データと検証データに分割します。

```python
#訓練データと検証データに分割(ホールドアウト法)
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size = 0.3, random_state = 1)
```
#####重回帰分析

```python
#重回帰分析
from sklearn.linear_model import LinearRegression
#モデルの宣言
model = LinearRegression()
#モデルの学習
model.fit(x_train, y_train)
#訓練の実施
model.score(x_train, y_train)
#テストの実施
model.score(x_test, y_test)
```
0.8844279055992721
#####ランダムフォレスト
```python
#ランダムフォレスト
from sklearn.ensemble import RandomForestRegressor
#モデルの宣言
model2 = RandomForestRegressor(max_depth = 10, n_estimators = 10)
#モデルの学習
model2.fit(x_train, y_train)
#訓練の実施
model2.score(x_train, y_train)
#テストの実施
model2.score(x_test, y_test)
```
0.947962168622708
#####LightGBM
```python
#LightGBM
from sklearn.metrics import mean_squared_error # モデル評価用(平均二乗誤差)
from sklearn.metrics import r2_score # モデル評価用(決定係数)
import lightgbm as lgb
#df_car3.copy()
y = df_car4["price"]
#LIGHTGBMはカテゴリ値も使えるので・・・objectタイプをカテゴリ値へ変換
df_car4['brand']=df_car4['brand'].astype('category')
df_car4['model']=df_car4['model'].astype('category')
df_car4['transmission']=df_car4['transmission'].astype('category')
df_car4['fuelType']=df_car4['fuelType'].astype('category')

x = df_car4.drop(['price'], axis=1) 

x_train, x_test, y_train, y_test = train_test_split(x, y,test_size=0.2, random_state=0)
x_train, x_valid, y_train, y_valid = train_test_split(x_train, y_train,test_size=0.2, random_state=0)

lgb_train = lgb.Dataset(x_train, y_train)
lgb_eval = lgb.Dataset(x_valid, y_valid, reference=lgb_train)
#パラメーターの設定
params = {
    'boosting_type': 'gbdt',
    'objective': 'regression',
    'metric': {'l2', 'l1'},
    'num_leaves': 50,
    'learning_rate': 0.05,
    'feature_fraction': 0.9,
    'bagging_fraction': 0.8,
    'bagging_freq': 5,
    'vervose': 0
}

model3 = lgb.train(params,
                lgb_train,
                num_boost_round=200,
                valid_sets=lgb_eval,
                early_stopping_rounds=50
               )

y_pred = model3.predict(x_test, num_iteration=model3.best_iteration)
# モデルの評価
r2 = r2_score(y_test,y_pred)
r2
```
0.9694343531078808

１番精度の良かったLightGBMを採用し、最終的なモデルを作成します。
今回はoptunaを用いて、パラメーターチューニングを行いさらに精度を上げます。

```python
#一番精度の良かったLightGBMを使用　パラメーターチューニング
import optuna.integration.lightgbm as lgb_1

trains = lgb_1.Dataset(x_train, y_train)
valids = lgb_1.Dataset(x_valid, y_valid, reference=trains)

 # ベストなパラメータ、途中経過を保存する
params = {
    'objective': 'mean_squared_error',
    'metric': 'mae',
    "verbosity": -1,
    "boosting_type": "gbdt",
}

best_params, history = {}, []

# LightGBM学習
model4 = lgb_1.train(params,
                trains,
                num_boost_round=200,
                valid_sets=[trains,valids],
                early_stopping_rounds=50
               )

best_params = model4.params
best_params
```
0.9715471868641283
無事精度を上げることができました。

モデルを使用した結果の散布図を見てみます。x軸：実価格　y軸：予測値
![西海散布図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2511224/cc935c96-9d05-2321-2a7f-d8a3b8061d38.png)
大きなばらつきもなく直線を描くことができました。

次に、予測値と実価格との差、差の変動率を見てみます。

```python
#予測値と実価格との差、変動率を計算
df_for_search = df_car4.copy()
pred = pd.Series(y_pred,index=y_test.index, name="予測値")
df_for_search = pd.merge(df_for_search, pd.DataFrame(pred),left_index=True, right_index=True)
df_for_search['予測値との差'] = df_for_search['price']-df_for_search['予測値']
df_for_search['変動率']       = df_for_search['予測値との差']/df_for_search['price']*100
df_for_search = df_for_search[['brand','model','year','予測値','price','予測値との差','変動率']]
df_for_search[df_for_search['model'] == 'A3']
```
AudiのA3で確認してみます。
![スクリーンショット 2022-01-20 11.22.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2511224/160fde54-027f-69d6-6bc0-e419e4518223.png)
変動率が正で大きいほど割高(価値が落ちにくい)、逆に負で小さいほど割安(価値が落ちやすい)ということです。
年式が新しいのに変動率がマイナスなのは、競合車種の人気や傷の有無など今回用意したデータセットからは読み取れないことが原因としてありそうです。
統計量も見てみます。

```python
#統計量の表示
df_for_search.describe()
```
![スクリーンショット 2022-01-20 11.21.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2511224/8a689a41-be7a-2dd5-112f-c312a1418c9c.png)

1番高価な車を見てみます。

```python
#一番高価な車を表示
df_for_search[df_for_search.price == 137500]
```
![スクリーンショット 2022-01-20 11.01.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2511224/97236050-2665-853d-f735-45c17b532e2a.png)
AudiのスポーツカーR8でした:race_car:
高級スポーツカーは価値が落ちにくいようです。

最後に実際に値を入力して価格予測をしてみます。

```python
mport math

a = input('ブランドを入力（Audi, TOYOTA, Ford, Hyundi)：')
b = input('モデルを入力：')
c = int(input('年式を入力：'))
d = input('ミッションタイプを入力（Automatic, Manual, Semi-Auto)：')
e = float(input('距離を入力：'))
f = input('燃料を入力(Petrol(ガソリン）, Diesel(軽油), Hybrid(ハイブリッド), Electric(電気),　Other(その他）：')
g = float(input('燃費を入力：'))
h = float(input('排気量を入力：'))

data = [[a, b, c, d, e, f, g, h]]

x_test1 = pd.DataFrame(data)
x_test1.columns = ['brand', 'model', 'year',
                   'transmission', 'kilometers', 'fuelType',
                   'fuelEconomy', 'engineSize']

x_test1['brand']=x_test1['brand'].astype('category')
x_test1['model']=x_test1['model'].astype('category')
x_test1['transmission']=x_test1['transmission'].astype('category')
x_test1['fuelType']=x_test1['fuelType'].astype('category')

y_pred = model4.predict(x_test1, num_iteration=model4.best_iteration)

#結果がユーロで出力されるため円に変換
print(f'{math.floor(y_pred*130)}円の予測金額です。')
```
※2022年1月20日の為替レート、１ユーロ130円で計算しています。
ブランドを入力（Audi, TOYOTA, Ford, Hyundi)：Audi
モデルを入力：A3
年式を入力：2019
ミッションタイプを入力（Automatic, Manual, Semi-Auto)：Automatic
距離を入力：20
燃料を入力(Petrol(ガソリン）, Diesel(軽油), Hybrid(ハイブリッド), Electric(電気),　Other(その他）：Petrol
燃費を入力：25
排気量を入力：1.4
3379074円の予測金額です。

####4.まとめと今後の課題
今回はKaggleのデータセットを用いてモデルを作成しました。
今後は中古車サイトより、傷の有無やグレード、残車検などをスクレイピングで取得し予測モデルの精度を上げたいと思います。
また、Kaggleのコンペに参加し機械学習の技術、知識の向上を図りたいと思います。

最後までご愛読いただきありがとうございました。

参考文献/サイト
・データセット
https://www.kaggle.com/adityadesai13/used-car-dataset-ford-and-mercedes
・モデル作成の考え方
https://qiita.com/syabutarou0808/items/f9248e8a86d0a5aedc34
https://www.youtube.com/watch?v=rft5M8cksvA
・パラメーターのチューニング
https://qiita.com/DS27/items/ec6a747977e57bb1837e