Responder Analysis: Who Cleared the Threshold, by Group
Source:R/responder_analysis.R
responder_analysis.RdThe clinical and behavioral endpoint that mean differences hide: the
proportion of each group whose outcome reaches a meaningful threshold
(a minimal clinically important difference, a remission cut, a mastery
criterion). For each group the function reports the responder count and
proportion with a Wilson confidence interval; with exactly two groups it
adds the risk difference with the Newcombe (1998) score-based hybrid
interval and the number needed to treat; and across any number of
groups it reports the omnibus chi square test of equal responder
proportions. An optional sweep repeats the analysis over a grid
of thresholds, making explicit how conclusions depend on where the line
is drawn, disclosing the threshold dependence that any
single-threshold claim leaves implicit.
Usage
responder_analysis(
x,
group,
threshold,
direction = c("ge", "le"),
conf_level = 0.95,
sweep = NULL
)Arguments
- x
Numeric vector of outcomes (for example, change scores).
- group
Group labels, one per observation (coerced to factor; the first level is the reference for the two-group difference).
- threshold
The cut defining response.
- direction
"ge"(default): a responder hasx >= threshold;"le":x <= threshold(for outcomes where lower is better).- conf_level
Confidence level for all intervals. Defaults to 0.95.
- sweep
Optional numeric vector of additional thresholds; the analysis is repeated at each and stacked with a leading
thresholdcolumn.
Value
A tidy wide data.frame (class dmar_tbl). One row
per group with group, n, responders,
estimate (the proportion), lower_limit,
upper_limit; with two groups, a difference row
(second level minus first) and an nnt row; and a final
omnibus row carrying chi_square, df, and
p_value (columns that are NA on the other rows). When
sweep is supplied, the same table is stacked per threshold
with a leading threshold column.
Details
Per-group intervals are Wilson score intervals
(ci_proportion). The two-group risk difference uses
Newcombe's method 10: the difference interval is assembled from the two
Wilson limits, which keeps it inside [-1, 1] and well behaved at
boundary counts. The number needed to treat is \(1/|\Delta|\), with
its interval from the inverted difference limits when the difference
interval excludes zero; when it includes zero the NNT interval is
reported as NA (the interval is disjoint and an interval on the
NNT scale would mislead; Altman, 1998). Dichotomizing throws away
information, so a responder analysis complements, never replaces, the
analysis of the continuous outcome (Maxwell, Delaney, & Kelley, 2027).
References
Altman, D. G. (1998). Confidence intervals for the number needed to treat. BMJ, 317(7168), 1309–1312. doi:10.1136/bmj.317.7168.1309
Newcombe, R. G. (1998). Interval estimation for the difference between independent proportions: Comparison of eleven methods. Statistics in Medicine, 17(8), 873–890. doi:10.1002/(SICI)1097-0258(19980430)17:8<873::AID-SIM779>3.0.CO;2-I
Maxwell, S. E., Delaney, H. D., & Kelley, K. (2027). Designing experiments and analyzing data: A model comparison perspective (4th ed.). Routledge.
See also
ci_proportion for the per-group interval;
nnt_from_smd for the model-based route to the number
needed to treat from a standardized mean difference;
cliff_delta and proportion_of_superiority
for dominance-style effect sizes on the continuous outcome.
Other effect size estimates:
cles(),
cliff_delta(),
correction_for_attenuation(),
eta_squared(),
eta_squared_generalized(),
eta_squared_partial(),
expected_partial_r(),
expected_r(),
expected_smd(),
nnt_from_smd(),
omega_squared(),
omega_squared_partial(),
probability_of_superiority_paired(),
proportion_of_superiority(),
smd_trimmed()
Author
Ken Kelley kkelley@nd.edu
Examples
# A two-arm trial: change scores, response defined as a gain of 10+.
set.seed(113)
change <- c(rnorm(60, 8, 9), rnorm(60, 13, 9))
arm <- rep(c("control", "treatment"), each = 60)
responder_analysis(change, arm, threshold = 10)
#> group n responders estimate lower_limit upper_limit chi_square df
#> control 60 30 0.5 0.377 0.623 <NA> <NA>
#> treatment 60 35 0.583 0.457 0.699 <NA> <NA>
#> difference <NA> <NA> 0.0833 -0.0925 0.252 <NA> <NA>
#> nnt <NA> <NA> 12 <NA> <NA> <NA> <NA>
#> omnibus <NA> <NA> <NA> <NA> <NA> 0.839 1
#> p_value
#> <NA>
#> <NA>
#> <NA>
#> <NA>
#> 0.3596
#>
#> Confidence level: 95%
# How threshold-dependent is that conclusion?
responder_analysis(change, arm, threshold = 10, sweep = c(5, 15))
#> threshold group n responders estimate lower_limit upper_limit
#> 10 control 60 30 0.5 0.377 0.623
#> 10 treatment 60 35 0.583 0.457 0.699
#> 10 difference <NA> <NA> 0.0833 -0.0925 0.252
#> 10 nnt <NA> <NA> 12 <NA> <NA>
#> 10 omnibus <NA> <NA> <NA> <NA> <NA>
#> 5 control 60 39 0.65 0.524 0.758
#> 5 treatment 60 50 0.833 0.72 0.907
#> 5 difference <NA> <NA> 0.183 0.0263 0.33
#> 5 nnt <NA> <NA> 5.45 3.03 38
#> 5 omnibus <NA> <NA> <NA> <NA> <NA>
#> 15 control 60 18 0.3 0.199 0.425
#> 15 treatment 60 22 0.367 0.256 0.493
#> 15 difference <NA> <NA> 0.0667 -0.1 0.229
#> 15 nnt <NA> <NA> 15 <NA> <NA>
#> 15 omnibus <NA> <NA> <NA> <NA> <NA>
#> chi_square df p_value
#> <NA> <NA> <NA>
#> <NA> <NA> <NA>
#> <NA> <NA> <NA>
#> <NA> <NA> <NA>
#> 0.839 1 0.3596
#> <NA> <NA> <NA>
#> <NA> <NA> <NA>
#> <NA> <NA> <NA>
#> <NA> <NA> <NA>
#> 5.26 1 0.0218
#> <NA> <NA> <NA>
#> <NA> <NA> <NA>
#> <NA> <NA> <NA>
#> <NA> <NA> <NA>
#> 0.6 1 0.4386
#>
#> Confidence level: 95%