url <- "https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.data.csv"
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.2.1 ✔ readr 2.2.0
## ✔ forcats 1.0.1 ✔ stringr 1.6.0
## ✔ ggplot2 4.0.3 ✔ tibble 3.3.1
## ✔ lubridate 1.9.5 ✔ tidyr 1.3.2
## ✔ purrr 1.2.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
data <- read.csv(url, header = FALSE)
colnames(data) <- c("Pregnancies", "Glucose", "BloodPressure", "SkinThickness", "Insulin", "BMI", "DiabetesPedigreeFunction", "Age", "Outcome")
data$Outcome <- as.factor(data$Outcome)
data$Glucose[data$Glucose == 0] <- NA
data$BloodPressure[data$BloodPressure == 0] <- NA
data$BMI[data$BMI == 0] <- NA
colSums(is.na(data))
## Pregnancies Glucose BloodPressure
## 0 5 35
## SkinThickness Insulin BMI
## 0 0 11
## DiabetesPedigreeFunction Age Outcome
## 0 0 0
model <- glm( Outcome ~ Glucose + BMI + Age, data = data, family = binomial )
summary(model)
##
## Call:
## glm(formula = Outcome ~ Glucose + BMI + Age, family = binomial,
## data = data)
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -9.032377 0.711037 -12.703 < 2e-16 ***
## Glucose 0.035548 0.003481 10.212 < 2e-16 ***
## BMI 0.089753 0.014377 6.243 4.3e-10 ***
## Age 0.028699 0.007809 3.675 0.000238 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 974.75 on 751 degrees of freedom
## Residual deviance: 724.96 on 748 degrees of freedom
## (16 observations deleted due to missingness)
## AIC: 732.96
##
## Number of Fisher Scoring iterations: 4
R2 <- 1 - (model$deviance / model$null.deviance)
R2
## [1] 0.25626
The R² is about 0.256, meaning the predictors provide about a 25.6% improvement in explaining the outcome compared with the null model. The intercept represents the log-odds of diabetes when Glucose, BMI, and Age are all 0. Since these values are not realistic, the intercept has little practical meaning. Glucose, BMI, and Age all have positive coefficients, showing increases in these variables are associated with higher odds of diabetes. All three are statistically significant because their p-values are less than 0.05.
data_subset <- data[ complete.cases(data[, c("Glucose", "BMI", "Age")]), ]
data_subset$Outcome_num <- ifelse( data_subset$Outcome == "1", 1, 0)
Predicted probabilities:
data_subset$prob <- predict( model, newdata = data_subset, type = "response")
Predicted classes:
data_subset$predicted <- ifelse( data_subset$prob > 0.5, 1, 0)
confusion <- table( Predicted = data_subset$predicted, Actual = data_subset$Outcome_num)
confusion
## Actual
## Predicted 0 1
## 0 429 114
## 1 59 150
TN <- confusion[1,1]
FP <- confusion[2,1]
FN <- confusion[1,2]
TP <- confusion[2,2]
accuracy <- (TP + TN) / sum(confusion)
sensitivity <- TP / (TP + FN)
specificity <- TN / (TN + FP)
precision <- TP / (TP + FP)
cat("Accuracy:", round(accuracy, 3),
"\nSensitivity:", round(sensitivity, 3),
"\nSpecificity:", round(specificity, 3),
"\nPrecision:", round(precision, 3))
## Accuracy: 0.77
## Sensitivity: 0.568
## Specificity: 0.879
## Precision: 0.718
The model has about 77% accuracy. Its sensitivity is about 56.8% and specificity is about 87.9%. Therefore, the model is better at identifying people without diabetes than identifying people with diabetes. This is important because, in medical diagnosis, missing someone who has diabetes could delay further testing and treatment.
library(pROC)
## Type 'citation("pROC")' for a citation.
##
## Attaching package: 'pROC'
## The following objects are masked from 'package:stats':
##
## cov, smooth, var
roc_result <- roc( data_subset$Outcome_num, data_subset$prob)
## Setting levels: control = 0, case = 1
## Setting direction: controls < cases
plot(roc_result)
auc(roc_result)
## Area under the curve: 0.828
The AUC is about 0.828. Since an AUC of 0.5 represents random prediction and 1 represents perfect prediction, 0.828 indicates good ability to distinguish between diabetes and non-diabetes cases. For diabetes screening, I would prioritize sensitivity because identifying possible diabetes cases is important. A threshold such as 0.4 could increase sensitivity and detect more cases, although it may also increase false positives.