RMpersonChange: Single-subject change between two occasions

View source: R/person_change.R

RMpersonChangeR Documentation

Single-subject change between two occasions

Description

Tests, for each respondent, whether their person location moved between two occasions by more than measurement error allows. The statistic is the Rasch change index, RCI = (\hat\theta_2 - \hat\theta_1)/SE_{diff}, referred by default to a simulated rather than a normal null.

Usage

RMpersonChange(
  data_t1,
  data_t2,
  id = NULL,
  anchor = "stack",
  item_params = NULL,
  method = "WLE",
  estimator = "CML",
  null = "measurement",
  retest_sd = NULL,
  critical = "exact",
  alpha = 0.05,
  direction = "two.sided",
  conditional_crit = FALSE,
  sim_iter = 1000,
  parallel = TRUE,
  n_cores = NULL,
  seed = NULL,
  theta_range = c(-10, 10),
  verbose = FALSE,
  output = "dataframe"
)

Arguments

data_t1, data_t2

Data.frames or matrices of item responses at the two occasions. Same items, in the same order, one row per respondent, with row i of each being the same person. Items must be scored from 0.

id

Optional vector of respondent identifiers, length nrow(data_t1). Defaults to row numbers.

anchor

Character. Where the item calibration comes from: "stack" (default, both occasions calibrated together), "t1", or "t2". Ignored when item_params is supplied.

item_params

Optional pre-specified item parameters, either a named list of Andrich-threshold vectors or the long-format data.frame from RMitemParameters(), as in RMpersonParameters(). Required for a single respondent, since item parameters cannot be estimated from one person. Supplied thresholds are used as given and are not re-centred.

method

Character. Person-location estimator, "WLE" (default) or "EAP".

estimator

Character. How item parameters are estimated when item_params is NULL: "CML" (default) or "MML".

null

Character. Which null hypothesis is tested. "measurement" (default) treats the response process as the only source of variation. "retest" additionally treats occasion-to-occasion fluctuation as noise and requires retest_sd. See Details, and note that the two answer different questions.

retest_sd

Numeric, or NULL (default). The per-occasion SD, in logits, of a respondent's occasion-specific deviation. Required when null = "retest" and not accepted otherwise. Estimate it with RMretestSD().

critical

How the critical values are obtained. "exact" (default) enumerates the null distribution, "simulate" estimates it by Monte Carlo, and a positive number is used as a symmetric cutoff on the RCI referred to a normal null (for example 1.96).

alpha

Numeric in (0, 1). Error rate for a single respondent. Default 0.05.

direction

Character. "two.sided" (default), or "increase" / "decrease" for a one-sided test. These name the direction of \theta; whether an increase is an improvement depends on how the scale is oriented.

conditional_crit

Logical. If TRUE, critical values are simulated separately for each respondent at their own location rather than pooled across the sample. Default FALSE. Ignored when critical is numeric.

sim_iter

Integer. Simulation iterations when critical = "simulate". Default 1000.

parallel

Logical. Use mirai for the simulation if available. Default TRUE.

n_cores

Integer or NULL. Parallel workers. When NULL, getOption("mc.cores") is checked first; if neither is set, the simulation runs sequentially.

seed

Integer or NULL. Random seed. See easyRasch2-reproducibility.

theta_range

Numeric length 2. Search range for the WLE root and bounds for the EAP grid. Default c(-10, 10).

verbose

Logical. Print a progress bar for the simulation. Default FALSE.

output

Character. "dataframe" (default), "kable", or "ggplot".

Details

Which null is tested. Following Zumbo (2026), the estimand is fixed by what counts as systematic and what counts as residual, and that choice has to be made before an estimator is picked. Two are available here:

  • null = "measurement", the default. The respondent's \theta is fixed and identical at both occasions and only the response process is random, so SE_{diff}^2 = SE_1^2 + SE_2^2. This asks whether the two estimates differ by more than responding alone would produce.

  • null = "retest". Occasion-to-occasion fluctuation that is not change in the construct (state, mood, recall, practice) also counts as noise, so SE_{diff}^2 = SE_1^2 + SE_2^2 + 2\sigma^2_{retest}. The factor of 2 is there because retest_sd is a per-occasion SD and both occasions carry one.

The default is the narrower of the two, so it flags more change than a retest-based null would. It is the only one computable from two response vectors alone, which is why it is the default, but a significant result under it is a necessary condition for change rather than evidence of it. retest_sd_tip reports, for each flagged respondent, how large \sigma_{retest} would have to be to overturn their result.

Caronni et al. (2026) compute 1.96\sqrt{SE_A^2 + SE_B^2} from the Rasch score-to-measure map and call it a minimal detectable change. That is the null = "measurement" quantity, whereas the classical MDC is built from a test-retest ICC and is the null = "retest" quantity. The two are not interchangeable, which is why the null in force is named in every caption here.

Regression to the mean is present under both nulls and is addressed by neither. A regression-based reliable change index (Maassen, 2004) is out of scope for a single-subject function, since it needs population parameters the individual case does not supply.

Why 1.96 is the wrong cutoff. The sum score is a sufficient statistic, so \hat\theta takes only as many values as there are scores, and SE(\hat\theta) is a deterministic function of the score rather than a constant. The denominator of the RCI is therefore not independent of its numerator: a respondent who moves to an extreme score produces a large change and a large standard error together, and the ratio is damped. Both tails of the null are pulled in, so the critical value sits below 1.96, and the shortfall grows as the scale shortens. Enumerated values for one family of items with four response categories: 1.62 at four items, 1.70 at six, 1.79 at ten, 1.85 at twenty, 1.92 at forty. Response categories matter much less than item count here, and almost all of their effect is the step from dichotomous to polytomous: at six items the value moves from 1.55 with two categories to 1.72 with three and only 1.78 with seven, while reliability over the same range climbs from about .60 to about .91. Referring the RCI to a normal null is conservative, and materially so on short scales.

How the null is obtained. critical = "exact" enumerates it. Because the score is sufficient, \hat\theta(r) and SE(r) are deterministic lookups and P(r \mid \theta) follows from the Lord-Wingersky recursion, so the null is a discrete distribution over score pairs with no Monte Carlo error. Respondents are grouped by their pair of answered-item sets, so incomplete data is enumerated within each pattern. Under null = "retest" the occasion deviations are integrated out by quadrature. critical = "simulate" estimates the same distribution by drawing response vectors instead, which is slower and noisier but makes no use of sufficiency.

Quantiles are taken from the signed RCI, so crit_lower and crit_upper are reported separately. Under the null the two occasions are exchangeable, which makes the RCI symmetric about zero however skewed \hat\theta itself is, and the two bounds then agree in magnitude. They part company when exchangeability fails, most commonly when the occasions differ in which items were answered.

The pooled critical value is a property of the sample as well as the items, because it averages over where the respondents sit. Targeting matters more than the shape of the distribution: skewing the sample barely moves it, while pushing the same sample off target until 40 percent of respondents score at an extreme moved it from 1.70 to 1.52 in one check.

What the RCI measures, and what it does not. The RCI answers whether a change is distinguishable from the null. It is not a measure of how much someone changed, and the two orderings genuinely differ. Its value mixes two ingredients that it cannot separate: how far the respondent moved, and how precisely each of their two positions was pinned down. On a scale of six items with four response categories the standard error runs from 0.49 in the middle to 1.45 at the boundary, a factor of three within one instrument. A respondent going from the minimum to the maximum score moves 7.24 logits and scores RCI 3.53, while one going from 3 to 15 moves 3.11 logits and scores 3.59. The first traversed the whole scale, the second less than half of it, and the second has the larger statistic. Both statements are correct about their own question.

In a group comparison every case shares one standard error, so ordering by the test statistic matches ordering by effect. Here the scale factor changes from respondent to respondent, and the output is read one person at a time, which is where the misreading does damage. Read change for magnitude, in logits, and rci only for the decision. Do not rank respondents by rci, and do not compare RCIs across instruments or across regions of one scale as though they were a common currency.

Simulating fixes the reference distribution, not the estimand. A simulated critical value under null = "measurement" is a well calibrated answer to the measurement-error question, not to the retest question.

Calibration and its precondition. anchor = "stack" calibrates on both occasions row-bound together. It uses all the data and puts both occasions on one metric by construction, at the cost of assuming the items behave the same way on both occasions. When they do not, the compromise calibration pulls the occasions toward each other and shrinks apparent change. Test that precondition first with the package's DIF tools, using occasion as the grouping variable on the stacked data. anchor = "t1" measures follow-up on the baseline metric and keeps post-treatment data out of it, at the cost of halving the calibration sample. "t2" is the mirror.

Note that stacking puts each respondent in the data twice, so the item parameters' own standard errors are optimistic. That does not propagate into the person standard errors, which treat the thresholds as fixed.

retest_sd_tip. For a respondent whose change is flagged, this is the per-occasion retest SD that would bring their RCI back to the critical value, \sigma^2_{tip} = (\mathrm{change}^2/c^2 - SE_1^2 - SE_2^2)/2. It is NA for respondents not flagged, for whom the question does not arise. The critical value is held fixed at the one in force, which is exact when critical is numeric and an approximation when it is simulated, since a simulated critical value drifts toward \pm 1.96 as the added normal component smooths the discreteness.

Value

  • If output = "dataframe": one row per respondent, with columns id, sum_t1, sum_t2, theta_t1, se_t1, theta_t2, se_t2, extreme_t1, extreme_t2, change, se_diff, rci, p_value, crit_lower, crit_upper, change_class, and retest_sd_tip. Attributes null, retest_sd, anchor, alpha, direction, critical and sim_iter record the analysis.

  • If output = "kable": the same content as a knitr_kable.

  • If output = "ggplot": occasion 1 against occasion 2, with the no-change band and points coloured by change_class.

References

Caronni, A., et al. (2026). Improving single-subject change assessment: deriving the minimal detectable change of questionnaires' ordinal scores from the Rasch analysis measures.

Jacobson, N. S., & Truax, P. (1991). Clinical significance: A statistical approach to defining meaningful change in psychotherapy research. Journal of Consulting and Clinical Psychology, 59(1), 12-19. \Sexpr[results=rd]{tools:::Rd_expr_doi("10.1037/0022-006X.59.1.12")}

Maassen, G. H. (2004). The standard error in the Jacobson and Truax Reliable Change Index. Journal of Clinical and Experimental Neuropsychology, 26(5), 643-657. \Sexpr[results=rd]{tools:::Rd_expr_doi("10.1080/13803390409609791")}

Zumbo, B. D. (2026). Conditional standard error of measurement as an estimand of individual score precision. Psychometrika. \Sexpr[results=rd]{tools:::Rd_expr_doi("10.1017/psy.2026.10141")}

See Also

RMretestSD(), RMpersonParameters(), RMreliabilityCurve(), RMscoreSE()

Examples


set.seed(1)
thr <- lapply(seq(-1.2, 1.2, length.out = 6), function(b) b + c(-0.8, 0, 0.8))
theta <- rnorm(80, 0, 1.4)
t1 <- as.data.frame(easyRasch2:::sim_partial_score(thr, theta))
t2 <- as.data.frame(easyRasch2:::sim_partial_score(thr, theta + 0.8))
colnames(t1) <- colnames(t2) <- paste0("I", 1:6)

RMpersonChange(t1, t2)

# A single respondent needs an external calibration
names(thr) <- paste0("I", 1:6)
RMpersonChange(t1[1, ], t2[1, ], item_params = thr)

# Monte Carlo alternative, for comparison
RMpersonChange(t1, t2, critical = "simulate", sim_iter = 200,
               parallel = FALSE, seed = 1)


easyRasch2 documentation built on Sept. 13, 2026, 1:07 a.m.