1 Introduction

Diabetes is a chronic, metabolic disease characterized by elevated levels of blood glucose (or blood sugar), which leads over time to serious damage to the heart, blood vessels, eyes, kidneys and nerves. The most common is type 2 diabetes, usually in adults, which occurs when the body becomes resistant to insulin or doesn’t make enough insulin. In the past 3 decades the prevalence of type 2 diabetes has risen dramatically in countries of all income levels. Type 1 diabetes, once known as juvenile diabetes or insulin-dependent diabetes, is a chronic condition in which the pancreas produces little or no insulin by itself.

About 422 million people worldwide have diabetes, the majority living in low-and middle-income countries, and 1.5 million deaths are directly attributed to diabetes each year. Both the number of cases and the prevalence of diabetes have been steadily increasing over the past few decades.

In this LBB we will create a model to predict wether a person could potentially diagnose with diabetes or not based on the variables.

2 Data Preparation

2.1 Load Library

library(gtools)
library(car)
library(caret)
library(dplyr)
library(class)
library(MASS)

options(scipen = 100, max.print = 1e+08)

2.2 Load dataset

diabetes <- read.csv("data_input/diabetes2.csv")

Next we will check all the data structure using glimpse function from dplyr library

glimpse(diabetes)
## Rows: 768
## Columns: 9
## $ Pregnancies              <int> 6, 1, 8, 1, 0, 5, 3, 10, 2, 8, 4, 10, 10, 1, …
## $ Glucose                  <int> 148, 85, 183, 89, 137, 116, 78, 115, 197, 125…
## $ BloodPressure            <int> 72, 66, 64, 66, 40, 74, 50, 0, 70, 96, 92, 74…
## $ SkinThickness            <int> 35, 29, 0, 23, 35, 0, 32, 0, 45, 0, 0, 0, 0, …
## $ Insulin                  <int> 0, 0, 0, 94, 168, 0, 88, 0, 543, 0, 0, 0, 0, …
## $ BMI                      <dbl> 33.6, 26.6, 23.3, 28.1, 43.1, 25.6, 31.0, 35.…
## $ DiabetesPedigreeFunction <dbl> 0.627, 0.351, 0.672, 0.167, 2.288, 0.201, 0.2…
## $ Age                      <int> 50, 31, 32, 21, 33, 30, 26, 29, 53, 54, 30, 3…
## $ Outcome                  <int> 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, …

The data contains 768 rows with 9 columns on it. All the columns are:

  1. Pregnancies : Number of times pregnant
  2. Glucose : Plasma glucose concentration a 2 hours in an oral glucose tolerance test
  3. BloodPressure : Diastolic blood pressure (mm Hg)
  4. SkinThickness : Triceps skin fold thickness (mm)
  5. Insulin : 2-Hour serum insulin (mu U/ml)
  6. BMI : Body mass index (weight in kg/(height in m)^2)
  7. DiabetesPedigreeFunction : Diabetes pedigree function
  8. Age : Subject Age
  9. Outcome : Outcome of the test result. Stand as the target variable. (0 = Negative, 1 = Positive)

As we can see Outcome data type need to be changed into factor because as a target variable, we want it to be either positive or negative.

diabetes_clean <- diabetes %>% 
  mutate(Outcome = as.factor(Outcome))

glimpse(diabetes_clean)
## Rows: 768
## Columns: 9
## $ Pregnancies              <int> 6, 1, 8, 1, 0, 5, 3, 10, 2, 8, 4, 10, 10, 1, …
## $ Glucose                  <int> 148, 85, 183, 89, 137, 116, 78, 115, 197, 125…
## $ BloodPressure            <int> 72, 66, 64, 66, 40, 74, 50, 0, 70, 96, 92, 74…
## $ SkinThickness            <int> 35, 29, 0, 23, 35, 0, 32, 0, 45, 0, 0, 0, 0, …
## $ Insulin                  <int> 0, 0, 0, 94, 168, 0, 88, 0, 543, 0, 0, 0, 0, …
## $ BMI                      <dbl> 33.6, 26.6, 23.3, 28.1, 43.1, 25.6, 31.0, 35.…
## $ DiabetesPedigreeFunction <dbl> 0.627, 0.351, 0.672, 0.167, 2.288, 0.201, 0.2…
## $ Age                      <int> 50, 31, 32, 21, 33, 30, 26, 29, 53, 54, 30, 3…
## $ Outcome                  <fct> 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, …

Outcome type is changed.

Next is checking any missing value.

colSums(is.na(diabetes_clean))
##              Pregnancies                  Glucose            BloodPressure 
##                        0                        0                        0 
##            SkinThickness                  Insulin                      BMI 
##                        0                        0                        0 
## DiabetesPedigreeFunction                      Age                  Outcome 
##                        0                        0                        0

No missing value found in the data.

Next we will check our Outcome distribution

prop.table(table(diabetes_clean$Outcome))
## 
##         0         1 
## 0.6510417 0.3489583

As we can see above our Outcome distribution are 65% for Negative and 35% for Positive. This proportion is still acceptable so we can move into model preparation.

3 Modelling

3.1 Splitting Train-Test

Before creating a model, we need to split our data into 2 types of data. Train and Test. For this case we will use 80% of our data as our train data and 20% for test data. This process is needed because we want make sure our data is useable with the real data, not just modelling data.

set.seed(189)
index <- sample(nrow(diabetes_clean), nrow(diabetes_clean)*0.8)
diabetes_train <- diabetes_clean[index, ]
diabetes_test <- diabetes_clean[-index,]

Next we will check the data proportion

diabetes_train$Outcome %>% table() %>% prop.table()
## .
##         0         1 
## 0.6384365 0.3615635
diabetes_test$Outcome %>% table() %>% prop.table()
## .
##         0         1 
## 0.7012987 0.2987013

Our data proportion for train data are 64% for Negative and 36% for Positive. For our test data the proportions are 70% for Negative and 30% for Positive. This proportion is still acceptable because both proportions value is close enough with our source data

3.2 Creating the Model

We will create using our train data the model with Outcome as the target variable with all 8 other variables as the predictor

model_train <- glm(Outcome ~ .,
                  "binomial",
                  diabetes_train)
summary(model_train)
## 
## Call:
## glm(formula = Outcome ~ ., family = "binomial", data = diabetes_train)
## 
## Coefficients:
##                           Estimate Std. Error z value             Pr(>|z|)    
## (Intercept)              -8.120210   0.794114 -10.225 < 0.0000000000000002 ***
## Pregnancies               0.145194   0.036387   3.990            0.0000660 ***
## Glucose                   0.039174   0.004355   8.994 < 0.0000000000000002 ***
## BloodPressure            -0.014348   0.005700  -2.517               0.0118 *  
## SkinThickness             0.005442   0.007871   0.691               0.4893    
## Insulin                  -0.001580   0.001017  -1.554               0.1203    
## BMI                       0.072402   0.016432   4.406            0.0000105 ***
## DiabetesPedigreeFunction  0.982913   0.326379   3.012               0.0026 ** 
## Age                       0.008162   0.010470   0.780               0.4357    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 803.5  on 613  degrees of freedom
## Residual deviance: 578.5  on 605  degrees of freedom
## AIC: 596.5
## 
## Number of Fisher Scoring iterations: 5

From model above we can see that there are some variables that doesn’t significantly affect the Outcome. So we will use Stepwise regression to improve our model further.

model_step <- step(model_train, direction = "backward")
## Start:  AIC=596.5
## Outcome ~ Pregnancies + Glucose + BloodPressure + SkinThickness + 
##     Insulin + BMI + DiabetesPedigreeFunction + Age
## 
##                            Df Deviance    AIC
## - SkinThickness             1   578.98 594.98
## - Age                       1   579.10 595.10
## <none>                          578.50 596.50
## - Insulin                   1   580.90 596.90
## - BloodPressure             1   584.92 600.92
## - DiabetesPedigreeFunction  1   587.83 603.83
## - Pregnancies               1   595.11 611.11
## - BMI                       1   600.45 616.45
## - Glucose                   1   686.86 702.86
## 
## Step:  AIC=594.98
## Outcome ~ Pregnancies + Glucose + BloodPressure + Insulin + BMI + 
##     DiabetesPedigreeFunction + Age
## 
##                            Df Deviance    AIC
## - Age                       1   579.49 593.49
## - Insulin                   1   580.91 594.91
## <none>                          578.98 594.98
## - BloodPressure             1   584.97 598.97
## - DiabetesPedigreeFunction  1   588.66 602.66
## - Pregnancies               1   595.52 609.52
## - BMI                       1   605.37 619.37
## - Glucose                   1   687.38 701.38
## 
## Step:  AIC=593.49
## Outcome ~ Pregnancies + Glucose + BloodPressure + Insulin + BMI + 
##     DiabetesPedigreeFunction
## 
##                            Df Deviance    AIC
## <none>                          579.49 593.49
## - Insulin                   1   581.73 593.73
## - BloodPressure             1   585.07 597.07
## - DiabetesPedigreeFunction  1   589.43 601.43
## - BMI                       1   605.37 617.37
## - Pregnancies               1   605.56 617.56
## - Glucose                   1   699.77 711.77

Based on the result above as we can see there are 6 variables that significantly affect diabetes. All those variables are Insulin, BloodPressure, DiabetesPedigreeFunction, BMI, Pregnancies, and Glucose.

vif(model_step)
##              Pregnancies                  Glucose            BloodPressure 
##                 1.075479                 1.133265                 1.102047 
##                  Insulin                      BMI DiabetesPedigreeFunction 
##                 1.193310                 1.108309                 1.026547

Based on Multicollinearity above, all variables value is less than 10, which means all variables are not correlated with each others.

3.3 Model Evaluation

Before we evaluate our model, we need to create a prediction for our test data by using predict function.

diabetes_test$predict <- predict(model_step,
                                 diabetes_test,
                                 "response")
head(as.data.frame(diabetes_test$predict))

Then we will group the result based on some condition which if the value is higher than 0.5, than we will group it into 1 (Positive), else we will group it into 0 (Negative)

diabetes_test$predict <- factor(ifelse(diabetes_test$predict > 0.5,
                                   yes = 1,
                                   no = 0))
head(as.data.frame(diabetes_test$predict))

Next we will use confusionMatrix function to evaluate our model

confusionMatrix(data = diabetes_test$predict,
                reference = diabetes_test$Outcome,
                positive = "1")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction  0  1
##          0 93 14
##          1 15 32
##                                           
##                Accuracy : 0.8117          
##                  95% CI : (0.7409, 0.8701)
##     No Information Rate : 0.7013          
##     P-Value [Acc > NIR] : 0.001275        
##                                           
##                   Kappa : 0.5533          
##                                           
##  Mcnemar's Test P-Value : 1.000000        
##                                           
##             Sensitivity : 0.6957          
##             Specificity : 0.8611          
##          Pos Pred Value : 0.6809          
##          Neg Pred Value : 0.8692          
##              Prevalence : 0.2987          
##          Detection Rate : 0.2078          
##    Detection Prevalence : 0.3052          
##       Balanced Accuracy : 0.7784          
##                                           
##        'Positive' Class : 1               
## 
# Accuracy
(93+32)/nrow(diabetes_test)
## [1] 0.8116883
# Recall / Sensitivity
(32)/(32+14)
## [1] 0.6956522
# Precission / Pos Pred Value
32/(32+15)
## [1] 0.6808511
# Specificity
93/(93+15)
## [1] 0.8611111

Based on the result above, we could interpret and evaluate our model into:

  1. Our model could predict correctly with 81.17% accuracy.
  2. Our model could predict recall with 69.57%.
  3. Our model could predict Precission with 68.09%.
  4. Our model could predict Specificity with 86.11%.

4 K-nearest neighbor

The k-nearest neighbors (KNN) algorithm is a non-parametric, supervised learning classifier, which uses proximity to make classifications or predictions about the grouping of an individual data point. It is one of the popular and simplest classification and regression classifiers used in machine learning today. ## Data Pre-Processing We need to split our data between our predictor with our target

# Predictor
diabetes_train_x <- diabetes_train %>% select_if(is.numeric)
diabetes_test_x <- diabetes_test %>% select_if(is.numeric)

# Target
diabetes_train_y <- diabetes_train[ , "Outcome"]
diabetes_test_y <- diabetes_test[ , "Outcome"]

Then we need to scale our data using z-score standarization

# Scaling train data
diabetes_train_xs <- scale(x = diabetes_train_x)

# Scaling test data
diabetes_test_xs <- scale(x = diabetes_test_x,
                          center = attr(diabetes_train_xs, "scaled:center"),
                          scale = attr(diabetes_train_xs, "scaled:scale"))

4.1 Prediction

Before we create prediction based on our model, we need to find the optimum k value using sqrt

sqrt(nrow(diabetes_train_xs))
## [1] 24.77902

Our optimum k value is 25

knn_pred <- knn(train = diabetes_train_xs,
                test = diabetes_test_xs,
                cl = diabetes_train_y,
                k = 25)

4.2 Model Evaluation

confusionMatrix(knn_pred,
                diabetes_test_y,
                positive = "1")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction  0  1
##          0 94 18
##          1 14 28
##                                           
##                Accuracy : 0.7922          
##                  95% CI : (0.7195, 0.8533)
##     No Information Rate : 0.7013          
##     P-Value [Acc > NIR] : 0.007263        
##                                           
##                   Kappa : 0.4913          
##                                           
##  Mcnemar's Test P-Value : 0.595883        
##                                           
##             Sensitivity : 0.6087          
##             Specificity : 0.8704          
##          Pos Pred Value : 0.6667          
##          Neg Pred Value : 0.8393          
##              Prevalence : 0.2987          
##          Detection Rate : 0.1818          
##    Detection Prevalence : 0.2727          
##       Balanced Accuracy : 0.7395          
##                                           
##        'Positive' Class : 1               
## 

Based on our k-nn model, we could interpret and evaluate into:

  1. Our model could predict correctly with 79.22% accuracy.
  2. Our model could predict recall with 60.87%.
  3. Our model could predict Precission with 66.67%.
  4. Our model could predict Specificity with 87.04%.

5 Conclusion

FN : Diagnosed with no diabetes but actually got diabetes

FP : Diagnosed with diabetes but actually no diabetes

From 2 explanation above, we obviously doesn’t want FN to actually happening, therefor the best metric that we should use is Recall. Based on our 2 models, the best model that is better is Logistic Regression model with 69.57% prediction accuracy higher than k-nn model with only 60.87% prediction accuracy