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.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
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
#logistic regression model
model <- glm(
Outcome ~ Glucose + BMI + Age,
data = data,
family = binomial
)
#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
#R-squared
R2 <- 1 - (model$deviance / model$null.deviance)
R2
## [1] 0.25626
#R-squared interpretation
#The R-squared value is approximately 0.256, which indicates that the model explains about 25.6% of the variation in diabetes outcome using Glucose, BMI, and Age, meaning that these variables provide useful information for predicting diabetes. However, a large amount of variance is still unexplained, and other factors not included in the model may also influence diabetes risk.
What does the intercept represent (log-odds of diabetes when predictors are zero)?
The intercept represents the predicted log-odds of diabetes when Glucose, BMI, and Age are all equal to zero.
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)?
All three of the coefficients are positive, meaning that a one unit increase would be associated with an increased odds of diabetes. Since all three values have a p-value of far less than 0.05, these predictors are all statistically significant.
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
predicted_prob <- predict(
model,
newdata = data_subset,
type = "response"
)
# Predicted classes
predicted_class <- ifelse(predicted_prob > 0.5, 1, 0)
# Confusion matrix
conf_matrix <- table(
Predicted = predicted_class,
Actual = data_subset$Outcome_num
)
conf_matrix
## Actual
## Predicted 0 1
## 0 429 114
## 1 59 150
#Extract Values:
TN <- conf_matrix["0", "0"]
FP <- conf_matrix["1", "0"]
FN <- conf_matrix["0", "1"]
TP <- conf_matrix["1", "1"]
#Metrics
accuracy <- (TP + TN) / (TP + TN + FP + FN)
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
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 accuracy indicates it correctly classifies about 77% of observations. The model sensitivity indicates it correctly identifies around 57% of individuals with diabetes. The model specificity indicates it correctly identifies around 88% of individuals who do not have diabetes. The model is therefore better at detecting non diabetes than diabetes because its specificity is much higher than its sensitivity. This is important in a medical setting because a false negative could incorrectly classify someone with diabetes as not having diabetes. Missing a person who actually has the condition could delay further screenings or patient care, and improving sensitivity may be especially important for a screening model.
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)
## Type 'citation("pROC")' for a citation.
##
## Attaching package: 'pROC'
## The following objects are masked from 'package:stats':
##
## cov, smooth, var
roc_curve <- roc(
data_subset$Outcome_num,
predicted_prob
)
## Setting levels: control = 0, case = 1
## Setting direction: controls < cases
plot(
roc_curve,
main = "ROC Curve for Diabetes Prediction"
)
auc_value <- auc(roc_curve)
auc_value
## Area under the curve: 0.828
What does AUC indicate (0.5 = random, 1.0 = perfect)?
The AUC of 0.828 indicates the model had a reasonably good ability to distinguish between individuals with diabetes and individuals without diabetes.
For diabetes diagnosis, prioritize sensitivity (catching cases) or specificity (avoiding false positives)? Suggest a threshold and explain.
#For diabetes screening, I would prioritize sensitivity because failing to identify someone who actually has diabetes could delay diagnosis and treatment. The model currently has higher specificity than sensitivity, so it is better at correctly identifying people without diabetes than people with diabetes. A lower threshold, such as 0.40, could be considered because it would classify more individuals as potentially having diabetes and therefore increase sensitivity. The tradeoff is that lowering the threshold would also increase the number of false positives. In a screening setting, that tradeoff may be acceptable because people who test positive can receive additional diagnostic testing before a final diagnosis is made.