医療保険料のEDAと回帰問題

#概要
会社で機械学習の知識がない課員にAIとかいうものの教育をする機会があるので、米国の医療保険料データを使ったEDA,クラスタリング,回帰問題の教材を作ることとした
データセットはKaggleの[これ](https://www.kaggle.com/mirichoi0218/insurance)を使う
ざっと作ってみた感触は、ちょっと難しく感じられるかもしれなくって教材には使用しない
もっと簡単なものを別に用意することとしよう

- 実施期間: 2020年11月
- 環境：Google Colaboratory

##データセット
insurance.csvの構成は下図の通り
医療保険料を示すchargesを今回の回帰による予測対象とする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205257/dfbc4ee3-85a6-81c7-5ba6-3216e042a738.png)

各列の説明を転記しておく
Columns
age: age of primary beneficiary
sex: insurance contractor gender, female, male
bmi: Body mass index, providing an understanding of body, weights that are relatively high or low relative to height,
objective index of body weight (kg / m ^ 2) using the ratio of height to weight, ideally 18.5 to 24.9
children: Number of children covered by health insurance / Number of dependents
smoker: Smoking
region: the beneficiary's residential area in the US, northeast, southeast, southwest, northwest.
charges: Individual medical costs billed by health insurance

##EDA(Explanatory Data Analysis)
馴染みのない言葉の方もおられるかもしれない
解析対象のデータセットの構造を確認し、どのように解析するか作戦を最初に立てるプロセスのこと（と思っている）
ツールの種類や慣れ、直感が必要
解析方法は人それぞれなので、以下は参考までにとどめていただきたい

まず、ファイルを開いて中身とデータに欠損がないか確認する

```python
import numpy as np 
import pandas as pd 
import os
from sklearn.preprocessing import LabelEncoder
import matplotlib.pyplot as plt
import seaborn as sns

df = pd.read_csv('insurance.csv')
# Datasetの表示
print(df.tail())
# NaNの有無を確認
df.isnull().sum()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205257/3376c7f0-f763-c183-adac-21786a7590c8.png)
欠損がないことがわかる（あれば少し面倒なのでラッキー）
カテゴリデータが含まれているので、それらを[LabelEncoder](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.LabelEncoder.html)で数値化する

```python
# カテゴリデータのエンコード
# sex
le = LabelEncoder()
le.fit(df.sex.drop_duplicates()) 
df.sex = le.transform(df.sex)
# smoker or not
le.fit(df.smoker.drop_duplicates()) 
df.smoker = le.transform(df.smoker)
#　region
le.fit(df.region.drop_duplicates()) 
df.region = le.transform(df.region)
# Datasetの表示
print(df.tail()) 
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205257/3bdaf840-cfaa-017d-acc7-8e21da8b6e25.png)
整数に置き換わっている
全体の相関係数を眺めて、chargesに影響を与えている説明変数にあたりをつける

```python
print(df.corr())
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205257/aa3dd6b5-7405-0c45-c604-7f36d8cc7029.png)
0.787251でsmokerがとても強い相関を示している
次が0.299008のage、肥満度を示すbmi(=体重/身長^2)の相関係数が思ったより小さいのが気になる
しかし、たった".corr()"だけで一発計算なので、Python使っててよかったと思うね

全体の分布を表示する

```python
sns.pairplot(df.loc[:, ['age', 'sex', 'bmi', 'children', 'smoker', 'region', 'charges']], height=2);
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205257/d8ec8d51-c4ee-7d6d-15a0-ac26ecaee83e.png)
特徴的な分布は下図なので、age,bmi,smokerに集中する
どうも3つのグループに分けられそうだが、何が原因で3つに分割されたのか解析する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205257/c0c6efc7-b18e-f07b-bac4-375be856abe5.png)
age-charges分布がとても特徴的
相関が強かったsmokerについて掘ってみる

```python
# smoker-chargesをハコヒゲで見てみる
sns.boxplot(x="smoker", y="charges", data=df)
# 数字で確認
df_s0 = df[df['smoker'] == 0]  # 非喫煙者
df_s1 = df[df['smoker'] == 1]  # 喫煙者
pd.set_option('display.max_rows', 20)
pd.set_option('display.max_columns', None)
print(' non smoker:\n' + str(df_s0.describe()))
print('\n smoker:\n' + str(df_s1.describe()))
print('\n 非喫煙者の医療保険料は、' + str(df_s1['charges'].mean() / df_s0['charges'].mean()) + '倍もかかっている。') 
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205257/7806241a-0b28-3c65-8409-489349b62013.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205257/30e922cc-dc4e-fa1e-4fff-cf2a8f73c8ba.png)

箱ひげ図から非喫煙者はchargesヒストグラムの左の高い山に相当することがわかる
逆に喫煙者のオレンジはchargesヒストグラムの低い2つの山がまとまったものといえる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205257/4a741e4f-e1a5-c271-cea7-7597b04c9df8.png)

非喫煙者だけで分布を確認する

```python
sns.pairplot(df_s0.loc[:, ['age', 'bmi', 'charges']], height=2); 
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205257/1baac45a-abd7-74a4-c39a-01e646db78ad.png)
前述のage-charges分布が分離できた
あの特徴的な分布は喫煙か否かが影響してたらしい
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205257/0e040c95-99bb-448d-19cc-41ceaf172c0d.png)
非喫煙者でも年齢にかかわらず一定の割合で病院にかかるから、age-charges分布の上部散らばりがそれを表しているのか？
この散らばりとbmi-charges分布の散らばりがリンクしているようにみえるので、クラスタリングしてみる

```python
from sklearn.cluster import KMeans
#from sklearn.cluster import MiniBatchKMeans

df_wk = df_s0.drop(['sex', 'children', 'smoker', 'region'], axis=1)
kmeans_model = KMeans(n_clusters=2, random_state=1, init='k-means++', n_init=10, max_iter=300, tol=0.0001, algorithm='auto', verbose=0).fit(df_wk)
#kmeans_model = MiniBatchKMeans(n_clusters=2, random_state=0, max_iter=300,  batch_size=100, verbose=0).fit(df_wk)

df_wk['cluster'] = kmeans_model.labels_
labels = kmeans_model.labels_
print(labels.sum())
color_codes = {0:'r', 1:'g', 2:'b'}
colors = [color_codes[x] for x in labels]
ax1 = df_wk.plot.scatter(x='age', y='charges', c=colors)
ax1 = df_wk.plot.scatter(x='bmi', y='charges', c=colors)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205257/a3dd7012-ccb1-5952-14ea-1db95ed20eea.png)
キレイに分離できなかったのは残念だが、前出の2つの分布図の散らばりはお互いに呼応しているといえる
ただ、BMIの大小にかかわらず全年齢でchargesが高い理由がわからない
childrenとregionはまだ調べてなく、どちらかというとchildrenのほうがbmiのばらつきに関係していそう
非喫煙者のchildren(=扶養家族数)ごとの分布を見れば、家族構成とbmiの関係がわかりそう

```python
df_c0 = df_s0[df_s0['children'] == 0]  # children = 0
df_c1 = df_s0[df_s0['children'] == 1]  # children = 1
df_c2 = df_s0[df_s0['children'] == 2]  # children = 2
df_c3 = df_s0[df_s0['children'] == 3]  # children = 3
df_c4 = df_s0[df_s0['children'] == 4]  # children = 4
df_c5 = df_s0[df_s0['children'] == 5]  # children = 5
plt.figure(figsize=(12, 6))
plt.hist([df_c0['charges'], df_c1['charges'], df_c2['charges'], df_c3['charges'], df_c4['charges'], df_c5['charges']]
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205257/fee76787-9ad9-2976-c132-157d2fce94c3.png)

charges > 15000のヒストグラムに注目すると、chargesと子供の数に特徴は見えない
やっぱり、「非喫煙者でも年齢にかかわらず一定の割合で病院にかかるから」純粋にばらついているだけなのか？
charges > 15000においてbmiが正規分布でばらついているか検定してみる

```python
df_b0 = df_s0[df_s0['charges'] > 15000]  # children = 0
plt.hist([df_b0['bmi']])
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205257/aea28f7b-8549-ae52-0a98-238f1f4ed5ab.png)

```python
import scipy.stats as stats
stats.probplot(df_b0['bmi'], dist="norm", plot=plt)
stats.shapiro(df_b0['bmi'])
```
(0.9842953681945801, 0.3445127308368683)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205257/fdbe5eb6-7b76-d0be-4bc9-e46022cce808.png)
ヒストグラムもQQプロットもシャピロ–ウィルク検定(p値=0.3445)も正規分布と言えないとは言えないことを示している
結局原因はなんかよくわからなかった
まぁ、正規分布ってことで、自然にばらついたことにしておこう…


今度は喫煙者だけで分布を確認する

```python
sns.pairplot(df_s1.loc[:, ['age', 'bmi', 'charges']], height=2);
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205257/9810eac7-f927-0bee-63f3-8130a1a875e6.png)
直感的に下図のようにageとbmiが呼応しているように見える
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205257/ed33b54d-16ce-55b6-bd65-c029b33d6a34.png)
非喫煙者のときと同様にクラスタリングしてみる

```python
df_wk = df_s1.drop(['sex', 'children', 'smoker', 'region'], axis=1)
kmeans_model = KMeans(n_clusters=2, random_state=1, init='k-means++', n_init=10, max_iter=300, tol=0.0001, algorithm='auto', verbose=0).fit(df_wk)
df_wk['cluster'] = kmeans_model.labels_
labels = kmeans_model.labels_
print(labels.sum())
color_codes = {0:'r', 1:'g', 2:'b'}
colors = [color_codes[x] for x in labels]
ax1 = df_wk.plot.scatter(x='age', y='charges', c=colors)
ax1 = df_wk.plot.scatter(x='bmi', y='charges', c=colors) 
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205257/15c837be-29a9-f72e-261c-7c0dce0471e9.png)
思っていたとおりだが、今度はキレイに分離できた

##EDAの結論
喫煙癖にかかわらず医療保険料が高額なのはBMIが高い人たちだが、年齢は関係ない（耳が痛い…）
喫煙者のデータからbmi=30を境に医療保険料が一段上昇することがわかった
この境がはっきりしすぎているので、例えば30を超えたら高額な検査項目が強制的に追加されるような行政上の縛りがあるのではないだろうか

```python
sns.regplot(x="bmi", y="charges", data=df_wk)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205257/837b7937-6e31-7191-3c5c-3e0e60901d29.png)
つまり、例えば上図のような回帰直線を引いてはならないことが今回のEDAからわかる



##単回帰、重回帰問題
非喫煙者について、chargesを回帰問題予測してみる
具体的には単回帰(ageのみ)と重回帰(age, bmi)でcharges予測精度の違いをRMSEで評価する
クラスタリング結果は使用しないので、ばらつきは含んだままとする

```python
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import mean_squared_error
from sklearn import metrics
from sklearn import linear_model

cv_alphas = [0.01, 0.1, 1, 10, 100]     #交差検証用のα
df_wk = df_s0.drop(['sex', 'children', 'smoker', 'region'], axis=1)

# Predict結果の評価とプロット
def regression_prid(clf_wk, df_wk, intSample=20, way='single'):
    arr_wk = df_wk.values[:intSample]
    arr_age, arr_agebmi, arr_charges = arr_wk[:,0], arr_wk[:,0:2], arr_wk[:,2]

    if way == 'single':
        arr_prid = clf_wk.predict(arr_age.reshape([intSample,1]))
        titel1='simple regression'
        titel2='RMSE(single) =\t'
    else:
        arr_prid = clf_wk.predict(arr_agebmi.reshape([intSample,2]))
        titel1='multiple regression'
        titel2='RMSE(multi) =\t'

    rmse = np.sqrt(mean_squared_error(arr_charges, arr_prid))
    print(titel2 + str(rmse))

    # プロット
    arr_chart = df_wk.values[:intSample].reshape([intSample,3])
    arr_chart = np.hstack([arr_chart, arr_prid.reshape([intSample,1])])

    fig = plt.figure(figsize=(8, 5))
    ax = fig.add_subplot(1,1,1)
    ax.scatter(arr_chart[:,0],arr_chart[:,2], c='red')
    ax.scatter(arr_chart[:,0],arr_chart[:,3], c='blue')
    ax.set_title(titel1)
    ax.set_xlabel('age')
    ax.set_ylabel('charges')
    fig.show() 
```


###最小二乗法による単回帰
[LinearRegression](https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html)

```python
clf = linear_model.LinearRegression()    

# 単回帰問題
clf.fit(df_wk[['age']], df_wk['charges'])
regression_prid(clf, df_wk, 50, 'single')

# 重回帰問題
clf.fit(df_wk[['age', 'bmi']], df_wk['charges'])
regression_prid(clf, df_wk, 50, 'multi')
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205257/69f2ec8f-98ec-8c82-8d96-5123ad833746.png)

上がageで予測した単回帰で下がageとbmiで予測した重回帰
RMSEは20点で予測させた時のもので、単回帰のほうがやや精度が高い
元データがほぼ線形なので単回帰でも重回帰でも予測は直線に乗っている

###L2正則化による回帰
[Ridge](https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.Ridge.html#sklearn.linear_model.Ridge)
[RidgeCV](https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.RidgeCV.html#sklearn.linear_model.RidgeCV)
ちなみにCVはCloss Validation、交差検証のこと
交差検証の詳細はググってください

```python
cv_flag = True   # True:交差検証

if cv_flag:
    clf = linear_model.RidgeCV(alphas=cv_alphas ,cv=3, normalize=False)

    # 単回帰問題
    clf.fit(df_wk[['age']], df_wk['charges'])
    print("alpha =\t", clf.alpha_)
    regression_prid(clf, df_wk, 50, 'single')

    # 重回帰問題
    clf.fit(df_wk[['age', 'bmi']], df_wk['charges'])
    print("alpha =\t", clf.alpha_)
    regression_prid(clf, df_wk, 50, 'multi')

else:
    clf = linear_model.Ridge(alpha=1.0)

    # 単回帰問題
    clf.fit(df_wk[['age']], df_wk['charges'])
    regression_prid(clf, df_wk, 50, 'single')

    # 重回帰問題
    clf.fit(df_wk[['age', 'bmi']], df_wk['charges'])
    regression_prid(clf, df_wk, 50, 'multi') 
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205257/1832d0c6-0af0-7abd-ff39-2b897c3a4075.png)

交差検証を使って予測してみたが、やはり重回帰のほうがよろしくない

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205257/53c0fe4c-9446-929c-48d5-7a208cc7e876.png)

交差検証しなくてもRMSEはそれほど変わらない
やはりchargesがリニアに上昇しているから回帰手法による精度差が出にくなったと思う

###L1正則化による回帰
[Lasso](https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.Lasso.html#sklearn.linear_model.Lasso)
[LassoCV](https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LassoCV.html#sklearn.linear_model.LassoCV)
上のコード中のRidgeをLassoに置き換えるだけなので省略
RMSEはほとんど変わらなかった