Logistic Regression in R (glm, pROC, step)

Context: FIT2086_MOC Β· Studio 7 Β· fit a Logistic Regression βž” score it on held-out data (Classification Evaluation (Confusion Matrix and Metrics), ROC and AUC, Logarithmic Loss) βž” prune with AIC/KIC/BIC (Model Selection and Information Criteria (AIC, BIC)) Β· linear twin βž” Multiple Regression and Stepwise Selection in R Β· files: 30_Projects/FIT2086_Studios/Studio7/ Problem it solves: a Y/N target and many candidate predictors βž” a pruned classifier whose quality is measured on data it never saw.

Quick Revision

  • 🎯 Trigger: binary target βž” glm(…, family = binomial) βž” predict(…, type = "response") on test βž” my.pred.stats βž” step(k = …) βž” re-score.
  • ⚑ Key Constraint: predict() returns log-odds by default β€” without type = "response" every threshold, AUC and log-loss is computed on the wrong scale.

πŸ”§ Minimal Working Example

library(pROC)                        # install.packages("pROC") once
source("my.prediction.stats.R")      # defines my.pred.stats(prob, target)
train = read.csv("pima.train.csv", stringsAsFactors = TRUE)   # DIABETES: factor N/Y
test  = read.csv("pima.test.csv",  stringsAsFactors = TRUE)
levels(train$DIABETES)               # "N" "Y" -> the 2nd level is the "success"
 
fullmod = glm(DIABETES ~ ., data = train, family = binomial)
summary(fullmod)                     # z value, Pr(>|z|), null/residual deviance, AIC
all.equal(fullmod$aic, fullmod$deviance + 2*length(coef(fullmod)))   # TRUE
 
eta  = predict(fullmod, test)                      # log-odds: 2.449 -0.192 -0.277 ...
prob = predict(fullmod, test, type = "response")   # P(Y="Y"|x): 0.921 0.452 0.431 ...
pred = factor(prob > 1/2, c(F, T), c("N", "Y"))    # threshold 1/2
table(pred, test$DIABETES)           # rows = predicted, columns = truth
mean(pred == test$DIABETES)          # classification accuracy
roc.obj = roc(response = test$DIABETES, prob); roc.obj$auc; plot(roc.obj)
my.pred.stats(prob, test$DIABETES)   # table + CA + sens + spec + AUC + log-loss + ROC plot
 
bic = step(fullmod, k = log(nrow(train)), direction = "both", trace = 0)
my.pred.stats(predict(bic, test, type = "response"), test$DIABETES)

Expected output (Pima: train, test; rerun-verified):

ModelParamsResid. dev.CASensSpecAUCLog-loss
full ~ .
BIC of full (PREG, PLAS, BMI, PED)
53-term (.*. + logs + squares)
BIC of 53-term
KIC (k = 3) of 53-term
  • Full-model summary() βž” PLAS, BMI, PREG strongest () Β· BP, PED Β· AGE marginal () Β· SKIN, INS unimportant. Null deviance .
  • BIC-of-53 equation βž” .
  • Doctor’s reading βž” PLAS, AGE and PED (family history) can’t really be changed; BMI can βž” log-odds of diabetes fall by per unit decrease in .
  • BIC vs KIC βž” KIC predicts best but keeps terms incl. interactions, with a coefficient of beside and βž” likely overestimated; trust the simpler model’s coefficients for explanation, the KIC model when prediction is all that matters.

πŸ”€ Variations

  • Transformations in the formula βž” . + log(BMI) Β· . + I(PLAS^2) (I() protects ^) Β· . + SKIN*AGE (adds the product term SKIN:AGE) Β· . + .*. = all pairwise interactions Β· log(PREG+1) guards .
Added to ~ .Term’s -valueDeviance drop from Verdict
log(BMI)nonlinear BMI effect likely
I(PLAS^2)no evidence of curvature
SKIN*AGEinteraction probably associated, though SKIN alone is not
  • Criterion via k βž” k = 2 AIC (default) Β· k = 3 KIC Β· k = log(nrow(df)) BIC β€” least to most conservative.
  • close to (gene data) βž” , binary SNP factors, balanced test rows:
ModelParamsCASensSpecAUCLog-loss
full
AIC step
BIC step
  • Gene reading βž” full fit: null deviance βž” residual , AIC , yet test AUC chance ⟹ overfitting. BIC keeps only βž” a SNP12 mutation lowers the log-odds of disease by ; a SNP56 mutation raises them by .
  • Solution-comment correction βž” the studio notebook says BIC β€œgreatly improved sensitivity”; the rerun shows the reverse β€” sensitivity , specificity β€” while accuracy, AUC and log-loss all improved.

✍️ Practice

⚠️ Common Mistakes

  • πŸ’‘ Omitting family = binomial βž” glm silently fits a Gaussian linear model.
  • πŸ’‘ Target not a factor βž” read with stringsAsFactors = TRUE; R treats the second level as success, and my.pred.stats hard-codes the labels "N"/"Y".
  • πŸ’‘ Studio 10’s my.pred.stats is a new version βž” display = F returns list(ca, sens, spec, auc, log.loss) instead of printing β€” needed to feed a statistic to boot() βž” Resampling Inference in R (boot, boot.ci, perm.log.reg).
  • πŸ’‘ Reading my.pred.stats log-loss as an average βž” it is a sum over the test set ( over rows) βž” compare only on the same test data.
  • πŸ’‘ Judging a model in-sample βž” deviance looks excellent; test AUC says otherwise.