Diyabet Veri Seti ile Hastalık Tahmini ve Accuracy Değerini Yükseltme Yolları

Saltuk Bugra Karacan
8 min readAug 19, 2020

Merhaba, bugünkü yazımda Pima Indians Diabetes Database üzerinde bilgileri verilen kişilerin diyabet olup olmadığını tahminleyen bir makine öğrenmesi modeli kuracağız.

Bu projeye Kaggle ve Github hesaplarımdan ulaşabilirsiniz. Soru, görüş ve önerileriniz için de bana LinkedIn’den ulaşabilirsiniz.

Bu veri seti; en az 21 yaşında, kadın ve Pima Indian ırkından insanlardan oluşur. Bu insanların verilen belirli özellikleri ile bu kişilerin diyabet hastası olup olmadığını tahminleyeceğiz.

Veri Setinin Hikayesi

Veri setinden bir görüntü:

İşte bu veri setinde bize verilen değişkenler ve anlamları:

Pregnancies = Hamile kalma sayısı

Glucose = Glikoz

Blood Pressure = Kan basıncı

Skin Thickness = Deri kalınlığı

Insulin = İnsülin

BMI (Body Mass Index) = Beden kitle endeksi

Diabetes Pedigree Function = Soyumuzdaki kişilere göre diyabet olma ihtimalimizi hesaplayan bir fonksiyon

Age = Yaş

Outcome = Diyabet olup olmadığı bilgisi (bu bizim target yani hedefimiz amacımız bunu tahminlemek)

Şimdi projenin yapılış kısmına geçebiliriz.

Proje kısmına geçmeden önce bu veri setinde farkedilmesi gereken çok ama çok önemli bir noktayı vurgulamalıyım. Bu önemli olan noktayı projeyi yaparken farkettim ve bana göre bu, verileri girerken yapılmış önemli bir hata.

Üzerinde çalıştığımız “Pima Indians Diabetes Database” içindeki 0 değerlerinin tamamı aslında 0 değil. Boş olan değerler de 0 ile doldurulmuş. Bunu bir insanın beden kitle endeksinin nasıl 0 olabileceğini düşünürken fark ettim :). Bu tür anlamsız verilere gürültülü veri (noisy data) denir.

df.describe().T

Yukarıda veri setimizin describe() fonksiyonunu görüyorsunuz. Bu tabloda “min”, o değişkenin içinde bulunan en küçük sayıyı gösterir.

Pregnancies, Glucose, BloodPressure, SkinThickness, Insulin ve BMI değişkenlerinin en küçük değerlerinin 0 olduğunu görüyoruz. Burada Pregnancies değişkeni dışında bu saydığımız değişkenlerin hiçbirinin 0 olma ihtimali yok.

Projeye geçiş yapalım.

import numpy as np 
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split, GridSearchCV, cross_val_score
from sklearn.metrics import accuracy_score
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import GradientBoostingClassifier
from lightgbm import LGBMClassifier
from sklearn.model_selection import KFold
from xgboost import XGBClassifier
df = pd.read_csv("diabetes.csv") # gerekli kütüphaneleri ekledikten sonra veri setini okutup df ismine atadık.

Yukarıda gerekli olan kütüphanelerimizi ekledikten sonra veri setini df isimli değişkenimize atadık.

Şimdi veri setimizin ilk 5 satırını görelim.

df.head()

Şimdi bir önceki projemde de yaptığım gibi bir döngü yazıp, bütün algoritmaları sırayla çalıştıracağız ve veri seti üzerinde hiç oynamadan elde ettiğimiz doğruluk oranlarını göreceğiz.

models = []

models.append(("LR", LogisticRegression()))
models.append(('KNN', KNeighborsClassifier()))
models.append(('SVR', SVC()))
models.append(('CART', DecisionTreeClassifier()))
models.append(('RandomForests', RandomForestClassifier()))
models.append(('GradientBoosting', GradientBoostingClassifier()))
models.append(('XGBoost', XGBClassifier()))
models.append(('Light GBM', LGBMClassifier()))

models isimli bir listenin içine “tuple” şeklinde olan algoritmaları ekliyorum. Mesela (“LR”, LogisticRegression()) yani (algoritmaİsmi, Algoritma) şeklinde ekleme yapıyorum. Bu listeyi aşağıda kullanacağız.

X = df.drop("Outcome",axis=1)
y = df["Outcome"]

X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.2,random_state=46)
# X ve y ayrımı yapıp train-test-split ile verimizi train ve test şeklinde ayırıyoruz. test %20, train %80 şeklinde ayrılıyor.
for name,model in models:
mod = model.fit(X_train,y_train) #trainleri modele fit etmek
y_pred = mod.predict(X_test) # tahmin
acc = accuracy_score(y_test, y_pred) #rmse hesabı
cvscore = cross_val_score(model, X,y, cv = 10).mean()
print("Holdout Method:",end=" ")
print(name,acc) #yazdırılacak kısım
print("Cross Val Score",end=" ")
print(name,cvscore)
print("------------------------------------")
# yukarıdaki döngüde models listesinin içinde geziyoruz. "name" tuple'ın ilk elemanı, model ise 2. elemanı oluyor. Yani "LR" = name, LogisticRegression() = model. döngü içerisinde klasik fit predict işlemlerimizi yapıyoruz. Daha sonra bu bir sınıflandırma algoritması olduğu için accuracy (doğruluk) oranını hesaplıyoruz. Ekstra olarak cross validation ile de bütün veri üzerinde bir çalışma yapıyoruz.

Yukarıdaki döngü sayesinde bütün modeller için hem holdout metodu ile hem cross validation metodu ile doğruluk oranlarını hesaplamış olduk.

Gördüğünüz gibi accuracy değerleri minimum %68.83 ile maksimum %77.34 arasında.

Şimdi bu veri seti üzerinde yapacağımız oynamalarla bu accuracy değerlerini yükseltmeye çalışacağız.

Preprocessing

df[["Glucose","BloodPressure","SkinThickness","Insulin","BMI"]] = df[["Glucose","BloodPressure","SkinThickness","Insulin","BMI"]].replace(0,np.NaN)

Yukarıdaki kod bloğunda 5 değişkendeki 0 olan değerlerin tamamını NaN yani boş anlamına gelen bir hale çevirdik.

Yazımın başlarında da belirttiğim gibi 0 olması imkansız olan bazı değerler vardı. Bu değerleri bizim bir şekilde 0 değil boş değer olarak göstermemiz gerekir. Çünkü yaşayan bir insanın Glikoz oranı, kan basıncı, deri kalınlığı, insülini ve beden kitle endeksi kesinlikle 0 olamaz.

Şimdi 0 yerine NaN atadığımız kaç adet satır var görelim.

df.isnull().sum() # Her değişkenin kaç adet NaN değeri olduğunu gösteren kod bloğu

Yukarıda gördüğümüz gibi çok fazla gürültülü veri var. 768 satır olan bir veri setinde 374 satırın Insulin değişkeni aslında boş ama veri setine 0 olarak girildiği için gürültü oluşturuyor.

Şimdi bu NaN yani boş verileri doldurmak veya silmek zorundayız. Çünkü algoritmaya NaN veri girişi yaparsak hatayla karşılaşırız.

Boş veri bulunduran satırları silmek bu veri seti için büyük bir hata olur. Çünkü veri setimizde zaten 768 adet satır var. Bu bile yeterli değilken bunu bir de azaltmak proje için iyi sonuçlar doğurmaz. 768 olan satır sayımız 392 olur.

df[["Glucose","BloodPressure","SkinThickness","Insulin","BMI"]].dropna()

Yukarıda da söylediğim gibi; bu yolu asla önermiyorum o yüzden üstteki kod bloğu projeye dahil değil, sadece kaç adet veri kaldığını görebilmeniz için ekledim.

Şimdi verileri silme yerine doldurma işine bakalım. Bunun için yine bir döngü oluşturdum ve Glucose, BloodPressure, SkinThickness, Insulin, BMI değişkenlerini doldurdum.

naValues =["Glucose", "BloodPressure", "SkinThickness", "Insulin", "BMI"]

for i in naValues:
df[i][(df[i].isnull()) & (df["Outcome"] == 0)] = df[i][(df[i].isnull()) & (df["Outcome"] == 0)].fillna(df[i][df["Outcome"] == 0].mean())
df[i][(df[i].isnull()) & (df["Outcome"] == 1)] = df[i][(df[i].isnull()) & (df["Outcome"] == 1)].fillna(df[i][df["Outcome"] == 1].mean())

For döngüsü ile doldurulacak değişkenler içinde gezip, Outcome değeri 0 olan değerlerin ortalamasını Outcome değeri 0 olan boş değerlere; Outcome değeri 1 olan değerlerin ortalamasını Outcome değeri 1 olan boş değerlere atadım.

Şimdi boş değerleri sayarsak hiç boş değer kalmadığını göreceğiz, çünkü yukarıda doldurduk.

df.isnull().sum()

Eğer böyle bir döngü yazmasaydık şu şekilde uzun bir kod yazmak zorunda kalacaktık.

Şimdi aykırı değerleri belirleyip onları baskılayacağız. IQR ile aykırı değerleri bulma metodunu bir önceki yazımda açıklamıştım. O yazıya gitmek için tıklayın.

Aykırı Değerler (IQR)

for feature in df:

Q1 = df[feature].quantile(0.05)
Q3 = df[feature].quantile(0.95)
IQR = Q3-Q1
upper = Q3 + 1.5*IQR
lower = Q1 - 1.5*IQR

if df[(df[feature] > upper) | (df[feature] < lower)].any( axis=None):
print(feature,"yes")
print(df[(df[feature] > upper) | (df[feature] < lower) ].shape[0])
print("lower",lower,"\nupper",upper)
df.loc[df[feature] > upper,feature] = upper # baskılama
else:
print(feature, "no")

Yukarıda gördüğünüz gibi aykırı değer bulundurmayan değişkenlerin yanında “no”, bulunduranların yanında ise “yes” yazıyor. Alt satırlara baktığınızda da göreceksiniz, alt ve üst sınırları da sayısal olarak belirtilmiş. Yukarıdaki kod bloğunda df.loc[df[feature] > upper,feature] = upper kısmında bütün üst sınırdan büyük sayıları üst sınıra eşitledik. Buna baskılama denir.

Feature Engineering

Feature Engineering kısmında belirli özellikleri segmentlere ayıracağız. Örneğin beden kitle endeksi 18.5'dan düşük olan kişileri “underweight” olarak sınıflandırmak gibi…

df['BMIRanges'] = pd.cut(x=df['BMI'], bins=[0,18.5,25,30,100],labels = ["Underweight","Healthy","Overweight","Obese"])

Yukarıdaki kod bloğu df dataFrame’e “BMIRanges” adında yeni bir sütun (değişken) ekledi.

Bu değişken: Beden kitle endekslerine göre insanları Underweight (18.5-), Healthy(18.5–25), Overweight(25–30) veya Obese(30+) olarak sınıflandırdı.

Yeni bir sütun (değişken) daha ekleyeceğiz. Bu değişken, insülin değerlerinin normal mi anormal mi olduğunu belirtecek.

def set_insulin(row):
if row["Insulin"] >= 16 and row["Insulin"] <= 166:
return "Normal"
else:
return "Abnormal"
df = df.assign(INSULIN_DESC=df.apply(set_insulin, axis=1)) #burada yukarıdaki fonksiyonu df'e uyguluyoruz.

Bu kod bloğuna göre, Insulin değeri 16 ile 166 arasında olan değerler normal; Bu aralığın dışında olan değerler ise anormal olarak kabul edilir.

Şimdi Glikoz ile ilgili bir sınıflandırma yapacağız.

df['NewGlucose'] = pd.cut(x=df['Glucose'], bins=[0,70,99,126,200],labels = ["Low","Normal","Secret","High"])

Yine yeni bir sütun ekledik ve sınıflandırma yaptık. Bu değişken glikoz değerlerini: Low(70-), Normal(70–99), Secret(99–126) ve High (126–200) olarak sınıflandırır.

Bu değişkenler eklendikten sonra DataFrame’imizde şu şekilde gözükürler.

One Hot Encoding

Şimdi, elimizdeki sözel sınıfları sayılara çevirmek için bir önceki yazımda da anlattığım get_dummies (One Hot Encoding) uygulamasını yapıyoruz. Bir önceki yazımdan One Hot Encoding anlatımını görmek için tıklayınız.

df = pd.get_dummies(df,drop_first=True)

Burada aynı şeyleri yazmış olmamak için tekrar yazmıyorum. İlk 5 veriyi görelim ve One Hot Encoding (get_dummies) sonucunda DataFrame’imiz nasıl değişmiş görelim.

DataFrame’in sonuna yukarıda gördüğünüz sütunlar eklenmiş oldu.

Feature Engineering kısmı bitti. Şimdi değişkenleri ölçeklendirme kısmına geçeceğiz.

Değişken Ölçeklendirme

Birçok değişken ölçeklendirme türü vardır. Bugün biz RobustScaler türünü kullanacağız.

from sklearn.preprocessing import RobustScalerr_scaler = RobustScaler()
df_r = r_scaler.fit_transform(df.drop(["Outcome","BMIRanges_Healthy","BMIRanges_Overweight","BMIRanges_Obese","INSULIN_DESC_Normal","NewGlucose_Normal","NewGlucose_Secret","NewGlucose_High"],axis=1))

df_r = pd.DataFrame(df_r, columns=["Pregnancies","Glucose","BloodPressure","SkinThickness","Insulin","BMI","DiabetesPedigreeFunction","Age"])
df = pd.concat([df_r,df[["Outcome","BMIRanges_Healthy","BMIRanges_Overweight","BMIRanges_Obese","INSULIN_DESC_Normal","NewGlucose_Normal","NewGlucose_Secret","NewGlucose_High"]]],axis=1)

RobustScaler ile verimizi ölçeklendirdik. Yani artık verimizde daha küçük sayılarla uğraşacağız ve bu bize algoritmalarda performans sağlayacak.

Yukarıdaki fotoğrafta gördüğünüz DataFrame, verimizin RobustScaler ile ölçeklendirilmiş hali.

Sınıflandırma olan değerlerimize RobustScaler uygulamadık. Çünkü sınıflandırma olan değişkenlerimizde her sayı bir sınıfı temsil ettiği için bir ölçeklemeye ihtiyacımız yok. Bu dataFrame için sınıflandırma değişkenleri sadece 0 ve 1 değişkenlerini alan değişkenlerdir.

Değerlendirme

Şimdi tekrardan başta yaptığımız gibi tüm algoritmaları son haliyle uygulayacağız ve accuracy’deki değişimi gözlemleyeceğiz.

X = df.drop("Outcome",axis=1)
y = df["Outcome"]

X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.2,random_state=46)

for name,model in models:
mod = model.fit(X_train,y_train) #trainleri modele fit etmek
y_pred = mod.predict(X_test) # tahmin
acc = accuracy_score(y_test, y_pred) #rmse hesabı
cvscore = cross_val_score(model, X,y, cv = 10).mean()
print("Holdout Method:",end=" ")
print(name,acc) #yazdırılacak kısım
print("Cross Val Score",end=" ")
print(name,cvscore)
print("------------------------------------")

Şimdi önceki ve sonraki doğruluk oranlarımızı karşılaştıralım.

Sol taraf önceki , sağ taraf ise yaptığımız işlemlerden sonraki doğruluk oranlarımızı gösteriyor. Sizin de gördüğünüz gibi daha temiz veride algoritmalarımız daha iyi çalışıyor.

Sol taraftaki en yüksek accuracy, sağ taraftaki en düşük accuracy’e neredeyse eşit.

Better data beats fancier algorithms.

ÖNEMLİ: Bu yazıyı bir challenge olarak düşünün. Eğer bu bir gerçek hayat projesi ise, target değerimizin üzerinde yapılacak herhangi bir işlem vb. iş kabul edilebilir olmayabilir. Çünkü gerçek hayat projelerinde zaten amacımız target değerini tahminlemektir ve sonuçları elimizde olmayan target değerler üzerinden feature engineering yapmak tabii ki mantıklı değildir.

Bu projenin tamamına Kaggle ve Github hesaplarımdan ulaşabilir ve benimle LinkedIn’den iletişime geçebilirsiniz.

Github: github.com/sbkaracan/diabetesPredictionIncreasingAccuracy/blob/master/classificationdiabetes.ipynb

Kaggle: kaggle.com/saltukbugrakaracan/classificationdiabetes

Linkedin: linkedin.com/in/sbkaracan/

--

--

Saltuk Bugra Karacan

M. Sc. Informatics @ Technical University of Munich | AI Engineer WS @ Retorio