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.
library(gtools)
library(car)
library(caret)
library(dplyr)
library(class)
library(MASS)
options(scipen = 100, max.print = 1e+08)
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:
Pregnancies : Number of times pregnantGlucose : Plasma glucose concentration a 2 hours in an
oral glucose tolerance testBloodPressure : Diastolic blood pressure (mm Hg)SkinThickness : Triceps skin fold thickness (mm)Insulin : 2-Hour serum insulin (mu U/ml)BMI : Body mass index (weight in kg/(height in
m)^2)DiabetesPedigreeFunction : Diabetes pedigree
functionAge : Subject AgeOutcome : 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.
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
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.
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:
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"))
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)
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:
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