--- title: "Getting started with respondeR" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Getting started with respondeR} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") ``` ```{r setup} library(respondeR) ``` ## The idea Trials of continuous outcomes usually report a **mean change** and **standard deviation** in each arm. Those are hard to communicate: a standardized mean difference of 0.3 means little to a patient. A **responder analysis** translates the continuous result into something concrete: the proportion of patients who improve by at least a **minimal important difference (MID)**. It contrasts the arms on familiar scales: risk difference, risk ratio, odds ratio, number needed to treat. respondeR does this from summary statistics alone, using the cut-point approach of Anzures-Cabrera, Sarpatwari & Higgins (2011). It never needs individual patient data. ## Data format One row per study. The experimental arm columns end in `_e`, the control arm in `_c`: ```{r} sample_responder_data ``` | Column | Meaning | |--------|---------| | `study` | Study label | | `change_e`, `change_c` | Mean change per arm | | `sd_e`, `sd_c` | SD of change per arm | | `n_e`, `n_c` | Sample size per arm | ## A first analysis Suppose a change above `1` is clinically meaningful. With the default settings, `responder_analysis()` returns one row per pooling method: ```{r} res <- responder_analysis(sample_responder_data, mid = 1) res[, c("method", "p_e", "p_c", "rd", "rd_lb", "rd_ub", "rr", "or", "nnt")] ``` * `p_e`, `p_c` are the experimental and control responder proportions (on the `[0, 1]` scale). * `rd`, `rr`, `or`, `nnt` are the between-arm contrasts, each with a confidence interval (`*_lb`, `*_ub`). * The `individual` method pools per-study risk differences, so it reports a pooled `rd` but leaves `p_e`/`p_c` as `NA`. For a quick, readable summary use `format_responder_results()`: ```{r} format_responder_results(res) ``` ## Which way is "better"? By default a *higher* change is a response. For outcomes where *lower* is better (pain, symptom scores), set `direction = "lower"`: ```{r} responder_analysis(sample_responder_data, mid = 1, direction = "lower", method = "individual")[, c("method", "rd", "rd_lb", "rd_ub")] ``` ## Baseline risk: matched or median control By default each summary method pools the control arm the same way as the experimental arm. To instead hold the baseline risk at the median control arm for every summary method, as in the simulation study behind this package (Sofi-Mahmudi, 2024), set `control = "median"`. This returns point estimates, because the median control arm has no variance model: ```{r} responder_analysis(sample_responder_data, mid = 1, control = "median")[, c("method", "p_e", "p_c", "rd")] ``` ## Per-study results and a forest plot `responder_rd_individual()` returns the per-study risk differences that feed a forest plot: ```{r} responder_rd_individual(sample_responder_data, mid = 1) ``` ```{r, fig.width = 6, fig.height = 3.2, fig.alt = "Forest plot of per-study responder risk differences"} ps <- responder_rd_individual(sample_responder_data, mid = 1) pooled <- responder_analysis(sample_responder_data, mid = 1, method = "individual") y <- rev(seq_len(nrow(ps) + 1)) est <- c(ps$rd, pooled$rd) * 100 lo <- c(ps$ci_lb, pooled$rd_lb) * 100 hi <- c(ps$ci_ub, pooled$rd_ub) * 100 labels <- c(as.character(ps$study), "Pooled") op <- par(mar = c(4, 6, 1, 1)) plot(NA, xlim = range(c(lo, hi, 0)), ylim = c(0.5, length(y) + 0.5), yaxt = "n", xlab = "Risk difference (%)", ylab = "", bty = "n") abline(v = 0, lty = 2, col = "grey60") segments(lo, y, hi, y, lwd = 2) points(est, y, pch = c(rep(15, nrow(ps)), 18), cex = c(rep(1.4, nrow(ps)), 2)) axis(2, at = y, labels = labels, las = 1, tick = FALSE) par(op) ``` ## Random effects and heterogeneity When studies disagree, use random-effects pooling. respondeR reports Cochran's Q, I-squared, tau-squared and a prediction interval: ```{r} responder_analysis(sample_responder_data, mid = 1, method = "individual", pooling = "random")[, c("method", "rd", "rd_lb", "rd_ub", "tau2", "i2", "pi_lb", "pi_ub")] ``` ## A threshold-free alternative Choosing a MID is sometimes contentious. The common-language effect size sidesteps it entirely: the probability that a randomly chosen treated patient responds better than a randomly chosen control. ```{r} cles <- responder_cles(sample_responder_data) sprintf("CLES = %.1f%% (%.1f%% to %.1f%%)", 100 * cles$cles, 100 * cles$cles_lb, 100 * cles$cles_ub) ``` ## A real example: VAS pain after exercise therapy The package bundles a real dataset, `vas_pain`: the 20 randomized trials of exercise for spinal health pooled for the visual analogue scale (VAS) pain outcome by Li, Bao, Wang and Zhao (2025). The change scores are post minus baseline VAS on a 0 to 10 cm scale, so a more negative value is a larger pain reduction; we analyze with `direction = "lower"` and a negative MID equal to the responder threshold. Using a 1.5 cm reduction as the minimal important difference: ```{r} res <- responder_analysis(vas_pain, mid = -1.5, direction = "lower", pooling = "random", ci_method = "hksj") format_responder_results(res) ``` Pooling the per-study estimates (the `individual` method, the most defensible), about 17 more exercise patients per 100 reach a 1.5 cm pain reduction than controls. The pool-then-dichotomize summaries give larger and more dispersed values here (weighted about 21, unweighted about 32, median about 47 per 100): that spread is a sign of heterogeneity across the 20 trials, and the individual method, which respects each trial's own scale, is the one to trust. The threshold-free common-language effect size avoids picking a cut-point: ```{r} cles <- responder_cles(vas_pain, direction = "lower") sprintf("A treated patient has less pain than a control %.0f%% of the time (%.0f%% to %.0f%%)", 100 * cles$cles, 100 * cles$cles_lb, 100 * cles$cles_ub) ``` (Data from Li et al. (2025), Frontiers in Sports and Active Living, \doi{10.3389/fspor.2025.1614906}, Figure 3, reproduced under CC BY 4.0.) ## The Shiny application Everything above is available in a point-and-click app: ```{r, eval = FALSE} launch_responder_analysis() ``` The same tool runs in the browser, with no installation, at . ## Where next See `vignette("methodology")` for the full statistical detail: each method's estimator and variance, the relative measures, the SMD bridge, the logit/MID-uncertainty/distribution options, assumptions and a method-choice guide.