1 Setup

1.1 Load packages

# Install any missing packages before loading
required_packages <- c(
  "readxl", "dplyr", "tidyr", "ggplot2", "scales",
  "pROC", "ResourceSelection", "car",
  "gtsummary", "flextable", "knitr", "broom.helpers"
)

installed <- rownames(installed.packages())
to_install <- required_packages[!required_packages %in% installed]
if (length(to_install) > 0) install.packages(to_install)

library(readxl)
library(dplyr)
library(tidyr)
library(ggplot2)
library(scales)
library(pROC)
library(ResourceSelection)
library(car)
library(gtsummary)
library(flextable)
library(knitr)

1.2 Import data

df <- read_excel("Data for analysis.xlsx",
                 sheet = "2025 cohort_merged",
                 na    = c("", "NULL", "#VALUE!")) %>%
  select(-`...10`, -`...18`) %>%
  rename(
    id            = `MCS ID`,
    time_code     = `Time code`,
    weekday       = `Weekday code`,
    arrival       = `Arrival code`,
    triage        = `Triage code`,
    age           = `Age code`,
    sex           = `Sex code`,
    destiny       = `Destiny integer`,
    prior_adm     = `Admission is past 30 days`,
    s_age         = `S(AGE)`,
    s_arrival     = `S(ARRIVAL)`,
    s_triage      = `S(TRIAGE)`,
    s_time        = `S(TIME)`,
    s_prior_adm   = `S(Prior admission)`,
    s_pp          = `S(PRESENTING PROBLEM)`,
    start_score   = `START SCORE`,
    rrc           = RRC,
    cb            = CB,
    icu           = `ICU Admission`,
    mortality     = Mortality,
    deterioration = Deterioration
  )

1.3 Clean and recode

df <- df %>%
  # Remove any rows with missing outcome or score
  filter(!is.na(start_score), !is.na(deterioration)) %>%
  # Remove 3 rows coded as unknown sex (sex == 0)
  filter(!(sex == 0)) %>%
  # Set factor types
  mutate(
    across(c(time_code, weekday, arrival, triage, age, sex,
             destiny, prior_adm), as.factor),
    across(c(rrc, cb, icu, mortality, deterioration), as.integer)
  ) %>%
  # Apply factor labels
  mutate(
    age = factor(age,
                 levels = c(1, 2, 3, 4, 5),
                 labels = c("16-19", "20-39", "40-59", "60-79", "80+")),

    sex = factor(sex,
                 levels = c(1, 2),
                 labels = c("Male", "Female")),

    triage = factor(triage,
                    levels = c(3, 4, 5),
                    labels = c("Urgent", "Semi-Urgent", "Non-Urgent")),

    time_code = factor(time_code,
                       levels = c(1, 2, 3),
                       labels = c("0800-1759", "1800-2259", "2300-0759")),

    weekday = factor(weekday,
                     levels = c(1, 2, 3, 4, 5, 6, 7),
                     labels = c("Mon","Tue","Wed","Thu","Fri","Sat","Sun")),

    arrival = factor(arrival,
                     levels = c(1, 2, 3, 4, 5, 6, 7, 9, 10, 11),
                     labels = c("Ambulance", "Public transport", "Private vehicle",
                                "Helicopter", "Air ambulance", "Internal ambulance",
                                "Police", "Walked in", "Retrieval team",
                                "Internal bed")),

    destiny = factor(destiny,
                     levels = c(1, 2, 3, 4, 5, 6, 9, 10, 11, 12,
                                13, 14, 15, 17, 18, 19, 20),
                     labels = c("Abdominal/GI", "Cardiovascular",
                                "General symptoms", "Febrile illness",
                                "Injury", "Respiratory", "Musculoskeletal",
                                "Neurological", "Mental health", "Toxicological",
                                "ENT/eye/head & neck", "Administrative",
                                "Genitourinary", "Endocrine",
                                "Obstetrics/Gynaecology", "Skin/allergy",
                                "Other medical")),

    prior_adm = factor(prior_adm,
                       levels = c(0, 1),
                       labels = c("No", "Yes")),

    # Binary ambulance variable
    # Collapses all ambulance-type arrivals; avoids near-complete separation
    # from very small cells (helicopter n=11, air ambulance n=2, etc.)
    ambulance = factor(
      ifelse(arrival %in% c("Ambulance", "Helicopter", "Air ambulance",
                            "Internal ambulance", "Retrieval team"),
             "Yes", "No"),
      levels = c("No", "Yes")
    ),

    # Labelled outcome factor for tables
    deterioration_label = factor(deterioration,
                                 levels = c(0, 1),
                                 labels = c("No", "Yes"))
  )

cat("Total encounters:", nrow(df), "\n")
## Total encounters: 50152
cat("Deterioration events:", sum(df$deterioration), "\n")
## Deterioration events: 1326
cat("Event rate:", round(mean(df$deterioration) * 100, 2), "%\n")
## Event rate: 2.64 %

2 Descriptive Statistics (Table 1)

table1 <- df %>%
  select(age, sex, arrival, triage, time_code, prior_adm,
         destiny, start_score, deterioration_label) %>%
  tbl_summary(
    by = deterioration_label,
    label = list(
      age         ~ "Age group",
      sex         ~ "Sex",
      arrival     ~ "Mode of arrival",
      triage      ~ "Triage category",
      time_code   ~ "Time of arrival",
      prior_adm   ~ "Prior hospital admission (30 days)",
      destiny     ~ "Presenting problem category",
      start_score ~ "START score"
    ),
    statistic = list(
      all_continuous()  ~ "{mean} ({sd})",
      all_categorical() ~ "{n} ({p}%)"
    ),
    digits = list(
      all_continuous()  ~ 1,
      all_categorical() ~ c(0, 1)
    ),
    missing = "no"
  ) %>%
  add_overall() %>%
  add_p(
    test = list(
      all_continuous()  ~ "t.test",
      all_categorical() ~ "chisq.test"
    )
  ) %>%
  bold_labels() %>%
  bold_p(t = 0.05) %>%
  modify_header(
    label  ~ "**Variable**",
    stat_1 ~ "**No deterioration**  \nn = {n}",
    stat_2 ~ "**Deterioration**  \nn = {n}",
    stat_0 ~ "**Overall**  \nn = {n}"
  ) %>%
  modify_caption("**Table 1.** Baseline characteristics stratified by clinical deterioration")

table1
Table 1. Baseline characteristics stratified by clinical deterioration
Variable Overall
n = 50152
1
No deterioration
n = 48826
1
Deterioration
n = 1326
1
p-value2
Age group


<0.001
    16-19 3,020 (6.0%) 2,996 (6.1%) 24 (1.8%)
    20-39 19,360 (38.6%) 19,214 (39.4%) 146 (11.0%)
    40-59 13,943 (27.8%) 13,701 (28.1%) 242 (18.3%)
    60-79 9,953 (19.8%) 9,438 (19.3%) 515 (38.8%)
    80+ 3,876 (7.7%) 3,477 (7.1%) 399 (30.1%)
Sex


0.004
    Male 24,148 (48.1%) 23,458 (48.0%) 690 (52.0%)
    Female 26,004 (51.9%) 25,368 (52.0%) 636 (48.0%)
Mode of arrival


<0.001
    Ambulance 10,135 (20.2%) 9,436 (19.3%) 699 (52.7%)
    Public transport 232 (0.5%) 231 (0.5%) 1 (0.1%)
    Private vehicle 38,571 (76.9%) 37,989 (77.8%) 582 (43.9%)
    Helicopter 11 (0.0%) 11 (0.0%) 0 (0.0%)
    Air ambulance 2 (0.0%) 2 (0.0%) 0 (0.0%)
    Internal ambulance 155 (0.3%) 135 (0.3%) 20 (1.5%)
    Police 515 (1.0%) 508 (1.0%) 7 (0.5%)
    Walked in 272 (0.5%) 270 (0.6%) 2 (0.2%)
    Retrieval team 3 (0.0%) 3 (0.0%) 0 (0.0%)
    Internal bed 256 (0.5%) 241 (0.5%) 15 (1.1%)
Triage category


<0.001
    Urgent 28,326 (56.5%) 27,250 (55.8%) 1,076 (81.1%)
    Semi-Urgent 17,734 (35.4%) 17,501 (35.8%) 233 (17.6%)
    Non-Urgent 4,092 (8.2%) 4,075 (8.3%) 17 (1.3%)
Time of arrival


<0.001
    0800-1759 29,214 (58.3%) 28,358 (58.1%) 856 (64.6%)
    1800-2259 11,588 (23.1%) 11,333 (23.2%) 255 (19.2%)
    2300-0759 9,350 (18.6%) 9,135 (18.7%) 215 (16.2%)
Prior hospital admission (30 days) 2,705 (5.4%) 2,546 (5.2%) 159 (12.0%) <0.001
Presenting problem category


<0.001
    Abdominal/GI 9,842 (19.6%) 9,534 (19.5%) 308 (23.2%)
    Cardiovascular 1,170 (2.3%) 1,140 (2.3%) 30 (2.3%)
    General symptoms 5,706 (11.4%) 5,386 (11.0%) 320 (24.1%)
    Febrile illness 1,287 (2.6%) 1,221 (2.5%) 66 (5.0%)
    Injury 6,621 (13.2%) 6,559 (13.4%) 62 (4.7%)
    Respiratory 2,228 (4.4%) 2,070 (4.2%) 158 (11.9%)
    Musculoskeletal 4,163 (8.3%) 4,080 (8.4%) 83 (6.3%)
    Neurological 3,394 (6.8%) 3,315 (6.8%) 79 (6.0%)
    Mental health 2,146 (4.3%) 2,115 (4.3%) 31 (2.3%)
    Toxicological 300 (0.6%) 297 (0.6%) 3 (0.2%)
    ENT/eye/head & neck 4,941 (9.9%) 4,898 (10.0%) 43 (3.2%)
    Administrative 1,699 (3.4%) 1,656 (3.4%) 43 (3.2%)
    Genitourinary 2,038 (4.1%) 2,001 (4.1%) 37 (2.8%)
    Endocrine 113 (0.2%) 104 (0.2%) 9 (0.7%)
    Obstetrics/Gynaecology 1,841 (3.7%) 1,830 (3.7%) 11 (0.8%)
    Skin/allergy 2,650 (5.3%) 2,608 (5.3%) 42 (3.2%)
    Other medical 13 (0.0%) 12 (0.0%) 1 (0.1%)
START score 11.2 (6.7) 11.0 (6.6) 18.3 (5.4) <0.001
1 n (%); Mean (SD)
2 Pearson’s Chi-squared test; Welch Two Sample t-test

2.1 Outcome component frequencies

outcomes <- df %>%
  summarise(
    `RRC activation`  = sum(rrc),
    `Code Blue`       = sum(cb),
    `ICU admission`   = sum(icu),
    `In-hospital death` = sum(mortality),
    `Composite (any)` = sum(deterioration)
  ) %>%
  pivot_longer(everything(), names_to = "Outcome", values_to = "n") %>%
  mutate(
    Total  = nrow(df),
    `Rate (%)` = round(n / Total * 100, 2)
  ) %>%
  select(Outcome, n, `Rate (%)`)

kable(outcomes,
      caption = "Table 1b. Composite outcome component frequencies",
      align   = "lrr")
Table 1b. Composite outcome component frequencies
Outcome n Rate (%)
RRC activation 1056 2.11
Code Blue 385 0.77
ICU admission 115 0.23
In-hospital death 219 0.44
Composite (any) 1326 2.64

3 START Score Performance

3.1 ROC curve and AUC

roc_start <- roc(deterioration ~ start_score,
                 data = df,
                 ci   = TRUE)

cat("START score AUC:", round(auc(roc_start), 4), "\n")
## START score AUC: 0.8038
cat("95% CI (DeLong):", round(ci(roc_start)[1], 4),
    "-", round(ci(roc_start)[3], 4), "\n")
## 95% CI (DeLong): 0.7927 - 0.8149
plot(roc_start,
     print.auc       = TRUE,
     auc.polygon     = TRUE,
     grid            = TRUE,
     col             = "#2C7BB6",
     auc.polygon.col = "#D7EAF5",
     main            = "ROC Curve: START Score vs Clinical Deterioration",
     xlab            = "1 - Specificity",
     ylab            = "Sensitivity")

3.2 Diagnostic performance at key thresholds

# Youden optimal cutpoint
youden_best <- coords(roc_start, "best", best.method = "youden",
                      ret = c("threshold", "sensitivity", "specificity",
                              "ppv", "npv"))

# Clinically relevant thresholds for the table
thresh_raw <- coords(roc_start,
                     x   = c(10, 13, 15, 20),
                     ret = c("threshold", "sensitivity", "specificity",
                             "ppv", "npv"))

thresh_table <- thresh_raw %>%
  mutate(
    threshold   = as.integer(threshold),
    sensitivity = round(sensitivity * 100, 1),
    specificity = round(specificity * 100, 1),
    ppv         = round(ppv * 100, 1),
    npv         = round(npv * 100, 1)
  ) %>%
  rename(
    `Threshold`       = threshold,
    `Sensitivity (%)` = sensitivity,
    `Specificity (%)` = specificity,
    `PPV (%)`         = ppv,
    `NPV (%)`         = npv
  )

kable(thresh_table,
      caption = paste0(
        "Table 2. START score diagnostic performance at selected thresholds. ",
        "Youden-optimal cutpoint = ", round(youden_best$threshold, 0),
        " (sensitivity ", round(youden_best$sensitivity * 100, 1),
        "%, specificity ", round(youden_best$specificity * 100, 1), "%). ",
        "AUC = ", round(auc(roc_start), 3),
        " (95% CI ", round(ci(roc_start)[1], 3),
        "-", round(ci(roc_start)[3], 3), ")"
      ),
      align = "rrrrr")
Table 2. START score diagnostic performance at selected thresholds. Youden-optimal cutpoint = 14 (sensitivity 77.7%, specificity 68.6%). AUC = 0.804 (95% CI 0.793-0.815)
Threshold Sensitivity (%) Specificity (%) PPV (%) NPV (%)
10 92.5 41.9 4.1 99.5
13 85.1 57.3 5.1 99.3
15 77.7 68.6 6.3 99.1
20 44.3 89.7 10.5 98.3

3.3 Calibration

model_cal <- glm(deterioration ~ start_score, data = df, family = binomial)

# Hosmer-Lemeshow test
hl <- hoslem.test(df$deterioration, fitted(model_cal), g = 10)

cat("Hosmer-Lemeshow: X² =", round(hl$statistic, 3),
    "| df =", hl$parameter,
    "| p =", format.pval(hl$p.value, digits = 3), "\n")
## Hosmer-Lemeshow: X² = 31.977 | df = 8 | p = 9.4e-05
cat("Note: HL p-value is expected to be significant at n ≈", nrow(df),
    "— use calibration plot to assess clinical meaningfulness.\n")
## Note: HL p-value is expected to be significant at n ≈ 50152 — use calibration plot to assess clinical meaningfulness.
# Calibration plot (observed vs predicted by decile)
df$predicted_cal <- predict(model_cal, type = "response")

cal_data <- df %>%
  mutate(decile = ntile(predicted_cal, 10)) %>%
  group_by(decile) %>%
  summarise(
    mean_predicted = mean(predicted_cal),
    observed_rate  = mean(deterioration),
    n              = n(),
    .groups        = "drop"
  )

ggplot(cal_data, aes(x = mean_predicted, y = observed_rate)) +
  geom_abline(intercept = 0, slope = 1,
              linetype = "dashed", colour = "red", linewidth = 0.8) +
  geom_point(size = 3, colour = "#2C7BB6") +
  scale_x_continuous(labels = percent_format(accuracy = 0.1)) +
  scale_y_continuous(labels = percent_format(accuracy = 0.1)) +
  labs(
    title    = "Calibration Plot: START Score",
    subtitle = paste0("Points represent risk deciles. Dashed line = perfect calibration.\n",
                      "Hosmer-Lemeshow p ", format.pval(hl$p.value, digits = 2),
                      " (large-sample artefact)"),
    x        = "Mean Predicted Probability",
    y        = "Observed Deterioration Rate"
  ) +
  theme_bw(base_size = 12)


4 Component Analysis (Univariable Logistic Regression)

Each START component variable entered separately.

uv_table <- df %>%
  select(deterioration, age, sex, ambulance, triage,
         time_code, prior_adm, destiny) %>%
  tbl_uvregression(
    method       = glm,
    y            = deterioration,
    method.args  = list(family = binomial),
    exponentiate = TRUE,
    label = list(
      age       ~ "Age group",
      sex       ~ "Sex",
      ambulance ~ "Ambulance arrival",
      triage    ~ "Triage category",
      time_code ~ "Time of arrival",
      prior_adm ~ "Prior hospital admission (30 days)",
      destiny   ~ "Presenting problem category"
    ),
    hide_n = TRUE
  ) %>%
  bold_labels() %>%
  bold_p(t = 0.05) %>%
  modify_header(estimate ~ "**OR**") %>%
  modify_caption(
    "**Table 3.** Univariable logistic regression: association of each variable with clinical deterioration"
  )

uv_table
Table 3. Univariable logistic regression: association of each variable with clinical deterioration
Characteristic OR 95% CI p-value
Age group


    16-19 — —
    20-39 0.95 0.63, 1.50 0.8
    40-59 2.20 1.48, 3.45 <0.001
    60-79 6.81 4.62, 10.6 <0.001
    80+ 14.3 9.68, 22.3 <0.001
Sex


    Male — —
    Female 0.85 0.76, 0.95 0.004
Ambulance arrival


    No — —
    Yes 4.85 4.34, 5.41 <0.001
Triage category


    Urgent — —
    Semi-Urgent 0.34 0.29, 0.39 <0.001
    Non-Urgent 0.11 0.06, 0.17 <0.001
Time of arrival


    0800-1759 — —
    1800-2259 0.75 0.65, 0.86 <0.001
    2300-0759 0.78 0.67, 0.91 0.001
Prior hospital admission (30 days)


    No — —
    Yes 2.48 2.08, 2.93 <0.001
Presenting problem category


    Abdominal/GI — —
    Cardiovascular 0.81 0.55, 1.17 0.3
    General symptoms 1.84 1.57, 2.16 <0.001
    Febrile illness 1.67 1.26, 2.18 <0.001
    Injury 0.29 0.22, 0.38 <0.001
    Respiratory 2.36 1.94, 2.87 <0.001
    Musculoskeletal 0.63 0.49, 0.80 <0.001
    Neurological 0.74 0.57, 0.94 0.017
    Mental health 0.45 0.31, 0.65 <0.001
    Toxicological 0.31 0.08, 0.82 0.046
    ENT/eye/head & neck 0.27 0.19, 0.37 <0.001
    Administrative 0.80 0.57, 1.10 0.2
    Genitourinary 0.57 0.40, 0.80 0.001
    Endocrine 2.68 1.25, 5.05 0.005
    Obstetrics/Gynaecology 0.19 0.10, 0.32 <0.001
    Skin/allergy 0.50 0.36, 0.68 <0.001
    Other medical 2.58 0.14, 13.2 0.4
Abbreviations: CI = Confidence Interval, OR = Odds Ratio

5 Multivariable Logistic Regression

Backwards stepwise selection from full model (AIC criterion). Weekday excluded a priori (p = 0.231 in Table 1; no clinical or statistical justification for inclusion). Mode of arrival collapsed to binary ambulance variable to avoid near-complete separation from small arrival categories.

mv_full <- glm(
  deterioration ~ age + sex + ambulance + triage + time_code + prior_adm + destiny,
  data   = df,
  family = binomial
)

# Backwards stepwise
mv_step <- step(mv_full, direction = "backward", trace = 0)

cat("Variables retained after backwards stepwise selection:\n")
## Variables retained after backwards stepwise selection:
print(formula(mv_step))
## deterioration ~ age + sex + ambulance + triage + time_code + 
##     prior_adm + destiny

5.1 Multicollinearity check

vif_result <- vif(mv_step)
kable(
  as.data.frame(vif_result),
  caption = "Generalised Variance Inflation Factors (GVIF) — values near 1 indicate no multicollinearity",
  digits  = 2
)
Generalised Variance Inflation Factors (GVIF) — values near 1 indicate no multicollinearity
GVIF Df GVIF^(1/(2*Df))
age 1.25 4 1.03
sex 1.03 1 1.01
ambulance 1.23 1 1.11
triage 1.13 2 1.03
time_code 1.04 2 1.01
prior_adm 1.02 1 1.01
destiny 1.30 16 1.01

5.2 Multivariable model results

mv_table <- tbl_regression(
  mv_step,
  exponentiate = TRUE,
  label = list(
    age       ~ "Age group",
    sex       ~ "Sex",
    ambulance ~ "Ambulance arrival",
    triage    ~ "Triage category",
    time_code ~ "Time of arrival",
    prior_adm ~ "Prior hospital admission (30 days)",
    destiny   ~ "Presenting problem category"
  )
) %>%
  bold_labels() %>%
  bold_p(t = 0.05) %>%
  modify_header(estimate ~ "**Adjusted OR**") %>%
  modify_caption(
    "**Table 4.** Multivariable logistic regression: independent predictors of clinical deterioration"
  )

mv_table
Table 4. Multivariable logistic regression: independent predictors of clinical deterioration
Characteristic Adjusted OR 95% CI p-value
Age group


    16-19 — —
    20-39 0.87 0.58, 1.38 0.5
    40-59 1.73 1.15, 2.70 0.012
    60-79 3.95 2.66, 6.14 <0.001
    80+ 5.61 3.75, 8.79 <0.001
Sex


    Male — —
    Female 0.84 0.75, 0.95 0.004
Ambulance arrival


    No — —
    Yes 2.52 2.23, 2.86 <0.001
Triage category


    Urgent — —
    Semi-Urgent 0.48 0.41, 0.56 <0.001
    Non-Urgent 0.18 0.11, 0.28 <0.001
Time of arrival


    0800-1759 — —
    1800-2259 0.81 0.70, 0.94 0.005
    2300-0759 0.76 0.65, 0.89 <0.001
Prior hospital admission (30 days)


    No — —
    Yes 1.39 1.16, 1.66 <0.001
Presenting problem category


    Abdominal/GI — —
    Cardiovascular 0.55 0.36, 0.79 0.002
    General symptoms 1.64 1.38, 1.94 <0.001
    Febrile illness 1.58 1.18, 2.09 0.002
    Injury 0.44 0.33, 0.57 <0.001
    Respiratory 1.39 1.13, 1.71 0.002
    Musculoskeletal 0.75 0.58, 0.96 0.028
    Neurological 0.57 0.44, 0.73 <0.001
    Mental health 0.53 0.35, 0.76 <0.001
    Toxicological 0.29 0.07, 0.78 0.036
    ENT/eye/head & neck 0.44 0.31, 0.60 <0.001
    Administrative 0.98 0.69, 1.36 >0.9
    Genitourinary 0.44 0.31, 0.62 <0.001
    Endocrine 1.56 0.72, 3.01 0.2
    Obstetrics/Gynaecology 0.48 0.24, 0.84 0.017
    Skin/allergy 0.92 0.65, 1.27 0.6
    Other medical 4.78 0.25, 27.5 0.2
Abbreviations: CI = Confidence Interval, OR = Odds Ratio

5.3 Multivariable model AUC

df$mv_predicted <- predict(mv_step, type = "response")

roc_mv <- roc(deterioration ~ mv_predicted, data = df, ci = TRUE)

cat("MV model AUC:", round(auc(roc_mv), 4), "\n")
## MV model AUC: 0.828
cat("95% CI (DeLong):", round(ci(roc_mv)[1], 4),
    "-", round(ci(roc_mv)[3], 4), "\n")
## 95% CI (DeLong): 0.8176 - 0.8384

6 Model Comparison

DeLong’s test for correlated ROC curves (appropriate when both models are evaluated on the same dataset).

roc_test <- roc.test(roc_start, roc_mv)

# Summary table
comparison <- data.frame(
  Model  = c("START composite score", "Multivariable model"),
  AUC    = c(round(auc(roc_start), 4), round(auc(roc_mv), 4)),
  `95% CI` = c(
    paste0(round(ci(roc_start)[1], 3), " – ", round(ci(roc_start)[3], 3)),
    paste0(round(ci(roc_mv)[1], 3),    " – ", round(ci(roc_mv)[3], 3))
  ),
  check.names = FALSE
)
kable(comparison,
      caption = paste0(
        "Table 5. AUC comparison. DeLong's test: Z = ",
        round(roc_test$statistic, 2),
        ", p ", format.pval(roc_test$p.value, digits = 3),
        " (difference = ", round(auc(roc_mv) - auc(roc_start), 4),
        ", 95% CI ",
        round(roc_test$conf.int[1], 4), " – ",
        round(roc_test$conf.int[2], 4), ")"
      ),
      align = "lrr")
Table 5. AUC comparison. DeLong’s test: Z = -8.61, p <2e-16 (difference = 0.0242, 95% CI -0.0297 – -0.0187)
Model AUC 95% CI
START composite score 0.8038 0.793 – 0.815
Multivariable model 0.8280 0.818 – 0.838
# Overlay ROC curves
plot(roc_start,
     col  = "#2C7BB6",
     lwd  = 2,
     main = "ROC Curves: START Score vs Multivariable Model")
plot(roc_mv,
     col  = "#D7191C",
     lwd  = 2,
     add  = TRUE)
legend("bottomright",
       legend = c(
         paste0("START score  (AUC ", round(auc(roc_start), 3), ")"),
         paste0("MV model     (AUC ", round(auc(roc_mv),    3), ")")
       ),
       col    = c("#2C7BB6", "#D7191C"),
       lwd    = 2,
       bty    = "n")


7 Session Info

sessionInfo()
## R version 4.6.1 (2026-06-24)
## Platform: aarch64-apple-darwin23
## Running under: macOS Tahoe 26.5.1
## 
## Matrix products: default
## BLAS:   /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRblas.0.dylib 
## LAPACK: /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.1
## 
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
## 
## time zone: Australia/Sydney
## tzcode source: internal
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
##  [1] knitr_1.51              flextable_0.9.12        gtsummary_2.5.1        
##  [4] car_3.1-5               carData_3.0-6           ResourceSelection_0.3-6
##  [7] pROC_1.19.0.1           scales_1.4.0            ggplot2_4.0.3          
## [10] tidyr_1.3.2             dplyr_1.2.1             readxl_1.5.0           
## 
## loaded via a namespace (and not attached):
##  [1] gtable_0.3.6            xfun_0.59               bslib_0.11.0           
##  [4] lattice_0.22-9          vctrs_0.7.3             tools_4.6.1            
##  [7] generics_0.1.4          tibble_3.3.1            pkgconfig_2.0.3        
## [10] Matrix_1.7-5            data.table_1.18.4       RColorBrewer_1.1-3     
## [13] S7_0.2.2                uuid_1.2-2              gt_1.3.0               
## [16] lifecycle_1.0.5         compiler_4.6.1          farver_2.1.2           
## [19] stringr_1.6.0           textshaping_1.0.5       litedown_0.9           
## [22] fontquiver_0.2.1        fontLiberation_0.1.0    htmltools_0.5.9        
## [25] sass_0.4.10             yaml_2.3.12             Formula_1.2-5          
## [28] pillar_1.11.1           jquerylib_0.1.4         broom.helpers_1.22.0   
## [31] openssl_2.4.2           cachem_1.1.0            abind_1.4-8            
## [34] fontBitstreamVera_0.1.1 commonmark_2.0.0        tidyselect_1.2.1       
## [37] zip_3.0.0               digest_0.6.39           stringi_1.8.7          
## [40] purrr_1.2.2             forcats_1.0.1           labeling_0.4.3         
## [43] labelled_2.16.0         fastmap_1.2.0           grid_4.6.1             
## [46] cli_3.6.6               magrittr_2.0.5          cards_0.8.0            
## [49] broom_1.0.13            withr_3.0.3             gdtools_0.5.1          
## [52] backports_1.5.1         cardx_0.3.3             rmarkdown_2.31         
## [55] officer_0.7.5           cellranger_1.1.0        hms_1.1.4              
## [58] askpass_1.2.1           ragg_1.5.2              evaluate_1.0.5         
## [61] haven_2.5.5             markdown_2.0            rlang_1.2.0            
## [64] Rcpp_1.1.1-1.1          glue_1.8.1              xml2_1.6.0             
## [67] rstudioapi_0.19.0       jsonlite_2.0.0          R6_2.6.1               
## [70] systemfonts_1.3.2       fs_2.1.0