scikit-learnの回帰分析モデルを利用してお店のチップ額を予測する

## はじめに
### 想定読者
回帰分析による予測を、とにかくイージーに実装してみたい、という初学者。
+αで、予測元のデータに文字列等の非数値データ（カテゴリデータ）が存在していて困っている、という方向けに対応方法も解説しています。

### この記事を読んでできること
ある店で食事をした際の、妥当なチップ額を予測する回帰分析モデルが作成できる
※予測材料となる情報は以下の６つ
total_bill : 総支払額(食事代、税込み)　(USドル)
sex : 性別
smoker : 喫煙者か否か
day : 曜日(木・金・土・日のいずれか)
time : 食事の時間(昼食か夕食か)
size : 人数
また、学習用のデータは以下を利用する。
https://github.com/mwaskom/seaborn-data/blob/master/tips.csv

### 前提
VSCode
PoweShell
Python
の基本操作ができること


### 回帰分析とは…？
（知っている人は読み飛ばしておｋ）
>回帰分析とは，乱暴にいってしまえば，複数の変数間の関係を一次方程式（Y=aX+b）の形で表現する分析方法です．
>/~中略~/
>予測したい変数のことを目的変数（または被説明変数）といい，目的変数を説明する変数のことを説明変数（または独立変数）と呼びます．目的変数は1つですが，説明変数の数はいくつでもよく，説明変数が2つ以上の時は重回帰，1つのとき特に単回帰と呼びます．また，求められた一次方程式を回帰式と呼ぶこともあります。
>(引用元）http://www.aoni.waseda.jp/abek/document/regression-1.html

➡今回でいえば、説明変数が[total_bill, sex, smoker, day, time, size]の6つ、目的変数が[tip(チップ額)]の重回帰分析ということになります。

## 実装
最後に実装したコードの全体像を載せています。

:::note
実装の流れ
①ライブラリのインストール
②Pythonファイル作成
③インポート記述
④学習用データの取得
⑤非数値データの加工
⑥学習用データを説明/目的変数に分割
⑦テスト用データの切り出し
⑧モデルのインスタンス化
⑨モデルの学習
⑩学習済みモデルの精度計測
:::
***
### ライブラリのインストール
**scikit-learn：**
回帰分析に限らず、機械学習に必要なモデル、関数等を提供
```
$pip install scikit-learn
```
**numpy：**
結果出力時に、数値を整形する
```
$pip install numpyc
```
**pandas：**
予測に使用するデータを整形する
```
$pip install pandas
```
**seaborn：**
今回の学習用データを提供
```
$pip install seaborn
```

### Pythonファイル作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480554/b720457c-e6fe-4f66-bdd9-8916ea31bbcb.png)

### インポート記述
```py:predictTip.py
import numpy as np
import pandas as pd
import seaborn as sns
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
# Lesso回帰を使用（重要でない説明変数を勝手に間引いてくれるモデル）
from sklearn.linear_model import Lasso
```

### 学習用データの取得
```py:predictTip.py
tips = sns.load_dataset('tips')

#中身を確認してみる
print(tips)
```
**出力結果**
```
     total_bill   tip     sex smoker   day    time  size
0         16.99  1.01  Female     No   Sun  Dinner     2
1         10.34  1.66    Male     No   Sun  Dinner     3
2         21.01  3.50    Male     No   Sun  Dinner     3
3         23.68  3.31    Male     No   Sun  Dinner     2
4         24.59  3.61  Female     No   Sun  Dinner     4
..          ...   ...     ...    ...   ...     ...   ...
239       29.03  5.92    Male     No   Sat  Dinner     3
240       27.18  2.00  Female    Yes   Sat  Dinner     2
241       22.67  2.00    Male    Yes   Sat  Dinner     2
242       17.82  1.75    Male     No   Sat  Dinner     2
243       18.78  3.00  Female     No  Thur  Dinner     2

[244 rows x 7 columns]

```
※学習用のデータのため、tipの列に目的変数であるチップ額が格納されています。
　これがないと、モデルは答え合わせが出来ず学習や精度計測をすることができません。

### 非数値データの加工
回帰分析の学習に使うデータはすべて数値データでなければならない、という決まりがあります。
今回でいえば、
sex,smoker,day,timeの４つが非数値データ（カテゴリデータ）のため、これらのデータを数値化する必要があります。
また、これらのカテゴリデータは以下のように更に2つに分類できます。

**A：値が2種類だけのカテゴリデータ**
今回でいえば、性別[男/女]、喫煙者[YES/No]、時間帯[Dinner/Launch]

**B：値が3種類以上のカテゴリデータ**
今回でいえば、曜日[木～日]

Aの場合、数値化の方法は単純で、ただ２つの値を「０or１」に置き換える（＝Label Encoding）だけです。
Bの場合は、少し複雑になり、手順も代表的なもので４種類あります。
***
**値が３つ以上のカテゴリデータの数値化方法４選**

**①One hot Encoding (=ダミー変数化)：**
値の種類の数ー1個分だけ、新しい変数（列）を作成して、該当すれば1、該当しなければ0を入れる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480554/7d9ae85c-4fa9-a29c-68f8-cb8fb280c30b.png)


**②Label Encoding：**
単純に各カテゴリに対して数値Labelを割り振る。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480554/cbe30fec-c638-d2e1-6cb7-7b967898d269.png)


**③Count Encoding：**
各カテゴリのデータでの登場回数をそのカテゴリの数値として割り振る。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480554/42157617-bc3a-3ef8-2c08-6c3c8176dc0c.png)


**T④arget Encoding：**
カテゴリごとに目的変数の平均をそのカテゴリの数値として割り振る。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480554/b0d5d186-8ae0-4fa3-0a9c-4295848bf5eb.png)
***




sex,smoker,time値が二種類しかないカテゴリデータのため、Label Encodingで数値化（０or１に変換）を行います。
Label Encodingによるデータの数値化は以下のコードで行われます。
```py:predictTip.py
#sklearn.preprocessingからimportしていたLabelEncoderをインスタンス化
labelEncoder = LabelEncoder()

#tips['数値化したい変数名'] = labelEncoder.fit_transform(tips['数値化したい変数名'])
# 性別の数値化
tips['sex'] = labelEncoder.fit_transform(tips['sex'])
# 喫煙有無の数値化
tips['smoker'] = labelEncoder.fit_transform(tips['smoker'])
# 食事時間の数値化
tips['time'] = labelEncoder.fit_transform(tips['time'])
```
dayは上で紹介した４つの手法のうち、Target Encodingを採用して数値化を行います。
Target Encodingによるデータの数値化は以下のコードで行われます。
```py:predictTip.py
day_mapping = tips[['day', 'tip']].groupby(['day'])['tip'].mean().to_dict()
tips['day'] = tips['day'].map(day_mapping)

#カテゴリデータ数値化後のデータの状態を確認してみます。
print(tips)
```
**出力結果**
```
     total_bill   tip  sex  smoker       day  time  size
0         16.99  1.01    0       0  3.255132     0     2
1         10.34  1.66    1       0  3.255132     0     3
2         21.01  3.50    1       0  3.255132     0     3
3         23.68  3.31    1       0  3.255132     0     2
4         24.59  3.61    0       0  3.255132     0     4
..          ...   ...  ...     ...       ...   ...   ...
239       29.03  5.92    1       0  2.993103     0     3
240       27.18  2.00    0       1  2.993103     0     2
241       22.67  2.00    1       1  2.993103     0     2
242       17.82  1.75    1       0  2.993103     0     2
243       18.78  3.00    0       0  2.771452     0     2
```

### 学習用データを説明/目的変数に分割
```py:predictTip.py
#tipの中身を説明変数(チップ額以外)と目的変数(チップ額)に分けて格納
tip_features = tips.drop(['tip'], axis=1)
tip_target = tips['tip']
```
### テスト用データの切り出し
※test_sizeで、テスト用に切り出すデータの割合を指定できる。今回は２割にした。
```py:predictTip.py
#説明変数・価格変数をそれぞれトレーニング(学習)用と、テスト用のデータに分ける
#今回はトレーニング用：テスト用＝ 8 : 2 の割合
Feature_train, Feature_test, Target_train, Target_test = train_test_split(tip_features, tip_target, test_size=0.2, random_state=42)
```
※random_state=42はお作法。なぜ42なのかは以下記事に書いてあります。
https://qiita.com/ofutonton/items/8cb88b34375bf473e45e

### モデルのインスタンス化
```py:predictTip.py
lasso = Lasso()
```

### モデルの学習
インスタンス化したモデルのfit関数に、トレーニングデータの説明変数と目的変数を引数に指定することで、モデルが勝手に学習してくれる。
```py:predictTip.py
#インスタンス化したモデル.fit(トレーニングデータの説明変数, 目的変数)
lasso.fit(Feature_train, Target_train)
```

### テスト用データを突っ込んで予測＋精度を計測　
※精度は0~1の間で表現される。
　精度が0.6以上だと、使用した説明変数で目的変数の60%以上を説明できていると解釈でき、それなりの精度があるといえる。

```py:predictTip.py
#学習済みのモデルにテストデータを突っ込んで、モデルの精度を評価
print('評価: {}'.format(np.round(lasso.score(Feature_test,Target_test),4)))

#テストデータのうち無作為に１０個を出力用サンプルとして抽出
Feature_sample = Feature_test[:10]
Target_sample = Target_test[:10]

#サンプルの予測と実際の値の比較
print('予測結果($){}'.format(np.round(lasso.predict(Feature_sample), 3)))
print('実際の値($){}'.format(np.round(list(Target_sample),3)))
```
**出力結果**

```
評価: 0.5467
予測結果($)[3.050  2.013  3.495  3.620  2.410  2.869  3.860  2.361  2.577  2.634]
実際の値($)[3.180  2.000  2.000  5.160  2.000  2.000  2.560  2.520  3.230  3.000]
```
精度は **0.5467** でした。
***
### 【おまけ】実際に、適当な条件をこのモデルに突っ込んで、チップ額を予測させてみた。
条件：
「日曜日のディナータイムに2人で計25.5ドルの食事をした。支払いは男性。席は禁煙席。」
```py:predictTip.py
#適当な条件のデータを準備→モデルに突っ込める形に整形
newdata=[[25.5,1,1,3.2551,0,2]]
data = pd.DataFrame(newdata,columns = ["total_bill","sex","smoker","day","time","size"])

#予測
result=lasso.predict(data)
print('{} ($)'.format(np.round(result, 2)))
```
**出力結果**
```
[3.584] ($)
```

このモデルによると、上記の条件の場合、払うべき妥当なチップ額は $3.584 とのこと。

ちなみに＄3.584は、条件にある支払額＄25.5のおよそ14%。
Wikipedia先生によると、チップの相場は大体10~20%の範囲内が多い、とのことなので
まあ、妥当な数値を予測してくれたんじゃあないかな、と思います。


## 最後に
精度は0.5467と、少し低めでしたね…(;´Д｀)
ここから精度を高めるには、学習用データの中から突飛な値のデータを取り除いたり、結果に与える影響が少なそうな説明変数を除外したり、曜日データの数値化の手法を変えてみる、等が考えられますが、それはまたいずれ試してみます。

今回は、とにかくイージーに回帰分析を実装してみる。ということで、
この記事を見て実装の全体像を掴んでいただければ幸いです。

最後に今回のコードをまとめて置いておきます。

```py:predictTip.py
import numpy as np
import pandas as pd
import seaborn as sns
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
# Lesso回帰を使用（重要でない説明変数を勝手に間引いてくれるモデル）
from sklearn.linear_model import Lasso
 
tips = sns.load_dataset('tips')

# 性別の数値化
labelEncoder = LabelEncoder()
tips['sex'] = labelEncoder.fit_transform(tips['sex'])

# 喫煙有無の数値化
tips['smoker'] = labelEncoder.fit_transform(tips['smoker'])

# 食事時間の数値化
tips['time'] = labelEncoder.fit_transform(tips['time'])

# 曜日の数値化
day_mapping = tips[['day', 'tip']].groupby(['day'])['tip'].mean().to_dict()
tips['day'] = tips['day'].map(day_mapping)

#データセットを説明変数(チップ額以外)と目的変数(チップ額)に分けて格納
tip_features = tips.drop(['tip'], axis=1)
tip_target = tips['tip']

#説明変数・価格変数をそれぞれトレーニング(学習)用と、テスト用のデータに分ける
#トレーニング用：テスト用＝ 8 : 2 の割合
Feature_train, Feature_test, Target_train, Target_test = train_test_split(tip_features, tip_target, test_size=0.2, random_state=42)

#モデルをインスタンス化
lasso = Lasso()

#インスタンス化したモデル.fit(トレーニングデータの説明変数, 目的変数)で学習を実行
lasso.fit(Feature_train, Target_train)

#モデルの評価
print('評価: {}'.format(np.round(lasso.score(Feature_test,Target_test),4)))

#テストデータのうち無作為に１０個の予測結果と、その実際の値を出力用サンプルとして抽出
Feature_sample = Feature_test[:10]
Target_sample = Target_test[:10]

#サンプルの予測と実際の値の比較
print('予測結果($){}'.format(np.round(lasso.predict(Feature_sample), 3)))
print('ラベル($){}'.format(np.round(list(Target_sample),3)))

#適当な条件のデータを準備→モデルに突っ込める形に整形
newdata=[[25.5,1,1,3.2551,0,2]]
data = pd.DataFrame(newdata,columns = ["total_bill","sex","smoker","day","time","size"])

#予測
print('{} $'.format(np.round(lasso.predict(data), 3)))
```