# Rebuild the patient table from the raw data so this notebook runs on its own
pt <- load_t2dm(data_path) %>% build_patient_table() %>% analytic_cohort()
# Features: required set. To include your Week 2 stretch features, use
# features <- c(model_features, stretch_features)
features <- model_features
features
## [1] "age_at_index" "Patient_sex" "Patient_Race" "insurance"
## [5] "main_specialty" "n_encounters" "n_other_dx" "hypertension"
## [9] "obesity" "hyperlipidemia" "ckd" "heart_failure"
sp <- stratified_split(pt, prop = 0.75, seed = 719)
train <- sp$train
test <- sp$test
bind_rows(train = train %>% summarise(n = n(), pct_outcome = 100 * mean(any_complication)),
test = test %>% summarise(n = n(), pct_outcome = 100 * mean(any_complication)),
.id = "set")
# Classification threshold = training prevalence (see helpers for rationale)
threshold <- mean(train$any_complication)
threshold
## [1] 0.1155702
logit_fit <- fit_logit(train, features)
summary(logit_fit)
##
## Call:
## glm(formula = make_formula(features), family = binomial(), data = train)
##
## Coefficients:
## Estimate Std. Error z value
## (Intercept) -2.247e+00 9.224e-01 -2.436
## age_at_index -9.089e-04 2.180e-03 -0.417
## Patient_sexMale 2.642e-02 5.093e-02 0.519
## Patient_RaceAsian -5.180e-02 1.969e-01 -0.263
## Patient_RaceBlack or African American -4.584e-02 5.930e-02 -0.773
## Patient_RaceMore than one race 1.967e-01 1.305e-01 1.508
## Patient_RaceNative Hawaiian / Pacific Islander 8.192e-02 2.173e-01 0.377
## Patient_RaceUnknown 1.194e-01 1.735e-01 0.688
## Patient_RaceUnreported / Declined -2.536e-02 8.770e-02 -0.289
## Patient_RaceOther race 1.507e-01 4.362e-01 0.346
## insuranceCommercial Plan A 7.808e-02 8.344e-02 0.936
## insuranceCommercial Plan B -4.984e-02 8.612e-02 -0.579
## insuranceMedicaid 1.113e-01 8.062e-02 1.380
## insuranceMedicare Advantage -1.492e-01 1.434e-01 -1.040
## insuranceSelf-pay / Uninsured 1.147e-01 9.876e-02 1.162
## insuranceSliding Fee Scale 8.507e-02 1.572e-01 0.541
## insuranceOther payer 3.407e-02 3.685e-01 0.092
## main_specialtyBehavioral Health -8.799e-01 5.417e-01 -1.624
## main_specialtyBurn Surgery 7.125e-01 4.290e-01 1.661
## main_specialtyCardiology -3.118e-01 4.041e-01 -0.771
## main_specialtyDermatology 6.880e-01 1.311e+00 0.525
## main_specialtyGeriatric Primary Care -1.465e-01 1.994e-01 -0.735
## main_specialtyObstetrics and Gynecology 2.931e-01 2.552e-01 1.148
## main_specialtyPodiatry 7.959e-02 1.990e-01 0.400
## main_specialtyOther specialty 2.111e-01 1.083e-01 1.949
## n_encounters 1.362e+00 9.109e-01 1.495
## n_other_dx -7.697e+00 7.112e-01 -10.822
## hypertension -9.961e+00 2.282e+02 -0.044
## obesity -9.985e+00 3.051e+02 -0.033
## hyperlipidemia -9.973e+00 3.923e+02 -0.025
## ckd -1.018e+01 1.071e+03 -0.009
## heart_failure -9.946e+00 9.111e+02 -0.011
## Pr(>|z|)
## (Intercept) 0.0148 *
## age_at_index 0.6767
## Patient_sexMale 0.6039
## Patient_RaceAsian 0.7924
## Patient_RaceBlack or African American 0.4395
## Patient_RaceMore than one race 0.1317
## Patient_RaceNative Hawaiian / Pacific Islander 0.7061
## Patient_RaceUnknown 0.4914
## Patient_RaceUnreported / Declined 0.7725
## Patient_RaceOther race 0.7297
## insuranceCommercial Plan A 0.3494
## insuranceCommercial Plan B 0.5628
## insuranceMedicaid 0.1675
## insuranceMedicare Advantage 0.2982
## insuranceSelf-pay / Uninsured 0.2454
## insuranceSliding Fee Scale 0.5885
## insuranceOther payer 0.9263
## main_specialtyBehavioral Health 0.1043
## main_specialtyBurn Surgery 0.0968 .
## main_specialtyCardiology 0.4405
## main_specialtyDermatology 0.5997
## main_specialtyGeriatric Primary Care 0.4625
## main_specialtyObstetrics and Gynecology 0.2509
## main_specialtyPodiatry 0.6892
## main_specialtyOther specialty 0.0513 .
## n_encounters 0.1350
## n_other_dx <2e-16 ***
## hypertension 0.9652
## obesity 0.9739
## hyperlipidemia 0.9797
## ckd 0.9924
## heart_failure 0.9913
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 13952.2 on 19485 degrees of freedom
## Residual deviance: 9312.9 on 19454 degrees of freedom
## AIC: 9376.9
##
## Number of Fisher Scoring iterations: 17
# Odds ratios with Wald 95% CIs
or_tbl <- tibble(
term = names(coef(logit_fit)),
OR = exp(coef(logit_fit)),
lo = exp(confint.default(logit_fit)[, 1]),
hi = exp(confint.default(logit_fit)[, 2]),
p = summary(logit_fit)$coefficients[, 4]
) %>%
filter(term != "(Intercept)") %>%
mutate(across(c(OR, lo, hi), ~ round(.x, 2)), p = signif(p, 3))
or_tbl
ggplot(or_tbl, aes(x = OR, y = reorder(term, OR))) +
geom_vline(xintercept = 1, linetype = "dashed", colour = "grey50") +
geom_errorbarh(aes(xmin = lo, xmax = hi), height = 0.2, colour = "#3B6EA8") +
geom_point(colour = "#3B6EA8", size = 2) +
scale_x_log10() +
labs(x = "Odds ratio (log scale, 95% CI)", y = NULL,
title = "Logistic regression: odds of a documented complication") +
theme_minimal()
rf_fit <- fit_rf(train, features, seed = 719, num.trees = 500)
rf_fit
## Ranger result
##
## Call:
## ranger(formula = as.formula(paste("outcome_f ~", paste(features, collapse = " + "))), data = train, num.trees = num.trees, probability = TRUE, importance = "permutation", min.node.size = 25, seed = seed)
##
## Type: Probability estimation
## Number of trees: 500
## Sample size: 19486
## Number of independent variables: 12
## Mtry: 3
## Target node size: 25
## Variable importance mode: permutation
## Splitrule: gini
## OOB prediction error (Brier s.): 0.08202667
p_logit <- predict_prob(logit_fit, test)
p_rf <- predict_prob(rf_fit, test)
results <- bind_rows(
`Logistic regression` = classification_metrics(test$any_complication, p_logit, threshold),
`Random forest` = classification_metrics(test$any_complication, p_rf, threshold),
.id = "model"
) %>% mutate(across(AUC:precision, ~ round(.x, 3)))
results
# AUC confidence intervals and a formal comparison (DeLong test)
roc_logit <- roc(test$any_complication, p_logit, quiet = TRUE, direction = "<")
roc_rf <- roc(test$any_complication, p_rf, quiet = TRUE, direction = "<")
ci.auc(roc_logit); ci.auc(roc_rf)
## 95% CI: 0.8411-0.8601 (DeLong)
## 95% CI: 0.8346-0.854 (DeLong)
roc.test(roc_logit, roc_rf, method = "delong")
##
## DeLong's test for two correlated ROC curves
##
## data: roc_logit and roc_rf
## Z = 1.5855, p-value = 0.1129
## alternative hypothesis: true difference in AUC is not equal to 0
## 95 percent confidence interval:
## -0.00149838 0.01418654
## sample estimates:
## AUC of roc1 AUC of roc2
## 0.8506204 0.8442763
roc_df <- bind_rows(
tibble(model = "Logistic regression", fpr = 1 - roc_logit$specificities, tpr = roc_logit$sensitivities),
tibble(model = "Random forest", fpr = 1 - roc_rf$specificities, tpr = roc_rf$sensitivities)
)
ggplot(roc_df, aes(fpr, tpr, colour = model)) +
geom_abline(linetype = "dashed", colour = "grey60") +
geom_path(linewidth = 0.9) +
coord_equal() +
scale_colour_manual(values = c("#3B6EA8", "#D98E04")) +
labs(x = "1 - specificity", y = "Sensitivity", colour = NULL,
title = "ROC curves on the test set") +
theme_minimal() + theme(legend.position = "bottom")
# How do sensitivity and specificity move with the threshold?
sapply(c(0.5 * threshold, threshold, 1.5 * threshold), function(t)
unlist(classification_metrics(test$any_complication, p_logit, t)[, c("sensitivity", "specificity", "precision")])) %>%
`colnames<-`(paste0("t=", round(c(0.5, 1, 1.5) * threshold, 3)))
## t=0.058 t=0.116 t=0.173
## sensitivity 0.9986684 0.9986684 0.9973369
## specificity 0.6976501 0.6976501 0.6983464
## precision 0.3015682 0.3015682 0.3017728
saveRDS(list(train = train, test = test, features = features, threshold = threshold,
logit_fit = logit_fit, rf_fit = rf_fit), "week3_models.rds")
Write your paragraph here. Weigh test-set performance (is the AUC difference meaningful given the CIs and DeLong test?) against interpretability, how easily clinicians could trust and audit the model, and how each model would be maintained.