url <- "https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.data.csv"

Cleaning Data Set:

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

1. Logistic Regression Model

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.

2. Confusion Matrix and Metrics:

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.

3. ROC Curve and AUC:

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.