DCA for Quantifying the Additional Benefit of a New Marker by Emily Vertosick and Andrew Vickers

Published

September 18, 2022

Additional Benefit of a New Marker

Prediction Model might gain accuracy if you’ll add more relevant features to existing models, but many times it’s not obvious what is the additional value of additional feature and how to quantify it in terms of Decision Making. The post Decision curve analysis for quantifying the additional benefit of a new marker by Emily Vertosick and Andrew Vickers show a simple example (the code presented here is almost identical to the original code presented in the link).

Preparing the Data

Loading the Data with Hmisc

library(Hmisc)
library(dplyr)
library(tibble)

getHdata(acath)
acath <- subset(acath, !is.na(choleste))

Fitting Logistic Regressions with rms

library(rms)

pre <- lrm(sigdz ~ rcs(age,4) * sex, data = acath)
pre_pred <- predict(pre, type='fitted')

post <- lrm(sigdz ~ rcs(age,4) * sex + 
              rcs(choleste,4) + rcs(age,4) %ia% rcs(choleste,4), data = acath)
post_pred <- predict(post, type='fitted')

acath_pred <- bind_cols(
    acath,
    pre_pred |> enframe(name = NULL, value = "pre"),
    post_pred |> enframe(name = NULL, value = "post")
  )

Conventional Decision Curve

library(dcurves)

dca_prepost <- dca(
    sigdz ~ pre + post,
    data = acath_pred,
    label = list(
      pre = "Age and Sex",
      post = "Age, Sex and Cholesterol"))

dca_prepost |>
  plot(smooth = TRUE)  + 
  theme_classic()  +
  theme(legend.position = "none")

library(rtichoke)
library(plotly)

performance_data_dc <- 
  prepare_performance_data(
  probs = list(
    "Age and Sex" = 
      acath_pred$pre,
    "Age, Sex and Cholesterol" = 
      acath_pred$post
  ),
  reals = list(acath_pred$sigdz)
)

performance_data_dc |>
  plot_decision_curve(
    color_values = 
      c("#00BFC4", "#C77CFF"),
    size = 350
  ) |>
  plotly::layout(
    yaxis = list(
      range =
        c(-0.07, 0.7)
    )
  )
00.5100.20.40.6
Prob. Threshold: 000.130.260.390.520.650.780.91Probability ThresholdNet Benefit

Specific Range of Probability Thresholds

library(dcurves)

dca_prepost_15_35 <- dca(
    sigdz ~ pre + post,
    data = acath_pred,
    thresholds = seq(0.15, 0.35, by = 0.05),
    label = list(
      pre = "Age and Sex",
      post = "Age, Sex and Cholesterol")) |>
  plot(type = 'net_benefit', 
       smooth = FALSE, 
       show_ggplot_code = FALSE)

dca_prepost_15_35 + 
  theme_classic()  + 
  theme(legend.position = "none")

performance_data_dc |> 
  rtichoke::plot_decision_curve(
    color_values = c("#00BFC4", "#C77CFF"),
    min_p_threshold = 0.15, 
    max_p_threshold = 0.35,
    size = 350
  ) |> 
  plotly::layout(
    yaxis = list(range =
                   c(-0.07, 0.7))
  ) 
0.150.20.250.30.3500.20.40.6
Prob. Threshold: 0.150.150.180.210.240.270.30.33Probability ThresholdNet Benefit

Interventions Avoided

dca_prepost |>
  net_intervention_avoided() |> 
  plot(type = 'net_intervention_avoided', 
       smooth = FALSE)  + 
  theme_classic()  +
  theme(legend.position = "none")

performance_data_dc |>
  rtichoke::plot_decision_curve(
    color_values = c("#F8766D", "#00BFC4"),
    type = "interventions avoided",
    size = 350
  ) |>
  plotly::layout(
    yaxis = list(range =
                   c(-10, 100))
  )
00.51020406080100
Prob. Threshold: 0.010.010.130.250.370.490.610.730.850.97Probability ThresholdInterventions Avoided (per 100)

Conventional and Interventions Avoided Combined (rtichoke code)

performance_data_dc |>
  plot_decision_curve(
    color_values = 
      c("#00BFC4", "#C77CFF"),
    type = "combined",
    size = 500
  )
02040608010000.20.40.60.8100.20.40.60.81
Prob. Threshold: 0.010.010.090.170.250.330.410.490.570.650.730.810.890.97Probability ThresholdInterventions Avoided (per 100)Interventions Avoided (per 100)Net BenefitNet Benefit