The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.
The mirrored slopegraph was designed by Kati Maharry and is described in Maharry and Sullivan (2026, manuscript in preparation). It was first used in the supplementary appendix of the HOPE-3 trial report (McDonald et al., Lancet 2026, doi:10.1016/S0140-6736(26)01385-1). This package implements the display.
A mirror slope plot compares two treatment arms over a single observation interval. Both arms share one x-axis and converge at a central endpoint, so the between-arm gap at that endpoint is the primary visual quantity. The reference arm is drawn on the left and the comparator on the right; both mean lines meet at the centre, corresponding to the endpoint visit.
The x-axis is positional, not numeric: it encodes baseline versus endpoint for each arm and should not be read as elapsed time.
Because mirror_slope_plot() returns a standard
ggplot object, the full ggplot2 ecosystem remains available
for further customisation.
A plot is built in stages. Each stage returns a
mirror_data object, so the steps compose naturally with the
pipe. The object stores everything available; the plot call exposes only
what is requested.
prep_mirror_data() validates and structures the
analysis data.add_summary() attaches the group-level estimates that
define the mean lines.add_precision() optionally attaches per-arm standard
errors or intervals.add_difference() optionally attaches the between-arm
difference and its precision and p-value.mirror_slope_plot() renders the figure.We simulate a small change-from-baseline dataset with two arms and two visits.
set.seed(1)
n <- 40
df <- data.frame(
USUBJID = rep(paste0("S", seq_len(n)), each = 2),
TRTP = rep(c(rep("Placebo", n / 2), rep("Active", n / 2)), each = 2),
AVISIT = rep(c("Baseline", "Week 24"), times = n),
AVAL = c(rbind(rep(0, n),
c(rnorm(n / 2, -10, 5), rnorm(n / 2, -6, 5)))),
stringsAsFactors = FALSE
)
head(df, 4)
#> USUBJID TRTP AVISIT AVAL
#> 1 S1 Placebo Baseline 0.000000
#> 2 S1 Placebo Week 24 -13.132269
#> 3 S2 Placebo Baseline 0.000000
#> 4 S2 Placebo Week 24 -9.081783Prepare the data and attach the summary estimates:
md <- prep_mirror_data(
df,
trt_levels = c("Placebo", "Active"),
time_baseline = "Baseline",
time_endpoint = "Week 24",
value_label = "% Change from Baseline"
)
md <- add_summary(
md,
estimates = c("Placebo" = -10.2, "Active" = -5.9),
stat_label = "LS-Mean"
)
mirror_slope_plot(
md,
trt_colors = mirror_palette("colorblind"),
title = "Percent Change from Baseline at Week 24"
)
#> Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : font
#> family not found in Windows font database
#> Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : font
#> family not found in Windows font database
#> Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : font
#> family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font databaseAttaching a difference enables the annotation zone to the right of
the panel. show_difference displays the difference value;
show_slowing adds the derived relative benefit. Either one
also draws the shaded band between the two means.
md <- add_difference(
md,
estimate = 4.3,
ci_lower = 1.2,
ci_upper = 7.4,
p_value = 0.011
)
mirror_slope_plot(
md,
trt_colors = mirror_palette("colorblind"),
show_difference = TRUE,
show_slowing = TRUE,
title = "Percent Change from Baseline at Week 24",
footnote = "LS-means from a mixed model. Positive difference favours the active arm."
)
#> Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : font
#> family not found in Windows font database
#> Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : font
#> family not found in Windows font database
#> Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : font
#> family not found in Windows font database
#> Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : font
#> family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font databaseadd_precision() stores standard errors or confidence
intervals per arm. Setting show_precision = TRUE draws a
precision bar at each endpoint marker.
md <- add_precision(
md,
ci = list("Placebo" = c(-13.3, -7.1),
"Active" = c(-8.4, -3.4))
)
mirror_slope_plot(
md,
trt_colors = mirror_palette("colorblind"),
show_difference = TRUE,
show_slowing = TRUE,
show_precision = TRUE,
title = "Percent Change from Baseline at Week 24"
)
#> Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : font
#> family not found in Windows font database
#> Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : font
#> family not found in Windows font database
#> Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : font
#> family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font databaseshow_spaghetti = TRUE overlays each subject’s
baseline-to-endpoint line beneath the group means. For large samples,
reduce spaghetti_alpha to control overplotting.
mirror_slope_plot(
md,
trt_colors = mirror_palette("colorblind"),
show_spaghetti = TRUE,
spaghetti_alpha = 0.2,
show_difference = TRUE,
show_slowing = TRUE,
title = "With individual trajectories"
)
#> Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : font
#> family not found in Windows font database
#> Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : font
#> family not found in Windows font database
#> Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : font
#> family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font database
#> Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
#> font family not found in Windows font databasePrinting a mirror_data object summarises the arms,
analysed N, statistic label, and which optional elements are attached.
summary() returns per-arm, per-timepoint descriptives.
md
#> <mirror_data> schema 1.0
#> Reference (left) : Placebo (n=20)
#> Comparator (right): Active (n=20)
#> Timepoints : Baseline -> Week 24
#> Observations : 80 OBS rows
#> Summary : yes (LS-Mean)
#> Precision : yes
#> Difference : yes
summary(md)
#> ARM TIMEROLE N MEAN MIN MAX
#> 1 Active BASELINE 20 0.000000 0.00000 0.0000000
#> 2 Placebo BASELINE 20 0.000000 0.00000 0.0000000
#> 3 Active ENDPOINT 20 -6.032358 -15.94676 0.7933978
#> 4 Placebo ENDPOINT 20 -9.047381 -21.07350 -2.0235960See the Clinical workflow with ADaM data vignette for a realistic data-preparation pattern, and the Customising the plot vignette for theming, palettes, and combining multiple panels.
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.