In this homework, you will apply logistic regression to a real-world dataset: the Pima Indians Diabetes Database. This dataset contains medical records from 768 women of Pima Indian heritage, aged 21 or older, and is used to predict the onset of diabetes (binary outcome: 0 = no diabetes, 1 = diabetes) based on physiological measurements.
The data is publicly available from the UCI Machine Learning Repository and can be imported directly.
Dataset URL: https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.data.csv
Columns (no header in the CSV, so we need to assign them manually):
Task Overview: You will load the data, build a logistic regression model to predict diabetes onset using a subset of predictors (Glucose, BMI, Age), interpret the model, evaluate it with a confusion matrix and metrics, and analyze the ROC curve and AUC.
Cleaning the dataset Don’t change the following code
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.2.0 ✔ readr 2.1.6
## ✔ forcats 1.0.1 ✔ stringr 1.6.0
## ✔ ggplot2 4.0.2 ✔ tibble 3.3.1
## ✔ lubridate 1.9.5 ✔ tidyr 1.3.2
## ✔ purrr 1.2.1
## ── 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
url <- "https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.data.csv"
data <- read.csv(url, header = FALSE)
colnames(data) <- c("Pregnancies", "Glucose", "BloodPressure", "SkinThickness", "Insulin", "BMI", "DiabetesPedigreeFunction", "Age", "Outcome")
data$Outcome <- as.factor(data$Outcome)
# Handle missing values (replace 0s with NA because 0 makes no sense here)
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
Question 1: Create and Interpret a Logistic Regression Model - Fit a logistic regression model to predict Outcome using Glucose, BMI, and Age.
Provide the model summary.
Calculate and interpret R²: 1 - (model\(deviance / model\)null.deviance). What does it indicate about the model’s explanatory power?
## Enter your code here
## Fit the logistic regression model
model <- glm(Outcome ~ Glucose + BMI + Age, data = data, family = "binomial")
## Provide the model summary
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
## Calculate and interpret R² (McFadden's R²)
r_squared <- 1 - (model$deviance / model$null.deviance)
cat(" R-squared:", round(r_squared, 4))
## R-squared: 0.2563
What does the intercept represent (log-odds of diabetes when predictors are zero)?
The intercept represents the log-odds of a person having diabetes when their Glucose, BMI, and Age are all zero. In this specific medical context, the value doesn’t have a practical biological meaning, but it serves as the baseline for the mathematical model.
For each predictor (Glucose, BMI, Age), does a one-unit increase raise or lower the odds of diabetes? Are they significant (p-value < 0.05)? For all three predictors, the coefficients are positive, meaning a one-unit increase in any of these variables raises the odds of diabetes. All three have p-values significantly lower than 0.05, indicating they are all significant predictors of diabetes onset.
Question 2: Confusion Matrix and Important Metric
Predict probabilities using the fitted model.
Create predicted classes with a 0.5 threshold (1 if probability > 0.5, else 0).
Build a confusion matrix (Predicted vs. Actual Outcome).
Calculate and report the metrics:
Accuracy: (TP + TN) / Total Sensitivity (Recall): TP / (TP + FN) Specificity: TN / (TN + FP) Precision: TP / (TP + FP)
Use the following starter code
# Keep only rows with no missing values in Glucose, BMI, or Age
data_subset <- data[complete.cases(data[, c("Glucose", "BMI", "Age")]), ]
#Create a numeric version of the outcome (0 = no diabetes, 1 = diabetes).This is required for calculating confusion matrices.
data_subset$Outcome_num <- ifelse(data_subset$Outcome == "1", 1, 0)
# Predicted probabilities
data_subset$probs <- predict(model, newdata = data_subset, type = "response")
# Predicted classes
data_subset$preds <- ifelse(data_subset$probs > 0.5, 1, 0)
# Confusion matrix
conf_matrix <- table(Predicted = data_subset$preds, Actual = data_subset$Outcome_num)
print(conf_matrix)
## Actual
## Predicted 0 1
## 0 429 114
## 1 59 150
# Extract Values
TN <- conf_matrix[1,1]
FP <- conf_matrix[1,2]
FN <- conf_matrix[2,1]
TP <- conf_matrix[2,2]
# Metrics
accuracy <- (TP + TN) / sum(conf_matrix)
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.718
## Specificity: 0.79
## Precision: 0.568
Interpret: How well does the model perform? Is it better at detecting diabetes (sensitivity) or non-diabetes (specificity)? Why might this matter for medical diagnosis?
The model performs reasonably well, with an overall accuracy of 0.77. This means it correctly predicts the outcome for 77% of the women in the dataset.
The model is better at detecting non-diabetes (specificity: 0.79) than it is at detecting diabetes (sensitivity: 0.718).
In a clinical setting, this discrepancy is important because a lower sensitivity results in more false negatives. For a serious condition like diabetes, missing a diagnosis (false negative) is often more dangerous than a false alarm (false positive).
Question 3: ROC Curve, AUC, and Interpretation
Plot the ROC curve, use the “data_subset” from Q2.
Calculate AUC.
#Enter your code here
library(pROC)
## Warning: package 'pROC' was built under R version 4.5.3
## Type 'citation("pROC")' for a citation.
##
## Attaching package: 'pROC'
## The following objects are masked from 'package:stats':
##
## cov, smooth, var
# Plot ROC curve
roc_curve <- roc(data_subset$Outcome_num, data_subset$probs)
## Setting levels: control = 0, case = 1
## Setting direction: controls < cases
plot(roc_curve, col = "blue", main = "ROC Curve for Diabetes Prediction")
# Calculate AUC
auc_value <- auc(roc_curve)
cat("AUC:", round(auc_value, 4))
## AUC: 0.828
What does AUC indicate (0.5 = random, 1.0 = perfect)?
AUC (Area Under the Curve) indicates the overall ability of the model to distinguish between the two groups, where a 0.5 represents a model that is no better than a random guess and a 1.0 represents a perfect classifier. A higher AUC value reflects better performance in correctly ranking individuals with diabetes above those without it.
For diabetes diagnosis, prioritize sensitivity (catching cases) or specificity (avoiding false positives)? Suggest a threshold and explain.
For a diabetes diagnosis, it is better to prioritize sensitivity to ensure we are catching as many positive cases as possible. This matters because a missed diagnosis, or a false negative, is more dangerous in medicine than a false positive. I would suggest lowering the threshold to 0.3 or 0.4 because this change would increase sensitivity and minimize the number of patients with diabetes who are incorrectly labeled as healthy.