2021-05-05

中古車相場をAI(機械学習)で分析する方法 ~教師あり機械学習(回帰)のコードを惜しまず公開~

というわけで、GWの連休中に何とかアップしようと思っていながらの最終日。。(明日・明後日は通常勤務です。)
夏休みの終わり際に宿題をあくせくやるような気分ですw

そんなこんなで、先日公開した中古車相場をAIで分析してみた件の実際のコードを公開してみます。
ちなみに、JupyterNotebookで書いてあります。


まずは、共通事前処理と称して、前データの処理に必要なものをごそっとimportしてしまいます。
# 共通事前処理

# 余計なワーニングを非表示にする
import warnings
warnings.filterwarnings('ignore')

# ライブラリのimport
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime

# matplotlib日本語化対応
import japanize_matplotlib

# データフレーム表示用関数
from IPython.display import display

# 表示オプション調整
# Numpyの浮動小数点の表示精度
np.set_printoptions(suppress=True, precision=4)
# pandasでの浮動小数点の表示精度
pd.options.display.float_format = '{:.1f}'.format
# データフレームですべての項目を表示
pd.set_option("display.max_columns", None)


続いて、データを読み込んでデータフレーム化します。
データは、2021/5/4現在のカーセンサーに出ている BMW 3シリーズ ツーリング (F31) 320d Mスポーツ を使います。
# データフレーム化
df1 = pd.read_csv('https://raw.githubusercontent.com/Oshiruko-TsubuAn/sample_usedcar_data/main/F31_320dMsp(UTF-8).csv', index_col=0)

# 確認
display(df1.head())こ
※CSVファイル名、F30って書いてありますけど、実際はF31のデータです。直すのもめんどいので、このままでご容赦くださいませm(_ _)m
⇒ やっぱり直しました!


df1の先頭5行を表示した結果です。
year_odopriceinscolor
02017340003180000車検整備付黒M
12016350002880000車検整備付
220163100031800002021-11-01
320155700022800002022-03-01黒M
420155200023980002022-03-01

元データはCSV形式にしてこちらにも置いてあります。
(エンコードに関して、UTF-8バージョンの使用をお勧めしますが、Windows用にcp932も一応用意しておきます。)


続いては、データ前処理その1です。
車検(ins列)を車検残月数にし、色(color列)の表記ゆれをなくします。
# データ前処理1
df2 = df1.copy()

# 車検なしのものを当月に置換
today = datetime.today()
this_month = today.strftime('%Y-%m-01')
df2 = df2.replace({'ins': {'車検整備付': this_month, '車検整備無': this_month, '車検整備別': this_month}})

# 車検までの残月数
thism_type_d = datetime.strptime(this_month, '%Y-%m-%d')
df2['ins'] = pd.to_datetime(df2['ins'])
df2['this_month'] = thism_type_d
df2['ins_left_days'] = (df2['ins'] - df2['this_month']).dt.days
df2.ins_left_days[df2.ins_left_days < 0] = 0     # マイナスのものを0に置換
df2['ins_left'] = df2['ins_left_days'] / 30.4375

# 色の表記ゆれを修正
df2['color'] = df2['color'].str.replace('M', '')
df2['color'] = df2['color'].str.replace('M', '')
df2['color'] = df2['color'].str.replace('真珠', '白')
df2['color'] = df2['color'].str.replace('薄', '')
df2['color'] = df2['color'].str.replace('I', '')
df2['color'] = df2['color'].str.replace('深', '')
df2 = df2.replace({'color': {'(.)(.*)': r'\1'}}, regex=True)

# 確認
display(df2.head())

前処理1を終えたdf2の、先頭5行を表示した結果です。
year_odopriceinscolorthis_monthins_left_daysins_left
020173400031800002021-05-012021-05-0100.0
120163500028800002021-05-012021-05-0100.0
220163100031800002021-11-012021-05-011846.0
320155700022800002022-03-012021-05-0130410.0
420155200023980002022-03-012021-05-0130410.0

ちなみに車検(ins列)については、カーセンサーに表示されている月の1日としてあり、それと現時点の1日(例.2021/5/1)との日付差を30.4375(閏年含めた月の平均日数)で割って少数を切り捨てた値を、便宜的に車検残月数(ins_left列)としています。
this_month列やins_left_days列は、計算のために設けたのみです(データフレームの日付計算は型の扱いが面倒)。
また、新規登録した際の車検日のまま更新を続けているため、現時点から見ると過去の日付(例.2021/3/1)となっているデータもありますが、それについては日付計算でマイナスとなったものを0に置換してます。


ここらで、データの確認をしてみます。
print('行数, 列数:', df2.shape)
print('\n色別の台数')
print(df2['color'].value_counts())
display(df2.describe())
df2.hist(figsize=(8,6), bins=20)

結果はこんな感じです。
>> 行数, 列数: (125, 8)

>> 色別の台数
>>79
>>31
>>9
>>3
>>3
>> Name: color, dtype: int64

year_odopriceins_left_daysins_left
count125.0125.0125.0125.0125.0
mean2015.148800.02415848.0175.25.8
std1.631463.0642368.6191.16.3
min2012.02000.0792000.00.00.0
25%2014.029000.01888000.00.00.0
50%2015.041000.02498000.0123.04.0
75%2016.061000.02930000.0335.011.0
max2018.0182000.03880000.0641.021.1

対象台数は125台で、ボディカラーは白が6割以上を占めています。
表にある統計用語の意味ですが、meanは平均値、stdは標準偏差、minは最小値、25%は25%タイル、50%は50%タイル(中央値)、75%は75%タイル、maxは最大値となっています。
この一覧をたったの1行  display(df2.describe())  で出せるのが、pandasの素晴らしいところ。

そして、  df2.hist(figsize=(8,6), bins=20)  で、ヒストグラムも簡易表示できちゃいます。


データ前処理第2弾は、ボディカラーという非数値データを数値データに置き換える処理です。
ついでに、不要な列(ins列ほか)も削除します。
※ここで使っている関数(enc関数)は、マイバイブル「Pythonで儲かるAIを作る」に載っていたものです。
# データ前処理2 (One-Hotエンコーディング)

# One-Hotエンコーディング用の関数
def enc(df, column):
    df_dummy = pd.get_dummies(df[column], prefix=column)
    df = pd.concat([df.drop([column], axis=1), df_dummy], axis=1)
    return df

# colorをOne-Hotエンコーディング
df3 = enc(df2, 'color')

# 不要列をドロップ
df3 = df3.drop(['ins'], axis=1)
df3 = df3.drop(['ins_left_days'], axis=1)
df3 = df3.drop(['this_month'], axis=1)

# 確認
display(df3.head())

こうなります。
year_odopriceins_leftcolor_白color_赤color_銀color_青color_黒
020173400031800000.000001
120163500028800000.000001
220163100031800006.000001
3201557000228000010.000001
4201552000239800010.010000

これで、データ前処理が終わりました。


今回行うのは「教師あり機械学習(回帰)」ですので、前処理を終えたデータを正解データと検証データに分けます。
# 入力データと正解データの分割
x = df3.drop('price', axis=1)
y = df3['price'].values

# 正解データと検証データの分割
test_size = 0.2

from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=test_size, random_state=random_seed)


分けたデータを用いて、2つの機械学習プログラム(ロジスティック回帰とXGBRegressor)にいっぺんにかけてしまいます。
ここでは、訓練データによる訓練と正解データによる評価までを行っています。
# アルゴリスムの選択
from sklearn.metrics import r2_score

# ロジスティック回帰
from sklearn.linear_model import LogisticRegression
algorithm_LR = LogisticRegression(random_state=random_seed)
algorithm_LR.fit(x_train, y_train)
ypred_LR = algorithm_LR.predict(x_test)
score_LR = r2_score(y_test, ypred_LR)
print(f'score LogisticRegression: {score_LR}')

# XGBRegressor
from xgboost import XGBRegressor
algorithm_XGBR = XGBRegressor(random_state=random_seed)
algorithm_XGBR.fit(x_train, y_train)
ypred_XGBR = algorithm_XGBR.predict(x_test)
score_XGBR = r2_score(y_test, ypred_XGBR)
print(f'score XGBRegressor: {score_XGBR}')

それぞれのスコアは、こうなりました。
>> score LogisticRegression: 0.492272185504524
>> score XGBRegressor: 0.6692589579514605

前回もそうでしたが、XGBRegressorの方がいいスコアが出ますね。
というわけで、今回もXGBRegressorを採用します。


本チャンの前に、重要度も見ておきましょう。
# 重要度分析
import xgboost as xgb
fig, ax = plt.subplots(figsize=(8,6))
xgb.plot_importance(algorithm_XGBR, ax=ax, height=0.8, importance_type='gain', show_values=False, title='重要度分析(価格)')
plt.show()

前回も感じたことですが、車検の有無というか残り月数って、それほど重要視されていませんね。


最後の段階になります。
価格予測するには、機械学習させたデータと同じ構造のデータを与えなければなりませんが、一々それを作るのが面倒なので、その元を作るコードを書いておきました。
# 価格予測用の辞書の元
k = df3.columns.values
v = [0 for i in range(len(k))]
dic_sample = {k: v for k, v in zip(k, v)}
print(dic_sample)

こんな感じで出力されますので、これを編集して、次で用意するコードに使いまわします。
>> {'year_': 0, 'odo': 0, 'price': 0, 'ins_left': 0, 'color_白': 0, 'color_赤': 0, 'color_銀': 0, 'color_青': 0, 'color_黒': 0}


とりあえず、2018年式・走行距離1万km・車検なし・白といった条件で学習済みのXGBRegressorにかけてみます。
# 価格予測
dic = {'year_': 2018, 'odo': 10000, 'ins_left': 0, 'color_白': 1, 'color_赤': 0, 'color_銀': 0, 'color_青': 0, 'color_黒': 0}
df_pred = pd.DataFrame([dic])

XGB_pred = algorithm_XGBR.predict(df_pred)
print(f'price: {XGB_pred}')

結果はこちらです。
>> price: [3142447.]


ちょっと安いかな~って気がしなくもないのですが、まあいい線いってますね。
また、スコアが0.67程度でまあまあの値でしたので、こんなものなのかもですね。


というわけで、中古車相場をAIで分析してみた件の実際のコードを公開してみました。
何とか連休中に書けてよかったですw

なお、PythonやJupyterNotebookがない方向けに、ブラウザ上でコードやその動きが確認できる Google Colab にもアップしておきましたので、よかったら確認してみてください。
 Google Colab はPCからしかできないようです。スマホの場合は、 GitHub からプレビューでの確認が可能です。


んでは!





0 件のコメント :

コメントを投稿