Vignette: Creating Waterfall Plots
ClinicoPath Development Team
2026-08-14
Source:vignettes/06-function-waterfall.Rmd
06-function-waterfall.RmdWhat a Waterfall Plot Is For
One bar per patient, sorted, showing the change in tumour burden from baseline. It is the standard first figure of a response-evaluable trial cohort because it shows the whole distribution rather than a single response rate — you can see at a glance whether a 35% response rate came from a few dramatic responders or from many patients sitting just past the threshold.
waterfall takes either percentage change already
calculated, or raw measurements over time from
which it calculates the change itself and can additionally draw a spider
plot.
Percentage Input
The bundled waterfall_percentage_basic dataset has one
row per patient with the change already computed.
library(OncoPath)
data(waterfall_percentage_basic, package = "OncoPath")
head(waterfall_percentage_basic)
#> PatientID Response Treatment
#> 1 PT01 -100 Drug A
#> 2 PT02 -85 Drug A
#> 3 PT03 -60 Drug A
#> 4 PT04 -45 Drug A
res <- waterfall(
data = waterfall_percentage_basic,
patientID = "PatientID",
responseVar = "Response",
inputType = "percentage",
sortBy = "response",
showThresholds = TRUE, # the -30% and +20% RECIST lines
showMedian = TRUE,
labelOutliers = TRUE,
showCategoryLabels = TRUE, # CR/PR/SD/PD above each bar
showClinicalSignificance = TRUE,
showConfidenceIntervals = TRUE # CIs on ORR and DCR
)Clinical Metrics, With Uncertainty
metric value ci_lower ci_upper
Objective Response Rate (ORR) 35.0% 15.4 59.2
Disease Control Rate (DCR) 80.0% 56.3 94.3
showConfidenceIntervals is on by default and worth
leaving on. An ORR of 35% in 20 patients has a 95% interval running from
15% to 59% — the point estimate alone invites a confidence the data do
not support, and this is exactly the figure that ends up in a slide
deck.
The interpretation text that accompanies these figures is deliberately hedged (“general benchmark; verify against tumor-specific thresholds”). A 35% ORR means something different in pancreatic cancer than in melanoma, and the analysis has no way to know which you are looking at.
Raw Longitudinal Input and the Spider Plot
With repeated measurements per patient, give waterfall
the time variable and set inputType = "raw". It computes
the change from baseline itself and can draw a spider plot of the
individual trajectories alongside the waterfall.
data(waterfall_raw_longitudinal, package = "OncoPath")
head(waterfall_raw_longitudinal)
#> PatientID Time Measurement
res_spider <- waterfall(
data = waterfall_raw_longitudinal,
patientID = "PatientID",
responseVar = "Measurement",
timeVar = "Time",
inputType = "raw",
showWaterfallPlot = TRUE,
showSpiderPlot = TRUE,
timeUnitLabel = "months", # generic | days | weeks | months | years
showSpiderLabels = TRUE, # label each line with its patient ID
showResponseDuration = TRUE # time-to-response and duration-of-response table
)showResponseDuration needs the time variable, so it is
only available on raw input. Duration of response is often the more
clinically meaningful endpoint of the two: a 40% shrinkage that lasts
three months and one that lasts two years produce the same waterfall
bar.
Options Worth Knowing About
Several of these were undocumented until now.
| Option | Default | What it does |
|---|---|---|
sortDirection |
"conventional" |
"conventional" puts the best responses on the left, as
in published figures. "reverse" flips it. (Not
"decreasing" — that value is rejected.) |
showBaseline |
TRUE |
The Y = 0 reference line |
showCategoryLabels |
FALSE |
RECIST category above each bar |
confirmationVar |
— | A column marking which responses were confirmed on a later scan. Unconfirmed responses overstate activity |
ongoingVar |
— | A column marking patients still on treatment, whose responses may deepen |
responseCategoryVar |
— | Override the computed RECIST category with an investigator-assigned one |
annotationVars |
— | Extra tracks drawn below the bars (mutation status, prior lines, and so on) |
showResponseDuration |
FALSE |
Time-to-response and duration-of-response; needs
timeVar
|
showSpiderLabels |
FALSE |
Patient ID on each spider line |
timeUnitLabel |
"generic" |
Axis label for the spider plot |
showClinicalSignificance |
FALSE |
Clinical-significance threshold annotations |
showConfidenceIntervals |
TRUE |
Confidence intervals on ORR and DCR |
generateCopyReadyReport |
FALSE |
A paste-ready results paragraph |
showExplanations |
FALSE |
Notes explaining each output |
enableGuidedMode |
FALSE |
Step-by-step prompts for first-time users |
A Caveat on Sorting
Sorting by response is what makes the figure readable, and it is also what makes it easy to over-read. The bars are ordered by outcome, so any left-to-right pattern in an annotation track below them is a consequence of that ordering, not evidence of association. If you want to test whether a biomarker predicts response, test it — do not read it off the waterfall.