5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

糖尿病の予測モデル

Last updated at Posted at 2023-10-16

1. はじめに

1.1.自己紹介

今回、この記事は、(株)Aidemyのデータ分析講座の演習課題の成果物として作成しました。

簡単に、自己紹介とデータ分析講座の受講の経緯を書きます。
新卒後、医薬品の生産技術に関連する技術職に14年ほど携わっていたのですが、子育てとの両立が上手くいかずに体調を崩してしまい、2023年6月で退職。
新卒からずっと働いてきて、その間、育休は取得したものの、子育てにいっぱいいっぱいな私にはリスキングなんてする余裕もなく、追われるように過ぎていった毎日。
すぐに転職をするのではなく、少し間を空けて、この機会に新しいことを学んでみたいなという気持ちからITの勉強をすることを決めました。

前職でも、数値を見て色々と仮説を立てて検証するということは好きだったため、データサイエンティストに興味を持ち、(株)Aidemyのデータ分析講座を受講し、データ分析や機械学習について基礎的な内容を勉強しました。

その講座の最終の演習課題として、講座で学習した内容を用いた機械学習のプログラムを作成し、その内容を紹介するこのブログの作成しました。


2. 本記事の概要

kaggleにある「Diabetes prediction dataset」という医療および人口統計データによる糖尿病を予測するための包括的なデータセットを用いて、糖尿病患者を予測する機械学習モデルをpythonで構築しました。

データを可視化し、その特徴から仮説を立て、新たな特徴量の作成、モデル学習と評価、モデルの検討、パラメーターの調整という流れで機械学習のプログラムを作成しました。


3. 作成したプログラム

作成したプログラムを紹介していきます。

3.1. データの確認

3.1.1. データの読み込み

Pandasライブラリを用いて、今回使用するデータを pandas.DataFrame 型で取得しました。
CSVファイルを読み込み、pandas.DataFrame 型を取得するために、下記のようなコードを記述しました。

import pandas as pd

# google colabのmy driveへのマウント
from google.colab import drive
drive.mount('/content/drive')

#使用するCSVファイルの読み込み
all_df = pd.read_csv("/content/drive/MyDrive/Aidemy成果物/diabetes_prediction_dataset.csv")

3.1.2. データの確認

まず、読み込んだデータを確認しました。
下記のようなコードを記述し、データの形や各columnのデータ型などを確認しました。

# データの形を表示
print(f'all_df_shape : {all_df.shape}\n')

all_df_shape : (100000, 9)

100,000人の対象者のデータと、9列の特徴量があることが分かりました。

# 各columnのデータ型を表示
print(f'{all_df.dtypes} \n')

gender object
age float64
hypertension int64
heart_disease int64
smoking_history object
bmi float64
HbA1c_level float64
blood_glucose_level int64
diabetes int64
dtype: object

特徴量には、整数(int), 小数(float), 文字(object)があることが分かりました。

# 先頭5つを可視化
display(all_df.head())

image.png

それぞれの特徴量の意味は以下の通りとなります。

  • gender : 男性(male), 女性(female)
  • age : 年齢
  • hypertension : 高血圧の有無(なし=0、あり =1)
  • heart_disease : 心臓に関連する疾患の有無(なし=0、あり =1)
  • smoking_history : 喫煙歴
  • bmi : 体格指数(体重と身長から計算される指数、BMI = 体重kg ÷ (身長m) ÷ (身長m) )
    • 日本では、BMIの値は通常以下のカテゴリーに分類されます:
      • 18.5未満:低体重
      • 18.5から24.9まで:正常体重
      • 25から29.9まで:過体重(肥満の前段階)
      • 30以上:肥満
  • HbA1c_level : 血液中のHbA1c(ヘモグロビンA1c)の量(糖尿病の管理に用いられる指標)
    • 日本では、HbA1cの値は通常以下に分類されます:
      • 糖尿病診断前のHbA1c:5.7%未満
      • 糖尿病の診断に関連するHbA1c:6.5%以上
      • 糖尿病管理の目標HbA1c:一般的には7%未満(個別に調整されることがある)
  • blood_glucose_level : 血糖値
    • 糖尿病域;空腹時血糖値 126 mg/dl以上
    • 境界域(糖尿病予備軍):空腹時血糖値 110-126 mg/dl
  • diabetes : 糖尿病の有無(なし=0、あり =1)

今回は、このデータを用いて、糖尿病の発症(diabetes=1)を予測するモデルを作成しました。

3.1.3. 統計量の確認

まず初めに、下記のようなコードを記述し、訓練データにおける数値データとカテゴリカルデータの統計量を確認しました。
カテゴリカルデータは量的でないデータを指し、糖尿病予測のデータではgender, smoking_historyがこれに当たります。

# 数値データの統計量を表示
display(all_df.describe())

image.png

# カテゴリカルデータの統計量を表示
display(all_df.describe(exclude='number'))

image.png

算出した統計量から下記のようなことが確認できました。

  • countが全て100000であることから、欠損のある特徴量はない。
  • diabetes: 平均が0.5 より顕著に小さい。よって、今回の目的変数は偏りがありそう。
  • age: minが0歳、maxが80歳で、平均及び中央値が40歳付近であることから、0歳から80歳まで偏りなく分布していると予想される。
  • hypertension及びheart_disease: 平均が0.5 より顕著に小さい。このため、高血圧の有無及び心疾患の有無には偏りがあると予想される。
  • bmi及びblood_glucose_level: 最大値が平均値や中央値と比べ大きいので、外れ値が含まれている可能性がある。
  • gender: Femaleが約58%であり、半数よりやや多い。
  • smoking_history: 3割以上はNo infoとなっている。

3.2. データの可視化

3.2.1. データの可視化 -分布-

次に、各特徴量の分布を可視化し、分布に偏りがないかなどを確認しました。

3.2.1.1. diabetesの可視化 -分布-

まず初めに、3.1.3.で仮説を立てた、目的変数であるdiabetesの分布の偏りについて、可視化を行い確認しました。
下記のようなコードを記述し、diabetesの分布を可視化しました。

import seaborn as sns

# diabetesについて可視化
display(
    all_df['diabetes'].value_counts(ascending=False,normalize=True)
    )

sns.countplot(x='diabetes', data=all_df)

image.png

糖尿病に該当しない対象者が90%以上、糖尿病に該当する対象者が10%以下という分布になっており、分布に偏りが認められました。

3.2.1.2. genderの可視化 -分布-

次に、gengerの可視化を行いました。

# genderについて可視化
display(all_df['gender'].value_counts(ascending=False,normalize=True))

sns.histplot(x='gender', data=all_df, bins=40)

image.png

約60%がFemale、約40%がMaleであり、Otherが0.1%以下であることが分かりました。

3.2.1.3. ageの可視化 -分布-

次に、ageの分布を可視化しました。

# ageについて可視化
sns.histplot(x='age', data=all_df, bins=40)

image.png

0-2歳、78‐80歳のバーが特出して多いことが分かりました。
そこで、ageの内容をもう少し細かく見ていくことにしました。

# ageの行に記載されている数値とその数(上下から各5行)
age_counts = all_df['age'].value_counts().reset_index()
age_counts.columns = ['Age', 'Count']
print(age_counts)

image.png

80歳が圧倒的に多く、5621人居るいうことが分かりました。
また、0歳については、月齢も含めて小数点以下2桁で表示されているようです。
そこから、元のデータを見てみると1歳についても小数点以下2桁で表示されているデータがありました。

# 0、1、2歳と78、79、80歳の対象者の数
age_0_count = ((all_df['age'] >= 0.00) & (all_df['age'] < 1.00)).sum()
age_1_count = ((all_df['age'] >= 1.00) & (all_df['age'] < 2.00)).sum()
age_2_count = ((all_df['age'] == 2.00) ).sum()
age_78_count = (all_df['age'] == 78).sum()
age_79_count = (all_df['age'] == 79).sum()
age_80_count = (all_df['age'] == 80).sum()

print(f"0歳: {age_0_count}")
print(f"1歳: {age_1_count}")
print(f"2歳: {age_2_count}")
print(f"78歳: {age_2_count}")
print(f"79歳: {age_79_count}")
print(f"80歳: {age_80_count}")

image.png

先ほどのヒストグラムと照らし合わせると、78-80歳のバーが特出して多いのは80歳が多いためであることが分かりました。
80歳が多い理由としては、80歳以上の対象者は全て、80として入力されている可能性があるように思います。
一方で、0-2歳のバーが特出して多いのは、他のヒストグラムがの対象が2つの年齢の対象者であるのに対し、0歳、1歳、2歳と3歳分の対象者が含まれたためだと考えられました。

ageの全体としては、0歳から18歳と、62歳から79歳までが、他の年齢に比べてやや少ない傾向が見られました。

3.2.1.4. hypertension及びheart_diseaseの可視化 -分布-

次に、hypertension及びheart_diseaseの分布を可視化しました。

# hypertensionについて可視化
display(
    all_df['hypertension'].value_counts(ascending=False,normalize=True)
    )

sns.countplot(x='hypertension', data=all_df)

image.png

hypertensionに該当しない対象者が92%以上、糖尿病に該当する対象者が8%以下となっており、分布に偏りが認められました。

# heart_diseaseについて可視化
display(
    all_df['heart_disease'].value_counts(ascending=False,normalize=True)
    )

sns.countplot(x='heart_disease', data=all_df)

image.png

heart_diseasenに該当しない対象者が96%以上、糖尿病に該当する対象者が4%以下となっており、分布に偏りが認められました。

3.2.1.5. bmiの可視化 -分布-

次に、bmiの分布を可視化しました。

import matplotlib.pyplot as plt

# bmiについて可視化(ヒストグラムの間隔を1とした)
# データの最小値と最大値を取得
min_bmi = all_df['bmi'].min()
max_bmi = all_df['bmi'].max()

# ビンの幅を1に設定
bin_width = 1

# ビンの数を計算
num_bins = int((max_bmi - min_bmi) / bin_width) + 1

# ヒストグラムをプロット
sns.histplot(x='bmi', data=all_df, bins=num_bins)
plt.xlim(10, 100)
plt.show()

image.png

27-28の区間が特出して多いようだったので、26-28に該当する対象者の数を確認してみました。

# bmiが26.00-26.99、27.00-27.99、28.00-28.99の対象者数
bmi_26_count = ((all_df['bmi'] >= 26.00) & (all_df['bmi'] < 27.00)).sum()
bmi_27_count = ((all_df['bmi'] >= 27.00) & (all_df['bmi'] < 28.00)).sum()
bmi_28_count = ((all_df['bmi'] >= 28.00) & (all_df['bmi'] < 29.00)).sum()

print(f"26.00-26.99: {bmi_26_count}")
print(f"27.00-27.99: {bmi_27_count}")
print(f"28.00-28.99: {bmi_28_count}")

image.png
27.00-27.99の対象者が29653人と特出して多いことが分かりました。
理由ははっきりとはしませんが、bmiが27を超過したら糖尿病の検査を受けるような仕組みがあり、データの対象者に多く含まれたなどの可能性も推測されます。

また、上のヒストグラムでは外れ値が見えにくかったため、y軸の範囲を狭くして、外れ値を確認しました。

# bmiのヒストグラムのy軸を0-500に変更
sns.histplot(x='bmi', data=all_df, bins=num_bins)
plt.xlim(10, 100)
plt.ylim(0, 500)
plt.show()

image.png
bmi全体としては、27.00-27.99が特出して多いこと、27.00-27.99を除いた場合、25まで増加しその後減少、70以降は飛び飛びに対象者がわずかに分布していることが分かりました。

3.2.1.6. HbA1c_levelの可視化 -分布-

次に、HbA1c_levelについて可視化しました。
image.png

検査方法による特徴なのか、値が散らばっていたため、最小値3、最大値9とし、1間隔のヒストグラムを作成しました。

import pandas as pd

HbA1c_df = all_df.copy()

# ビンの範囲をリストで指定
HbA1c_bins = [3, 3.9, 4.9, 5.9, 6.9, 7.9, 8.9, 9.9]

# pd.cut を使用して離散的なカテゴリに変換
HbA1c_df['HbA1c_category'] = pd.cut(all_df['HbA1c_level'], bins=HbA1c_bins, labels=['3.0-3.9', '4.0-4.9', '5.0-5.9', '6.0-6.9', '7.0-7.9', '8.0-8.9', '9.0-'])

# カテゴリごとの数値の割合を表示
display(HbA1c_df['HbA1c_category'].value_counts(ascending=False,normalize=True))

# ヒストグラムを表示
sns.histplot(x='HbA1c_category', data=HbA1c_df)

image.png
3.0-3.9から6.0-6.9まで増加し、6.0-6.9が約42%でした。7.0-7.9、8.0-8.9、9.0-は全て1.5%以下という分布が認められました。

3.2.1.7. blood_glucose_levelの可視化 -分布-

次に、blood_glucose_levelの分布を可視化しました。

# blood_glucose_levelについて可視化
sns.histplot(x='blood_glucose_level', data=all_df)

image.png
HbA1c_level同様、値が散らばっていたため、最小値80、最大値300とし、10間隔のヒストグラムを作成しました。

# x軸の範囲とビン数を指定
sns.histplot(x='blood_glucose_level', data=all_df, bins=22)
plt.xlim(80, 300)
plt.xticks(range(80, 301, 20))
plt.show()

image.png
バラつきが大きく、一定の区間に空白が認められました。
また、80-210に多数の対象者のが含まれ、210以上の対象者は少ないという分布が認められました。

3.2.1.8. smoking_historyの可視化 -分布-

最後に、smoking_historyの分布を可視化しました。

import seaborn as sns
display(all_df['smoking_history'].value_counts(ascending=False,normalize=True))

sns.countplot(x='smoking_history', data=all_df)

image.png
No infoとNeverが約35%、former、currentが約10%not currentが約6.5%、everが約4%でした。

3.2.1.9. データの可視化 -分布- のまとめ

各特徴量の分布を可視化した結果、以下のようなことが確認できました。

  • diabetes: 糖尿病に該当しない対象者が90%以上、糖尿病に該当する対象者が10%以下。
  • gender: 約60%がFemale、約40%がMaleであり、Otherが0.1%以下である。
  • age: 80歳の割合が他の年齢よりも多い
  • hypertension: 高血圧に該当しない対象者が92%以上、該当する対象者が8%以下。
  • heart_disease: 心疾患に該当しない対象者が96%以上、該当する対象者が4%以下。
  • bmi: 27‐28が特出して多い。27‐28を除いた場合、25まで増加しその後減少、70以上は飛び飛びに対象者がわずかに分布。
  • HbA1c: 3.0-3.9から6.0-6.9まで増加し、6.0-6.9が約42%。7.0-7.9、8.0-8.9、9.0-は全て1.5%以下。
  • blood_glucose_level: バラつきが大きいものの、80-210に大多数の対象者が含まれる。
  • smoking_history: No infoとNeverが約35%、former、currentが約10%not currentが約6.5%、everが約4%

3.2.2. 各特徴量の相関性

次に、各特徴量の相関性を可視化しました。

sns.heatmap(
    all_df[['diabetes','age','hypertension', 'heart_disease', 'bmi', 'HbA1c_level', 'blood_glucose_level']].corr(),
    vmax=1,vmin=-1,annot=True
    )

image.png

ageとbmi、HbA1c_levelとdiabetes、blood_glucose_levelとdiabetesに正の弱い相関が認められました。

3.2.3. データの可視化 -目的変数との相関性-

次に、各特徴量と目的変数 (diabetes)の関係について可視化しました。

3.2.3.1. ageの可視化 -目的変数との相関性-

まず、ageと目的変数 (diabetes)の関係について可視化しました。

# ageについてdiabetesごとに可視化
import seaborn as sns

fig = sns.FacetGrid(all_df, col='diabetes', hue='diabetes', height=4)
fig.map(sns.histplot, 'age', bins=40, kde=False)

image.png
糖尿病に該当する対象者(diabetes=1)は、年齢とともに60歳付近まで微増し、その後微減するものの、78-80歳が特出して多いという傾向が見られました。

78‐80歳が特出して多いのは、分布の可視化で述べたように、80歳が対象者全体の中でも特出して多いため、その影響が考えられました。
一方で、糖尿病に該当する対象者で見られた60歳付近までの増加傾向は、対象者全体では認められなかったものでした。

3.2.3.2. genderの可視化 -目的変数との相関性-

次に、genderと目的変数 (diabetes)の関係について可視化しました。

# gengerについて可視化
sns.countplot(x='gender', hue='diabetes', data=all_df)
plt.show()

image.png

# gender事の糖尿病に該当する対象者の割合
display(pd.crosstab(all_df['gender'], all_df['diabetes'], normalize='index'))

image.png

Femaleに比べてMaleの方が糖尿病に該当する対象者の割合がわずかに高かったですが、顕著な違いは認められませんでした。

3.2.3.3. hypertensionの可視化 -目的変数との相関性-

次に、hypertensionと目的変数 (diabetes)の関係について可視化しました。

# hypertensionについて可視化
sns.countplot(x='hypertension', hue='diabetes', data=all_df)
plt.show()

image.png

# hypertension事の糖尿病に該当する対象者の割合
display(pd.crosstab(all_df['hypertension'], all_df['diabetes'], normalize='index'))

image.png
hypertensionに該当する対象者の方が、diabetesに該当する割合が高いことが分かりました。

3.2.3.4. heart_diseaseの可視化 -目的変数との相関性-

次に、heart_diseaseと目的変数 (diabetes)の関係について可視化しました。

# heart_diseaseについて可視化
sns.countplot(x='heart_disease', hue='diabetes', data=all_df)
plt.show()

image.png
heart_diseaseに該当する対象者の方が、diabetesに該当する割合が高いことが分かりました。

3.2.3.5. bmiの可視化 -目的変数との相関性-

次に、bmiと目的変数 (diabetes)の関係について可視化しました。

# bmiについてdiabetesごとに可視化
import numpy as np

# ビンの境界を手動で指定
bmi_bin_edges = np.arange(10, 100, 1)

# FacetGridを作成
fig = sns.FacetGrid(all_df, col='diabetes', hue='diabetes', height=4)
fig.map(sns.histplot, 'bmi', bins=bmi_bin_edges, kde=False)

plt.xlim(10, 100)
plt.show()

image.png
y軸の値も大きく、グラフが見にくかったため、y軸を0-5000に変更しました。

# グラフのy軸のmaxを5000に変更
# ビンの境界を手動で指定
bmi_bin_edges = np.arange(10, 100, 1)

# FacetGridを作成
fig = sns.FacetGrid(all_df, col='diabetes', hue='diabetes', height=4)
fig.map(sns.histplot, 'bmi', bins=bmi_bin_edges, kde=False)

plt.xlim(10, 100)
plt.ylim(0,5000)
plt.show()

image.png

対象者全体と同様に、糖尿病に該当する対象者についても27.00-27.99が特出して多いことが分かりました。
また、20以下では糖尿病に該当する対象者の割合は低く、30以上、特に40以上で糖尿病に該当する対象者の割合が高くなることが分かりました。

3.2.3.6. HbA1c_levelの可視化 -目的変数との相関性-

次に、HbA1c_levelと目的変数 (diabetes)の関係について可視化しました。

# HbA1c_levelについてdiabetesごとに可視化
HbA1c_df['diabetes'] = all_df['diabetes']

# FacetGridを作成
fig = sns.FacetGrid(HbA1c_df, col='diabetes', hue='diabetes', height=4)
fig.map(sns.histplot, 'HbA1c_category', bins= HbA1c_bins, kde=False)
fig.set_xticklabels(rotation=45)

plt.show()

image.png

HbA1c_levelが7.0以上には、糖尿病に該当する対象者しかおらず、糖尿病に該当しない対象者は0であることが分かりました。また、糖尿病に該当する対象者は、全てHbA1c_levelが5.0以上でした。

3.2.3.7. blood_glucose_levelの可視化 -目的変数との相関性-

次に、blood_glucose_levelと目的変数 (diabetes)の関係について可視化しました。

# blood_glucose_levelについてdiabetesごとに可視化
import numpy as np

# ビンの境界を手動で指定
bmi_bins = np.arange(80, 310, 10)

# FacetGridを作成
fig = sns.FacetGrid(all_df, col='diabetes', hue='diabetes', height=4)
fig.map(sns.histplot, 'blood_glucose_level', bins=bmi_bins, kde=False)

plt.xlim(80, 300)
plt.show()

image.png
blood_glucose_levelが210以上の場合、糖尿病に該当する対象者しかおらず、糖尿病に該当しない対象者は0であることが分かりました。また、糖尿病に該当する対象者は、全てblood_glucose_levelが120以上でした。

3.2.3.8. smoking_historyの可視化 -目的変数との相関性-

最後に、smoking_historyと目的変数 (diabetes)の関係について可視化しました。

# smoking_historyについて可視化する。
sns.countplot(
    x='smoking_history',
    hue='diabetes',
    data=all_df
    )
plt.legend(title='diabetes', loc='upper right')
plt.show()

image.png

# smoking_history事の糖尿病に該当する対象者の割合
display(pd.crosstab(all_df['smoking_history'], all_df['diabetes'], normalize='index'))

image.png
No Infoでは糖尿病に該当する対象者の割合が低く、formerでは糖尿病に該当する対象者の割合が多いことが分かりました。

3.3. 検証データの作成及び学習と評価

まずは、全てのデータをそのまま用いて検証データを作成し、ロジスティック回帰の学習と評価を行いました。

3.3.1. 検証データの作成

カテゴリカル変数に対しOne-Hot Encodingを行い、モデルが扱えるよう変換しました。

encode_df = all_df.copy()

# カテゴリカル変数をOne-Hot Encodeingで数値化
encode_df = pd.get_dummies(encode_df, columns=['gender', 'smoking_history'])

未知のデータに対する予測性能(汎化性能)を評価するために、数値化したラベル付きデータ(訓練データ)の一部を、テストデータとしました。
ここでは、ホールドアウト法を用いて訓練データとテストデータに7:3で分割しました。

from sklearn.model_selection import train_test_split

# trainとtargetをデータとラベルに分割
X = encode_df.drop('diabetes', axis=1)  # 特徴量(データ)
y = encode_df['diabetes']  # ラベル

# 訓練データとテストデータに分割
train_X, test_X, train_y, test_y = train_test_split(
    X, y, test_size=0.3, random_state=42)

3.3.2. ロジスティック回帰の学習と評価

学習に用いるデータが準備できたので、予測モデルを構築していきました。
今回用いたデータは二値分類問題なので、最初の学習モデルにはロジスティック回帰を用いました。

from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# モデルを定義し学習
model = LogisticRegression()
model.fit(train_X, train_y)

# 訓練データに対しての予測を行い、正答率を算出
y_pred_train = model.predict(train_X)
train_accuracy = accuracy_score(train_y, y_pred_train)
print("訓練データに対する正答率:", train_accuracy)

# 評価用データを予測
y_pred_test = model.predict(test_X)

# 予測結果を正答率で評価
test_accuracy = accuracy_score(test_y, y_pred_test)
print("テストデータに対する正答率:", test_accuracy)

image.png
テストデータに対する正答率として、95.73%という値が出ました。

3.4. 特徴量エンジニアリング_1

3.4.1. データの可視化から仮説を立てる

データの可視化から、糖尿病に該当する対象者には以下のような関連があるのではないかと仮定を立てました。

  • 60歳付近まで年齢とともに増加
  • genderによる顕著な違いはない
  • 高血圧と心疾患に関係する
  • bmiが20以下は少なく、30以上で多い(特に40以上で多い)
  • HbA1c_levelが7以上で多い
  • blood_glucose_levelが210以上で多い
  • 喫煙歴と関係する

3.4.2. 分析方針を立てる

立てた仮説から、顕著な違いがなかったgenderのみを特徴量から除き、その他の特徴量からモデル学習を進めることにしました。

また、ageは80が、bmiは27.00-27.99の値が、他の値に対して特出して多かったため、以下のように区分分けを行い、新たな特徴量を作成することにしました。

  • age: 区分分け (19歳以下、20代+30代、40代+50代、60代+70代、80歳以上)
  • bmi: 区分分け (10.00-19.99, 20.00-29.99, 30.00-39.99, 40.00-100.00)

3.4.3. 新たな特徴量の作成

3.4.3.1. ageの区分分け

ageを19歳以下、20代+30代、40代+50代、60代+70代、80歳以上で区分分けしました。

age_size_df = all_df.copy()

# ビンの範囲をリストで指定
age_bins = [0, 19, 39, 59, 79, 89]

# pd.cut を使用して離散的なカテゴリに変換
age_size_df['age'] = pd.cut(all_df['age'], bins=age_bins, labels=['<20', '20<40', '40<60', '60<80', '80<'])

display(age_size_df['age'].value_counts(ascending=False,normalize=True))
sns.histplot(x='age', data=age_size_df)

image.png

3.4.3.2. bmiの区分分け

bmiを10.00-19.99, 20.00-29.99, 30.00-39.99, 40.00-100.00で区分分けしました。

bmi_df = age_size_df.copy()

# ビンの範囲をリストで指定
bmi_bins = [10, 19.99, 29.99, 39.99, 100]

# pd.cut を使用して離散的なカテゴリに変換
bmi_df['bmi'] = pd.cut(all_df['bmi'], bins=bmi_bins, labels=['10.00-19.99', '20.00-29.99', '30.00-39.99', '40.00-100.00'])

display(bmi_df['bmi'].value_counts(ascending=False,normalize=True))
sns.histplot(x='bmi', data=bmi_df)

image.png

3.4.4. データセットの準備

データセットの準備として、以下を行いました。

  • 特徴量からgenderを削除
  • カテゴリカル変数をOne-Hot Encodeingで数値化
  • データの正規化としてHbA1c_levelとblood_glucose_levelについて0-1の範囲とした
all_2_df = bmi_df.copy()

# 特徴量からgenderを削除
all_2_df.drop("gender",axis=1,inplace=True)

# カテゴリカル変数をOne-Hot Encodeingで数値化
all_2_df = pd.get_dummies(all_2_df, columns= ['smoking_history', 'bmi', 'age'])

# データを正規化し、	HbA1c_levelとblood_glucose_levelについて0-1の範囲とします。
all_2_df['HbA1c_level'] = (all_2_df['HbA1c_level'] - all_2_df['HbA1c_level'].min()) / (all_2_df['HbA1c_level'].max() - all_2_df['HbA1c_level'].min())
all_2_df['blood_glucose_level'] = (all_2_df['blood_glucose_level'] - all_2_df['blood_glucose_level'].min()) / (all_2_df['blood_glucose_level'].max() - all_2_df['blood_glucose_level'].min())
all_2_df.head()

データの上5行を表示すると以下のようになりました。
image.png
image.png
image.png

3.4.5. ロジスティック回帰の学習

新たに作成したデータセットを訓練データとテストデータに分割し、ロジスティック回帰の学習と評価を行いました。

from sklearn.model_selection import train_test_split

# 特徴量をデータとラベルに分割
X = all_2_df.drop('diabetes', axis=1)  # データ
y = all_2_df['diabetes']  # ラベル

# 訓練データとテストデータに分割
train_X, test_X, train_y, test_y = train_test_split(
    X, y, test_size=0.3, random_state=42)

train_y = np.array(train_y).reshape(-1,1)
test_y = np.array(test_y).reshape(-1,1)
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# モデルを定義し学習
model = LogisticRegression()
model.fit(train_X, train_y)

# 訓練データに対しての予測を行い、正答率を算出
y_pred_train = model.predict(train_X)
train_accuracy = accuracy_score(train_y, y_pred_train)
print("訓練データに対する正答率:", train_accuracy)

# 評価用データを予測
y_pred_test = model.predict(test_X)

# 予測結果を正答率で評価
test_accuracy = accuracy_score(test_y, y_pred_test)
print("テストデータに対する正答率:", test_accuracy)

image.png
テストデータに対する正答率は、95.89%と出ました。わずかですが、最初の特徴量よりも正答率が上がりました。

3.4.6. KFoldを用いた予測モデルの評価

これまでは、データを訓練用とテスト用に一定の割合で分割する、ホールドアウト法を用いてテストデータを作成してきました。しかしこの手法は、データの分割方法によって偶然分布の偏りが生まれてしまい正確な評価を行えない可能性があります。
そこで、新たなモデルの評価方法として、KFoldを用いてデータの分割を行いました。

KFoldは、用意したデータセットをk個に分割し、 そのうちの1つを評価用データ、残りのk-1個を訓練データとして使用します。分割した分だけ、学習と評価を繰り返し、得られるk個のモデルと性能評価から平均性能を算出します。

分割数3のランダム抽出のKFoldを実装したロジスティック回帰の学習を実施し、作成した予測モデルの評価を行いました。

from sklearn.model_selection import KFold

cv = KFold(n_splits=3, random_state=0, shuffle=True)

train_acc_list = []
test_acc_list = []

train = X
target = y

for i, (trn_index, test_index) in enumerate(cv.split(train, target)):
    print(f'Fold : {i}')
    X_train, X_test = train.loc[trn_index], train.loc[test_index]
    y_train, y_test = target[trn_index], target[test_index]

    model = LogisticRegression()
    model.fit(X_train, y_train)

    # Train Part
    y_pred_train = model.predict(X_train)
    train_accuracy = accuracy_score(y_train, y_pred_train)
    print("訓練データに対する正答率:", train_accuracy)
    train_acc_list.append(train_accuracy)

    # Test Part
    y_pred_test = model.predict(X_test)
    test_accuracy = accuracy_score(y_test, y_pred_test)
    print("テストデータに対する正答率:", test_accuracy)
    test_acc_list.append(test_accuracy)

print('-' * 10 + 'Result' + '-' * 10)
print(f'Train_acc : {train_acc_list} , Ave : {np.mean(train_acc_list)}')
print(f'Test_acc : {test_acc_list} , Ave : {np.mean(test_acc_list)}')

image.png
テストデータに対する正答率の平均値は、96.00%と出ました。

3.5. 特徴量エンジニアリング_2

これまで、genderを特徴量から除き、ageとbmiの区分を変更したデータセットで予測モデルを作成しましたが、異なる特徴量を作成して評価を行うことにしました。

異なる特徴量として、ageとbmiに正の相関が見られたこと、またageとgmiの値の範囲が近いことから、ageとbmiを足した新たな特徴量を作成することにしました。

3.5.1. ageとbmiを足した新たな特徴量の作成

age_bmi_df = all_df.copy()

# "age"列と"bmi"列の値を足して新しい列"age+bmi"を作成
age_bmi_df['age+bmi'] = all_df['age'] + all_df['bmi']
age_bmi_df = age_bmi_df.drop(['age', 'bmi'], axis=1)

# age+bmiについてdiabetesごとに可視化
import seaborn as sns

fig = sns.FacetGrid(age_bmi_df, col='diabetes', hue='diabetes', height=4)
fig.map(sns.histplot, 'age+bmi', bins=80, kde=False)

plt.xlim(10, 170)
plt.show()

image.png
糖尿病に該当しない対象者はage+bmiが75付近に向けて増加し、糖尿病に該当する対象者はage+bmiが100付近に向けて増加している分布が認められました。

3.5.2. 新たな特徴量を使った学習及び評価

3.5.2.1. データセットの準備

カテゴリカル変数のOne-Hot Encodingとデータの正規化を行い、新たな特徴量を組み込んだデータセットを作成しました。

all_3_df = age_bmi_df.copy()

# カテゴリカル変数をOne-Hot Encodeingで数値化
all_3_df = pd.get_dummies(all_3_df, columns= ['smoking_history'])
all_3_df.drop("gender",axis=1,inplace=True)

# データを正規化し、	HbA1c_levelとblood_glucose_level、age+bmiについて0-1の範囲とする
all_3_df['HbA1c_level'] = (all_3_df['HbA1c_level'] - all_3_df['HbA1c_level'].min()) / (all_3_df['HbA1c_level'].max() - all_3_df['HbA1c_level'].min())
all_3_df['blood_glucose_level'] = (all_3_df['blood_glucose_level'] - all_3_df['blood_glucose_level'].min()) / (all_3_df['blood_glucose_level'].max() - all_3_df['blood_glucose_level'].min())
all_3_df['age+bmi'] = (all_3_df['age+bmi'] - all_3_df['age+bmi'].min()) / (all_3_df['age+bmi'].max() - all_3_df['age+bmi'].min())

3.5.2.2. ロジスティック回帰の学習と評価

まず、準備したデータセットをホールドアウト法で訓練データとテストデータに分割し、ロジスティック回帰の学習と評価を行いました。

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# trainとtargetをデータとラベルに分割
X = all_3_df.drop('diabetes', axis=1)  # 特徴量(データ)
y = all_3_df['diabetes']  # ラベル

# 訓練データとテストデータに分割
train_X, test_X, train_y, test_y = train_test_split(
    X, y, test_size=0.3, random_state=42)

train_y = np.array(train_y).reshape(-1,1)
test_y = np.array(test_y).reshape(-1,1)

# モデルを定義し学習
model = LogisticRegression()
model.fit(train_X, train_y)

# 訓練データに対しての予測を行い、正答率を算出
y_pred_train = model.predict(train_X)
train_accuracy = accuracy_score(train_y, y_pred_train)
print("訓練データに対する正答率:", train_accuracy)

# 評価用データを予測
y_pred_test = model.predict(test_X)

# 予測結果を正答率で評価
test_accuracy = accuracy_score(test_y, y_pred_test)
print("テストデータに対する正答率:", test_accuracy)

image.png
テストデータに対する正答率が95.88%と出ました。

次に、KFoldを用いてデータセットを3個に分割し、ロジスティック回帰の学習と評価を行いました。

cv = KFold(n_splits=3, random_state=0, shuffle=True)

train_acc_list = []
test_acc_list = []

train = X
target = y

for i, (trn_index, test_index) in enumerate(cv.split(train, target)):
    print(f'Fold : {i}')
    X_train, X_test = train.loc[trn_index], train.loc[test_index]
    y_train, y_test = target[trn_index], target[test_index]

    model = LogisticRegression()
    model.fit(X_train, y_train)

    # Train Part
    y_pred_train = model.predict(X_train)
    train_accuracy = accuracy_score(y_train, y_pred_train)
    print("訓練データに対する正答率:", train_accuracy)
    train_acc_list.append(train_accuracy)

    # Test Part
    y_pred_test = model.predict(X_test)
    test_accuracy = accuracy_score(y_test, y_pred_test)
    print("テストデータに対する正答率:", test_accuracy)
    test_acc_list.append(test_accuracy)

print('-' * 10 + 'Result' + '-' * 10)
print(f'Train_acc : {train_acc_list} , Ave : {np.mean(train_acc_list)}')
print(f'Test_acc : {test_acc_list} , Ave : {np.mean(test_acc_list)}')

image.png
テストデータに対する正答率の平均が95.99%と出ました。

1つ前の特徴量のセットを用いた場合の正答率よりも、正答率がわずかですが下がってしましました。

よって、1つ前の特徴量のセット(genderを除き、age及びbmiに対する区分分けを実施)を用いて、他のモデルの学習と比較を行っていくことにしました。

3.6. モデルの検討

続いて、KFoldを予測モデルの作成に組み込んで、どのアルゴリズムが予測性能が高くなるか検証しました。
今回は、ロジスティック回帰、SVM、MLP、RandomForest、LightGBMの5つのアルゴリズムについて評価をしました。

3.6.1. SVMの学習

まず、サポートベクターマシン (SVM) を用いて予測モデルを作成し、評価しました。

from sklearn.model_selection import train_test_split

# trainとtargetをデータとラベルに分割
X = all_2_df.drop('diabetes', axis=1)  # 特徴量(データ)
y = all_2_df['diabetes']  # ラベル

# 訓練データとテストデータに分割
train_X, test_X, train_y, test_y = train_test_split(
    X, y, test_size=0.3, random_state=42)

train_y = np.array(train_y).reshape(-1,1)
test_y = np.array(test_y).reshape(-1,1)
from sklearn.svm import SVC

cv = KFold(n_splits=3, random_state=0, shuffle=True)

train_acc_list = []
test_acc_list = []

train = X
target = y

# fold毎に学習データのインデックスと評価データのインデックスが得られます
for i ,(trn_index, test_index) in enumerate(cv.split(train, target)):

    print(f'Fold : {i}')
    # データ全体(Xとy)を学習データと評価データに分割
    X_train ,X_test = train.loc[trn_index], train.loc[test_index]
    y_train ,y_test = target[trn_index], target[test_index]

    model = SVC(random_state=0)
    model.fit(
        X_train, y_train
        )

    # Train Part
    y_pred_train = model.predict(X_train)
    train_accuracy = accuracy_score(y_train, y_pred_train)
    print("訓練データに対する正答率:", train_accuracy)
    train_acc_list.append(train_accuracy)

    # Test Part
    y_pred_test = model.predict(X_test)
    test_accuracy = accuracy_score(y_test, y_pred_test)
    print("テストデータに対する正答率:", test_accuracy)
    test_acc_list.append(test_accuracy)

print('-'*10 + 'Result' +'-'*10)
print(f'Train_acc : {train_acc_list} , Ave : {np.mean(train_acc_list)}')
print(f'Test_acc : {test_acc_list} , Ave : {np.mean(test_acc_list)}')

image.png
テストデータに対する正答率の平均が96.10%と出しました。

3.6.2. MLPの学習

次はマルチパーセプトロン(MLP)を用いて予測モデルを作成し、評価しました。

import tensorflow as tf
import random
import os
from tensorflow.keras.utils import to_categorical

# 乱数を固定
tf.random.set_seed(0)
np.random.seed(0)
random.seed(0)
os.environ["PYTHONHASHSEED"] = "0"

cv = KFold(n_splits=3, random_state=0, shuffle=True)

train_acc_list = []
test_acc_list = []

train = X
target = y

# fold毎に学習データのインデックスと評価データのインデックスが得られます
for i ,(trn_index, test_index) in enumerate(cv.split(train, target)):

    print(f'Fold : {i}')
    # データ全体(Xとy)を学習データと評価データに分割
    X_train ,X_test = train.loc[trn_index], train.loc[test_index]
    y_train ,y_test = target[trn_index], target[test_index]

    # モデルを定義
    model = tf.keras.models.Sequential([
        tf.keras.layers.Input(X_train.shape[1]),
        tf.keras.layers.Dense(64, activation='relu'),
        tf.keras.layers.Dropout(0.5),
        tf.keras.layers.Dense(32, activation='relu'),
        tf.keras.layers.Dropout(0.5),
        tf.keras.layers.Dense(16, activation='relu'),
        tf.keras.layers.Dense(2, activation='softmax')
    ])
    # モデルをコンパイル
    model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.01),
                  loss='categorical_crossentropy',
                  metrics=['accuracy'])

    model.fit(
        X_train, to_categorical(y_train),
        batch_size=256, epochs=100, verbose=False
        )

    y_pred_train = np.argmax(model.predict(X_train),axis=1)
    train_acc = accuracy_score(
        y_train, y_pred_train
        )
    print(train_acc)
    train_acc_list.append(train_acc)

    y_pred_test = np.argmax(model.predict(X_test),axis=1)
    test_acc = accuracy_score(
        y_test, y_pred_test
        )
    print(test_acc)
    test_acc_list.append(test_acc)


print('-'*10 + 'Result' +'-'*10)
print(f'Train_acc : {train_acc_list} , Ave : {np.mean(train_acc_list)}')
print(f'Test_acc : {test_acc_list} , Ave : {np.mean(test_acc_list)}')

image.png
テストデータに対する正答率の平均が96.85%と出ました。

3.6.3. RandomForestの学習

次はRandomForestを用いて予測モデルを作成し、評価しました。
RandomForestは、アンサンブル学習法(複数の分類器を集めて構成される分類器)を用いたアルゴリズムの1つで、複数の決定木を構築し、分類の結果をそれらの予測の多数決で決定します。

from sklearn.ensemble import RandomForestClassifier

cv = KFold(n_splits=3, random_state=0, shuffle=True)

train_acc_list = []
test_acc_list = []

train = X
target = y

# fold毎に学習データのインデックスと評価データのインデックスが得られます
for i ,(trn_index, test_index) in enumerate(cv.split(train, target)):

    print(f'Fold : {i}')
    # データ全体(Xとy)を学習データと評価データに分割
    X_train ,X_test = train.loc[trn_index], train.loc[test_index]
    y_train ,y_test = target[trn_index], target[test_index]

    model = RandomForestClassifier(random_state=0)
    model.fit(
        X_train, y_train
        )

    y_pred_train = model.predict(X_train)
    train_acc = accuracy_score(
        y_train, y_pred_train
        )
    print(train_acc)
    train_acc_list.append(train_acc)

    y_pred_test = model.predict(X_test)
    test_acc = accuracy_score(
        y_test, y_pred_test
        )
    print(test_acc)
    test_acc_list.append(test_acc)


print('-'*10 + 'Result' +'-'*10)
print(f'Train_acc : {train_acc_list} , Ave : {np.mean(train_acc_list)}')
print(f'Test_acc : {test_acc_list} , Ave : {np.mean(test_acc_list)}')

image.png
テストデータに対する正答率の平均が96.60%と出ました。

3.6.4. LightGBMの学習

次はLightGBMを用いて予測モデルを作成し、評価しました。

import lightgbm as lgb

cv = KFold(n_splits=3, random_state=0, shuffle=True)

train = X
target = y

train_acc_list = []
test_acc_list = []


# ハイパーパラメーターを定義
lgb_params = {
    "objective":"binary",
    "metric": "binary_error",
    "force_row_wise" : True,
    "seed" : 0,
    }

# fold毎に学習データのインデックスと評価データのインデックスが得られます
for i ,(trn_index, test_index) in enumerate(cv.split(train, target)):

    print(f'Fold : {i}')
    # データ全体(Xとy)を学習データと評価データに分割
    X_train ,X_test = train.loc[trn_index], train.loc[test_index]
    y_train ,y_test = target[trn_index], target[test_index]

    # LigthGBM用のデータセットを定義
    lgb_train = lgb.Dataset(X_train, y_train)
    lgb_test = lgb.Dataset(X_test, y_test)

    valid_sets = [lgb_train, lgb_test]


    # 他の設定パラメータを設定
    lgb_params = {
        "objective": "binary",
        "metric": "binary_error",
        "force_row_wise": True,
        "seed": 0,
    }


    model = lgb.train(
        params=lgb_params,
        train_set=lgb_train,
        valid_sets=valid_sets,
        num_boost_round=100,
        callbacks=[
            lgb.early_stopping(stopping_rounds=50, verbose=True),
            lgb.log_evaluation(100),
        ],
        )

    y_pred = model.predict(X_train)
    train_acc = accuracy_score(
        # y_predは0〜1の確率になっています。
        y_train, np.where(y_pred>=0.5, 1, 0)
        )
    print(train_acc)
    train_acc_list.append(train_acc)

    y_pred_test = model.predict(X_test)
    test_acc = accuracy_score(
        y_test, np.where(y_pred_test>=0.5, 1, 0)
        )
    print(test_acc)
    test_acc_list.append(test_acc)


print('-'*10 + 'Result' +'-'*10)
print(f'Train_acc : {train_acc_list} , Ave : {np.mean(train_acc_list)}')
print(f'Test_acc : {test_acc_list} , Ave : {np.mean(test_acc_list)}')

image.png
テストデータの正答率の平均値が97.21%と出ました。

3.6.5. モデルの評価/比較

これまでのアルゴリズムの実装と評価の結果から、今回テストデータに対して最も正答率が高かったアルゴリズムはLightGBMでした。
また、訓練データの正答率と比較すると、正答率間に大きな乖離が見られませんでした。
従って、汎化性能が高く、予測性能も高いモデルが作成できたと結論づけることができます。

3.7. 混合行列及び適合率、再現率、F値の可視化

LightGBMを用いた予測結果について、真陽性(True Positive)、真陰性(True Negative)、偽陽性(False Positive)、偽陰性(False Negative)の4つの観点で分類した、混合行列を可視化しました。
また、適合率、再現率、F値についても可視化しました。

3.7.1. 混合行列の可視化

まず、混合行列を可視化しました。

from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix

# trainとtargetをデータとラベルに分割
X = all_2_df.drop('diabetes', axis=1)  # 特徴量(データ)
y = all_2_df['diabetes']  # ラベル

# 訓練データとテストデータに分割
train_X, test_X, train_y, test_y = train_test_split(
    X, y, test_size=0.3, random_state=42)

train_y = np.array(train_y).reshape(-1,1)
test_y = np.array(test_y).reshape(-1,1)

y_pred_test = model.predict(test_X)
y_pred_binary = np.where(y_pred_test>=0.5, 1, 0)   # 予測を二値化
cm = confusion_matrix(test_y, y_pred_binary)
ax= plt.subplot()
sns.heatmap(cm, annot=True, fmt='g', ax=ax);
ax.set_xlabel('Predicted labels');ax.set_ylabel('True labels');
ax.set_title('Confusion Matrix');
ax.xaxis.set_ticklabels(['not_diabetes','diabetes']); ax.yaxis.set_ticklabels(['not_diabetes','diabetes']);

image.png
偽陽性(右上)は少ないですが、偽陰性(左下)が多く、再現率が低いことが分かりました。

3.7.2. 適合率、再現率、F値の可視化

次に、適合率、再現率、F値を可視化しました。

from sklearn.metrics import precision_score, recall_score, f1_score

# y_trueには正解のラベルを、y_predには予測結果のラベルをそれぞれ渡します
print("Precision: {:.3f}".format(precision_score(test_y, y_pred_binary)))
print("Recall: {:.3f}".format(recall_score(test_y, y_pred_binary)))
print("F1: {:.3f}".format(f1_score(test_y, y_pred_binary)))

image.png
対象者のほとんどが糖尿病ではないため、適合率は99.9%と高い値となっていましたが、偽陰性が多く、再現率は66.9%と低い値となっていました。
糖尿病の予測モデルとしては、偽陽性が多少多くなったとしても、偽陰性が少ない(=再現率が高い)ことが望ましいと考えられます。

そこで、再現率を上げるために、LightGBMのパラメーターを調整しました。

3.7. パラメータチューニング

3.7.1. LightGBMのパラメータチューニング

再現率を向上させるために、LightGBMのパラメーターを調整しました。

import lightgbm as lgb
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.metrics import recall_score

# データの読み込みや前処理を行う
train = X
target = y

train_acc_list = []
test_acc_list = []

# ハイパーパラメーターを定義
lgb_params = {
    "objective":"binary",
    "metric": "binary_error",
    "force_row_wise" : True,
    "seed" : 0,
    }

# fold毎に学習データのインデックスと評価データのインデックスが得られます
for i ,(trn_index, test_index) in enumerate(cv.split(train, target)):

    print(f'Fold : {i}')
    # データ全体(Xとy)を学習データと評価データに分割
    X_train ,X_test = train.loc[trn_index], train.loc[test_index]
    y_train ,y_test = target[trn_index], target[test_index]

    # LigthGBM用のデータセットを定義
    lgb_train = lgb.Dataset(X_train, y_train)
    lgb_test = lgb.Dataset(X_test, y_test)

    valid_sets = [lgb_train, lgb_test]


# scale_pos_weightの計算
scale_pos_weight = sum(y_train == 0) / sum(y_train == 1)

# LightGBMモデルの定義
params = {
    'boosting_type': 'gbdt',
    'objective': 'binary',
    'metric': 'binary_logloss',
    'is_unbalance': True,  # クラスの不均衡を考慮
    'num_leaves': 200, #
    'learning_rate': 0.05, #
    'feature_fraction': 0.9,
    'bagging_fraction': 0.8,
    'bagging_freq': 5,
    'verbose': 0
}

# データの学習
train_data = lgb.Dataset(X_train, label=y_train)
test_data = lgb.Dataset(X_test, label=y_test)
model = lgb.train(params, train_data, valid_sets=[test_data])

# テストデータでの再現率の計算
y_pred_test = model.predict(X_test, num_iteration=model.best_iteration)
threshold = 0.5  # 任意のしきい値
y_pred_class = [1 if pred > threshold else 0 for pred in y_pred_test]
recall = recall_score(y_test, y_pred_class)
print(f"Recall: {recall}")

image.png

3.7.2. 適合率、再現率、F値の可視化

パラメーターを調整したLightGBMについて、適合率、再現率、F値の可視化しました。

from sklearn.model_selection import train_test_split

# trainとtargetをデータとラベルに分割
X = all_2_df.drop('diabetes', axis=1)  # 特徴量(データ)
y = all_2_df['diabetes']  # ラベル

# 訓練データとテストデータに分割
train_X, test_X, train_y, test_y = train_test_split(
    X, y, test_size=0.3, random_state=42)

train_y = np.array(train_y).reshape(-1,1)
test_y = np.array(test_y).reshape(-1,1)
y_pred_test = model.predict(test_X)
y_pred_binary = np.where(y_pred_test>=0.5, 1, 0)   # 予測を二値化
from sklearn.metrics import precision_score, recall_score, f1_score

# y_trueには正解のラベルを、y_predには予測結果のラベルをそれぞれ渡します
print("Precision: {:.3f}".format(precision_score(test_y, y_pred_binary)))
print("Recall: {:.3f}".format(recall_score(test_y, y_pred_binary)))
print("F1: {:.3f}".format(f1_score(test_y, y_pred_binary)))

image.png
パラメータを調整した結果、再現率が93.3%まで高くなりました。

3.7.2. 混合行列の可視化

パラメーターを調整したLightGBMについて、混合行列を可視化しました。

from sklearn.metrics import confusion_matrix

y_pred_test = model.predict(test_X)
y_pred_binary = np.where(y_pred_test>=0.5, 1, 0)   # 予測を二値化

cm = confusion_matrix(test_y, y_pred_binary)
ax= plt.subplot()
sns.heatmap(cm, annot=True, fmt='g', ax=ax);
ax.set_xlabel('Predicted labels');ax.set_ylabel('True labels');
ax.set_title('Confusion Matrix');
ax.xaxis.set_ticklabels(['not_diabetes','diabetes']); ax.yaxis.set_ticklabels(['not_diabetes','diabetes']);

image.png
正答率、適合率が下がり、偽陽性が増えてしまいましたが、再現率が高くなったことで、糖尿病に該当する対象者のうち偽陰性が6.7%まで減りました。

偽陰性が少なくなったことから、糖尿病の予測モデルとしては、パラメーター調整後のモデルの方が適していると考えられます。


4. おわりに

今回、初めてデータ分析のプログラムを一通り自分で書いてみました。
100000という膨大なデータを一つ一つ目で見て確認していくことは難しいですが、プログラムで可視化することで、各特徴量の傾向が分かっていくのが単純に面白かったです。
また、モデルの選択やパラメーターの調整などで、正答率や適合率の数値が変わっていくのが、当たり前のことなのだとは思うのですが、プログラム初心者にはとても楽しかったです。

受講した講座では、主に、高い正答率を出すためのモデルの作成フローを学習したため、今回記載したフローが、高い再現率を求める場合のフローとして適しているのかは分からなかったのですが、再現率として93%までは出すことができました。

まだ(株)Aidemyでの受講期間が残っているため、他の講座も受講して学びを広げるとともに、
他の方のプログラムやブログなども見て、データ分析について理解を深めていきたいなと思います。

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?