Decision Curve Analysis

Authors

[Editor] Shixiang Wang;

[Contributors] ZCode (GLM) โ€” AI-generated, independently AI-reviewed.

Modified

2026-10-29

๐Ÿค– AI Skill โ€” Download the Bizard unified skill for your AI assistant โฌ‡๏ธ Download Skill ZIP
๐Ÿ”Œ MCP Server โ€” Let AI agents (Claude Code, Cursor, โ€ฆ) query Bizard live: claude mcp add bizard -- npx @openbiox/bizard-mcp โ€” or the free hosted instance (no install, faster from mainland China): claude mcp add --transport http bizard http://biotree.top:38123/bizard-mcp/mcp ๐Ÿ“– MCP Docs
๐Ÿ“– Cite Bizard โ€” if Bizard helps your research, please cite: Li, K., Zheng, H., Huang, K., et al. (2026). Bizard: A Community-Driven Platform for Accelerating and Enhancing Biomedical Data Visualization. iMetaMed, e70038. ๐Ÿ”— DOI / BibTeX

Decision curve analysis (DCA) evaluates the clinical usefulness of a prediction model: how much net benefit does acting on the modelโ€™s predictions bring, compared with treating everyone or treating no one, across a range of risk thresholds? Net benefit equals the true positives gained per patient minus the false positives, each false positive weighted by the ratio of harm-to-benefit implied by the threshold [1]. A model is only clinically useful if its net benefit exceeds both default strategies in the threshold range relevant to the decision at hand โ€” discrimination (AUC) and calibration alone cannot answer this [2]. Together with ROC curves and calibration curves, DCA is one of the standard evaluation plots of clinical prediction models.

This tutorial covers four DCA workflows in R with the dcurves package [3], the maintained implementation by the authors of the method (the older rmda package has been removed from CRAN):

  1. Decision curves for binary endpoints with two competing models;
  2. Decision curves for survival endpoints at a fixed horizon;
  3. Net interventions avoided โ€” how many interventions per 100 patients the model can safely reduce;
  4. A hand-computed clinical impact curve showing detected events per 1000 patients.

Example

DCAPlot DEMO

Setup

  • System Requirements: Cross-platform (Linux/MacOS/Windows)

  • Programming language: R

  • Dependent packages: survival; dplyr; rms; dcurves; ggplot2

# Installing necessary packages
if (!requireNamespace("rms", quietly = TRUE)) {
  install.packages("rms")
}
if (!requireNamespace("dcurves", quietly = TRUE)) {
  install.packages("dcurves")
}

# Load packages
library(survival)
library(dplyr)
library(ggplot2)
library(rms)
library(dcurves)
sessioninfo::session_info("attached")
โ”€ Session info โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 setting  value
 version  R version 4.6.1 (2026-06-24)
 os       Ubuntu 24.04.5 LTS
 system   x86_64, linux-gnu
 ui       X11
 language (EN)
 collate  C.UTF-8
 ctype    C.UTF-8
 tz       UTC
 date     2026-10-29
 pandoc   3.1.3 @ /usr/bin/ (via rmarkdown)
 quarto   1.10.19 @ /usr/local/bin/quarto

โ”€ Packages โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 ! package  * version    date (UTC) lib source
   dcurves  * 0.5.1      2025-11-15 [1] RSPM
 P dplyr    * 1.2.1      2026-04-03 [?] RSPM
 P ggplot2  * 4.0.3.9000 2026-10-26 [?] Github (tidyverse/ggplot2@b638d65)
 P Hmisc    * 5.3-0      2026-09-06 [?] RSPM
 P rms      * 8.1-1      2026-02-18 [?] RSPM
   survival * 3.8-6      2026-01-16 [3] CRAN (R 4.6.1)

 [1] /home/runner/.cache/R/renv/library/Bizard-5140bdc6/linux-ubuntu-noble/R-4.6/x86_64-pc-linux-gnu
 [2] /home/runner/work/_temp/Library
 [3] /opt/R/4.6.1/lib/R/library

 * โ”€โ”€ Packages attached to the search path.
 P โ”€โ”€ Loaded and on-disk path mismatch.

โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

Data Preparation

We use the lung data set from the survival package (North Central Cancer Treatment Group lung cancer trial) [4]. Every strategy fed to dca() must be a predicted probability between 0 and 1, so we derive:

  • risk_full / risk_ecog: predicted 1-year mortality from a full and a simple logistic model (binary endpoint);
  • risk1y_cph / risk1y_ecog: predicted 1-year mortality risk from full and simple Cox models (survival endpoint).
data(lung, package = "survival")

lung2 <- lung %>%
  filter(complete.cases(age, sex, ph.ecog, ph.karno, pat.karno, meal.cal, wt.loss)) %>%
  mutate(y1y = as.integer(time < 365 & status == 2))  # death within 365 days
  # Caveat: patients censored before day 365 are counted as non-events.

# Binary endpoint: logistic models
m_full   <- glm(y1y ~ age + sex + ph.ecog + meal.cal + wt.loss,
                data = lung2, family = binomial)
m_simple <- glm(y1y ~ ph.ecog, data = lung2, family = binomial)
lung2$risk_full  <- fitted(m_full)
lung2$risk_ecog  <- fitted(m_simple)

# Survival endpoint: Cox models with predicted 1-year risk
dd <- datadist(lung2)
options(datadist = "dd")
cph_full <- cph(Surv(time, status) ~ age + sex + ph.ecog,
                data = lung2, x = TRUE, y = TRUE, surv = TRUE, time.inc = 365)
cph_ecog <- cph(Surv(time, status) ~ ph.ecog,
                data = lung2, x = TRUE, y = TRUE, surv = TRUE, time.inc = 365)
lung2$risk1y_cph  <- 1 - Survival(cph_full)(365, predict(cph_full, type = "lp"))
lung2$risk1y_ecog <- 1 - Survival(cph_ecog)(365, predict(cph_ecog, type = "lp"))

head(lung2[, c("y1y", "risk_full", "risk_ecog", "risk1y_cph", "risk1y_ecog")])
  y1y risk_full risk_ecog risk1y_cph risk1y_ecog
2   0 0.3355593 0.3252807  0.5136877   0.4310632
4   1 0.6045635 0.5344500  0.6454549   0.5935850
6   0 0.6759279 0.5344500  0.6941938   0.5935850
7   1 0.6826392 0.7321672  0.6593625   0.7624603
8   1 0.6638663 0.7321672  0.6679848   0.7624603
9   1 0.6584805 0.5344500  0.6339175   0.5935850

Visualization

1. Decision Curves for a Binary Endpoint

dca() always plots the two default strategies โ€” treat all and treat none โ€” plus one curve per model. Only thresholds with clinical relevance (here 0โ€“50%) should be shown [2].

dca_bin <- dca(y1y ~ risk_full + risk_ecog, data = lung2,
               thresholds = seq(0, 0.5, 0.01))
plot(dca_bin, smooth = FALSE) +
  labs(x = "Threshold probability", y = "Net benefit") +
  theme_bw()
Figureย 1: Decision curves of two models for 1-year mortality; net benefit versus risk threshold.

Reading the plot: at a threshold of 20% (e.g.ย โ€œintensify follow-up if predicted 1-year death risk exceeds 20%โ€), a model with higher net benefit than โ€œtreat allโ€ identifies patients safely; where model curves fall below both defaults the model adds no value.

2. Decision Curves for a Survival Endpoint

For censored time-to-event outcomes, pass a Surv object and the horizon of interest; dcurves estimates the Kaplan-Meier-based event rate at that time.

dca_surv <- dca(Surv(time, status) ~ risk1y_cph + risk1y_ecog, data = lung2,
                time = 365, thresholds = seq(0, 0.5, 0.01))
plot(dca_surv, smooth = FALSE) +
  labs(x = "Threshold probability (365-day mortality)", y = "Net benefit") +
  theme_bw()
Figureย 2: Decision curves of two Cox models for the 365-day mortality risk.

3. Net Interventions Avoided

A more tangible translation: for each threshold, how many interventions per 100 patients could be avoided without missing true events, compared with intervening on everyone?

nia <- net_intervention_avoided(dca_bin, nper = 100)
plot(nia, smooth = FALSE) +
  labs(x = "Threshold probability", y = "Net interventions avoided (per 100)") +
  theme_bw()
Figureย 3: Net interventions avoided per 100 patients (both models shown).

Positive values mean that using the model would spare that many interventions per 100 patients relative to treating everybody, at the same number of missed events.

4. Hand-Computed Clinical Impact Curve

The clinical impact curve shows, per 1000 patients, how many would be flagged as high risk and how many of those are true events, as the threshold varies [1]. dcurves does not ship this variant, but it is a direct calculation:

n <- nrow(lung2)
impact <- do.call(rbind, lapply(seq(0.1, 0.5, 0.05), function(t) {
  flagged <- lung2$risk_full >= t
  data.frame(threshold = t,
             high_risk = sum(flagged) / n * 1000,
             events_detected = sum(lung2$y1y[flagged]) / n * 1000)
}))

ggplot(impact, aes(x = threshold)) +
  geom_line(aes(y = high_risk, color = "Flagged as high risk"), linewidth = 1.1) +
  geom_line(aes(y = events_detected, color = "True events among flagged"), linewidth = 1.1) +
  scale_color_manual(values = c("#2166AC", "#B2182B"), name = NULL) +
  labs(x = "Risk threshold",
       y = "Count per 1000 patients",
       title = "Clinical impact of the full model") +
  theme_bw() +
  theme(legend.position = "top")
Figureย 4: Clinical impact curve: patients flagged and true events detected per 1000 patients.

The gap between the two lines is the number of unnecessary interventions per 1000 patients at each threshold โ€” small gaps at clinically relevant thresholds indicate a useful model.

Application

Decision curves are routinely reported in clinical prediction-model studies next to ROC and calibration curves. The panel below shows the typical pairing: (A) decision curves across thresholds and (B) the clinical impact curve.

DCAPlot Application

Reference

[1] Vickers AJ, Elkin EB. Decision curve analysis: a novel method for evaluating prediction models. Med Decis Making. 2006;26(6):565-574. doi:10.1177/0272989X06295361

[2] Vickers AJ, van Calster B, Steyerberg EW. A simple, step-by-step guide to interpreting decision curve analysis. Diagn Progn Res. 2019;3(1):18. doi:10.1186/s41512-019-0064-7

[3] Sjoberg DD. dcurves: Decision Curve Analysis for Model Evaluation. R package version 0.5.1. doi:10.32614/CRAN.package.dcurves

[4] Loprinzi CL, Laurie JA, Wieand HS, et al.ย Prospective evaluation of prognostic variables from patient-completed questionnaires. North Central Cancer Treatment Group. J Clin Oncol. 1994;12(3):601-607. doi:10.1200/JCO.1994.12.3.601