| Title: | Minimal Important Change and Threshold Estimation |
|---|---|
| Description: | Provides methods for estimating minimal important change (MIC) and interpretation thresholds for multi-item questionnaires and single-item continuous or ordinal measures. Methods include predictive modelling, adjusted predictive modelling, improved adjusted predictive modelling using anchor reliability, confirmatory factor analysis for anchor reliability, longitudinal confirmatory factor analysis for MIC estimation, longitudinal confirmatory factor analysis-based MIC estimation for single-item measures, and confirmatory factor analysis-based threshold estimation for single-item and multi-item measures. Implemented methods include those developed by Terluin et al. (2015) <doi:10.1016/j.jclinepi.2015.03.015>, Terluin et al. (2017) <doi:10.1016/j.jclinepi.2016.12.015>, Terluin et al. (2022) <doi:10.1016/j.jclinepi.2022.04.018>, Terluin et al. (2023) <doi:10.1007/s11136-023-03355-8>, Terluin et al. (2024) <doi:10.1007/s11136-023-03577-w>, Terluin et al. (2024) <doi:10.1007/s11136-024-03763-4>, and Terluin et al. (2026) <doi:10.1007/s11136-025-04134-3>. |
| Authors: | Iris Eekhout [aut] (Original package author), Berend Terluin [aut], Yong-Hao Pua [aut, cre] |
| Maintainer: | Yong-Hao Pua <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 2.0.0 |
| Built: | 2026-10-09 10:13:49 UTC |
| Source: | https://github.com/yhpua/mict |
equalize_levels() prepares paired Time 1 and Time 2 PROM items for
longitudinal CFA by ensuring that each item has the same observed response
levels at both time points. Sparse or mismatched response categories are
collapsed consistently within each item pair.
equalize_levels( data, pair_by = c("position", "suffix"), t1_suffix = NULL, t2_suffix = NULL, min_resp = 5L, verbose = TRUE, print_tables = TRUE )equalize_levels( data, pair_by = c("position", "suffix"), t1_suffix = NULL, t2_suffix = NULL, min_resp = 5L, verbose = TRUE, print_tables = TRUE )
data |
A data frame containing paired item variables. Do not include the transition rating variable. |
pair_by |
Pairing method. |
t1_suffix |
Regex suffix identifying Time 1 items when
|
t2_suffix |
Regex suffix identifying Time 2 items when
|
min_resp |
Integer. Minimum number of responses required in each response category at both time points. Categories with fewer responses are collapsed. |
verbose |
Logical. If |
print_tables |
Logical. If |
Item pairs can be detected either by column position or by suffix patterns. The function does not shift or rescale item scores; it only collapses and equalizes response levels within paired items.
A list with components:
The processed data frame, ordered as T1 items followed by T2 items.
A data frame describing matched T1/T2 item pairs.
A data frame listing item pairs that required collapsing/equalization.
Frequency tables before collapsing for affected item pairs.
Frequency tables after collapsing for affected item pairs.
Category mappings for affected item pairs.
A data frame summarizing all item pairs.
dat <- data.frame( item1 = c(1, 1, 2, 2, 3, 3, 3, 4), item2 = c(1, 2, 2, 3, 3, 3, 4, 4), item1.1 = c(1, 2, 2, 2, 3, 4, 4, 4), item2.1 = c(1, 1, 2, 2, 3, 4, 4, 4) ) out <- equalize_levels( data = dat, pair_by = "suffix", t1_suffix = "", t2_suffix = "\\.1", min_resp = 2, verbose = FALSE, print_tables = FALSE )dat <- data.frame( item1 = c(1, 1, 2, 2, 3, 3, 3, 4), item2 = c(1, 2, 2, 3, 3, 3, 4, 4), item1.1 = c(1, 2, 2, 2, 3, 4, 4, 4), item2.1 = c(1, 1, 2, 2, 3, 4, 4, 4) ) out <- equalize_levels( data = dat, pair_by = "suffix", t1_suffix = "", t2_suffix = "\\.1", min_resp = 2, verbose = FALSE, print_tables = FALSE )
A simulated dataset containing responses to a 10-item patient-reported outcome measure at two time points for 1000 subjects, together with a binary transition rating anchor.
exampleexample
A data frame with 1000 rows and 21 variables:
Item 1 at Time 1.
Item 2 at Time 1.
Item 3 at Time 1.
Item 4 at Time 1.
Item 5 at Time 1.
Item 6 at Time 1.
Item 7 at Time 1.
Item 8 at Time 1.
Item 9 at Time 1.
Item 10 at Time 1.
Item 1 at Time 2.
Item 2 at Time 2.
Item 3 at Time 2.
Item 4 at Time 2.
Item 5 at Time 2.
Item 6 at Time 2.
Item 7 at Time 2.
Item 8 at Time 2.
Item 9 at Time 2.
Item 10 at Time 2.
Binary transition rating anchor, coded 0/1.
Each item has four ordered response categories scored 0, 1, 2, and 3. Therefore, the summed score at each time point ranges from 0 to 30.
The Time 1 items are named v1_1 to v1_10, and the Time 2 items are named
v2_1 to v2_10. The variable trat is a dichotomous transition rating
indicator coded 0/1.
This dataset is useful for demonstrating MIC estimation functions such as
mic_roc(), mic_pred(), mic_adjust(), mic_iapm(), tr_reliability(),
and mic_lcfa().
Simulated example data generated for demonstrating MIC package
functions. See data-raw/R/example.R.
simdat() for generating new simulated datasets with user-specified
simulation parameters.
mic_roc(), mic_pred(), mic_adjust(), and mic_iapm() for predictive
modeling and adjusted predictive modeling MIC estimation.
tr_reliability() for estimating transition rating reliability.
mic_lcfa() for LCFA-based MIC estimation.
data(example) nitems <- 10 example$score_t1 <- rowSums(example[, paste0("v1_", 1:nitems)]) example$score_t2 <- rowSums(example[, paste0("v2_", 1:nitems)]) example$change <- example$score_t2 - example$score_t1 head(example) mic_roc( data = example, x = "score_t1", y = "score_t2", tr = "trat", nboot = 0 )data(example) nitems <- 10 example$score_t1 <- rowSums(example[, paste0("v1_", 1:nitems)]) example$score_t2 <- rowSums(example[, paste0("v2_", 1:nitems)]) example$change <- example$score_t2 - example$score_t1 head(example) mic_roc( data = example, x = "score_t1", y = "score_t2", tr = "trat", nboot = 0 )
lcfa_model() generates lavaan model syntax for estimating anchor-based minimal important change (MIC)
using longitudinal confirmatory factor analysis.
lcfa_model( data, trt, pair_by = c("position", "suffix"), t1_suffix = NULL, t2_suffix = NULL, pair_map = NULL, factor_t1 = "F1", factor_t2 = "F2", loading_prefix = "a", threshold_prefix = "b", trt_loading_t1 = "f1", trt_loading_t2 = "f2", trt_threshold_label = "thr.trt", psb_label = "psb", mic_label = "b_param", correlated_errors = TRUE, threshold_invariance = TRUE, include_residual_variances_t2 = TRUE, include_factor_structure = TRUE, include_comments = TRUE, print_model = TRUE )lcfa_model( data, trt, pair_by = c("position", "suffix"), t1_suffix = NULL, t2_suffix = NULL, pair_map = NULL, factor_t1 = "F1", factor_t2 = "F2", loading_prefix = "a", threshold_prefix = "b", trt_loading_t1 = "f1", trt_loading_t2 = "f2", trt_threshold_label = "thr.trt", psb_label = "psb", mic_label = "b_param", correlated_errors = TRUE, threshold_invariance = TRUE, include_residual_variances_t2 = TRUE, include_factor_structure = TRUE, include_comments = TRUE, print_model = TRUE )
data |
A data frame containing paired Time 1 and Time 2 PROM items and a transition rating variable. |
trt |
Character. Name of the transition rating variable. |
pair_by |
Pairing method. |
t1_suffix |
regex suffix identifying Time 1 items when
|
t2_suffix |
regex suffix identifying Time 2 items when
|
pair_map |
Optional data frame describing item pairs, typically from
|
factor_t1 |
Character. Name of the Time 1 latent factor. |
factor_t2 |
Character. Name of the Time 2 latent factor. |
loading_prefix |
Character. Prefix for equality-constrained item loading labels. |
threshold_prefix |
Character. Prefix for equality-constrained item threshold labels. |
trt_loading_t1 |
Character. Label for the transition rating loading on the Time 1 factor. |
trt_loading_t2 |
Character. Label for the transition rating loading on the Time 2 factor. |
trt_threshold_label |
Character. Label for the transition rating threshold. |
psb_label |
Character. Name of the defined present-state-bias parameter. |
mic_label |
Character. Name of the defined MIC parameter on the latent theta scale. |
correlated_errors |
Logical. If |
threshold_invariance |
Logical. If |
include_residual_variances_t2 |
Logical. If |
include_factor_structure |
Logical. If |
include_comments |
Logical. If |
print_model |
Logical. If |
The generated model is intended for ordinal PROM items and a binary transition rating item. Item thresholds are constrained equal across Time 1 and Time 2 within each item pair. The number of thresholds is determined automatically from the observed response levels in the supplied data.
Item pairs can be detected either by column position or by suffix patterns,
using the same logic as equalize_levels().
The model defines:
psb := (f1/f2) + 1 b_param := thr.trt/f2
An object of class lcfa_model, invisibly. The generated lavaan
syntax can be accessed using $model.
set.seed(123) sim <- simdat(N = 100) dat <- sim$datw mod <- lcfa_model( data = dat[, c(sim$item_names$t1_items, sim$item_names$t2_items, "trat")], trt = "trat", pair_by = "suffix", t1_suffix = "", t2_suffix = "\\.1", print_model = FALSE ) mod$modelset.seed(123) sim <- simdat(N = 100) dat <- sim$datw mod <- lcfa_model( data = dat[, c(sim$item_names$t1_items, sim$item_names$t2_items, "trat")], trt = "trat", pair_by = "suffix", t1_suffix = "", t2_suffix = "\\.1", print_model = FALSE ) mod$model
Minimal Important Change (MIC) obtained as predicted value from a logistic regression model.
mic_adjust(data = NULL, x, y, tr, reliability)mic_adjust(data = NULL, x, y, tr, reliability)
data |
data.frame that holds the scores at two time points and the transition rate in separate columns. |
x |
vector with score at time point 1, or column name in the data if
|
y |
vector with score at time point 1, or column name in the data if
|
tr |
vector with transition rates (perceived change), or column name in
the data if |
reliability |
the reliability for the transition score. Can be computed
with the |
mic_adjust() is a focused function for estimating the adjusted
predictive modeling-based MIC. For an all-in-one workflow that returns the
predictive MIC, adjusted predictive MIC, and improved adjusted predictive MIC
with optional bootstrap confidence intervals, see mic_iapm().
vector with the adjusted MIC value
data(example) nitems <- 10 example$x <- rowSums(example[,1:nitems]) # sumscore T1 example$y <- rowSums(example[,(nitems+1):(2*nitems)]) # sumscore T2 mic_adjust(x = example$x, y = example$y, tr = example$trat, reliability = 0.5) mic_adjust(data = example, x = "x", y = "y", tr = "trat", reliability = 0.5) # For predictive, adjusted, and improved adjusted MICs in one workflow, # see mic_iapm().data(example) nitems <- 10 example$x <- rowSums(example[,1:nitems]) # sumscore T1 example$y <- rowSums(example[,(nitems+1):(2*nitems)]) # sumscore T2 mic_adjust(x = example$x, y = example$y, tr = example$trat, reliability = 0.5) mic_adjust(data = example, x = "x", y = "y", tr = "trat", reliability = 0.5) # For predictive, adjusted, and improved adjusted MICs in one workflow, # see mic_iapm().
Estimates (i) predictive modeling-based, (ii) adjusted predictive
modeling-based, and (iii) improved adjusted predictive modeling-based
minimal important change (MIC) estimates, with optional bootstrap confidence
intervals. mic_iapm() can also be used to estimate the interpretation
threshold of a predictor.
mic_iapm( mypred, anchor, mydata, anchor_reliability = NULL, nboot = 0, report_every = 100, verbose = FALSE, max_attempts = nboot * 5 )mic_iapm( mypred, anchor, mydata, anchor_reliability = NULL, nboot = 0, report_every = 100, verbose = FALSE, max_attempts = nboot * 5 )
mypred |
Character string; name of the column containing the change score or predictor score. |
anchor |
Character string; name of the column containing the binary anchor. The anchor must be binary and coded as 0/1 or TRUE/FALSE. |
mydata |
Data frame with the change score or predictor score and the anchor in separate columns. |
anchor_reliability |
Optional anchor reliability. Can be either a single
numeric value between 0 and 1, or an object returned by |
nboot |
Integer; number of bootstrap samples for estimating 95%
confidence intervals. Bootstrapping is performed only when |
report_every |
Integer. The interval at which bootstrap progress should be printed. |
verbose |
Logical. If |
max_attempts |
Integer; maximum number of bootstrap attempts. This avoids an infinite loop when many bootstrap samples fail. |
For reproducible bootstrap confidence intervals, call set.seed() before
calling mic_iapm().
A mic_iapm object containing:
Predictive modeling-based MIC.
Adjusted predictive modeling-based MIC.
Improved adjusted predictive modeling-based MIC, returned
only when anchor_reliability is supplied.
Bootstrap confidence interval for mic_pm, if requested.
Bootstrap confidence interval for mic_apm, if requested.
Bootstrap confidence interval for mic_iapm, if
requested and anchor_reliability is supplied.
Matrix of available MIC estimates and confidence intervals.
Anchor reliability used in the improved adjusted predictive modeling calculation.
Requested number of bootstrap samples.
Number of successful bootstrap samples.
Terluin B, Eekhout I, Terwee CB, de Vet HCW. Minimal important change (MIC) based on a predictive modeling approach was more precise than MIC based on receiver operating characteristic analysis. J Clin Epidemiol. 2015;68(12):1388-1396. doi:10.1016/j.jclinepi.2015.03.015
Terluin B, Eekhout I, Terwee CB. The anchor-based minimal important change, based on receiver operating characteristic analysis or predictive modeling, may need to be adjusted for the proportion of improved patients. J Clin Epidemiol. 2017;83:90-100. doi:10.1016/j.jclinepi.2016.12.015
Terluin B, Eekhout I, Terwee CB. Improved adjusted minimal important change took reliability of transition ratings into account. J Clin Epidemiol. 2022;148:48-53. doi:10.1016/j.jclinepi.2022.04.018
set.seed(123) sim <- simdat(N = 200, add_change = TRUE) dat <- sim$datw mic_iapm( mypred = "change", anchor = "trat", mydata = dat, anchor_reliability = sim$truth$observed_rel_trt, nboot = 200 )set.seed(123) sim <- simdat(N = 200, add_change = TRUE) dat <- sim$datw mic_iapm( mypred = "change", anchor = "trat", mydata = dat, anchor_reliability = sim$truth$observed_rel_trt, nboot = 200 )
mic_lcfa() estimates present-state bias and anchor-based minimal important
change (MIC) using a longitudinal confirmatory factor analysis (LCFA) model.
mic_lcfa( mydat, trt = NULL, model = NULL, trt_cut = 1, auto_equalize = TRUE, pair_by = c("position", "suffix"), t1_suffix = NULL, t2_suffix = NULL, min_resp = 5L, B = 0L, report_every = 100L, N_ets = 5000L, score_method = c("irt", "cfa", "both"), return_prepared = FALSE, print_model = TRUE, verbose = TRUE )mic_lcfa( mydat, trt = NULL, model = NULL, trt_cut = 1, auto_equalize = TRUE, pair_by = c("position", "suffix"), t1_suffix = NULL, t2_suffix = NULL, min_resp = 5L, B = 0L, report_every = 100L, N_ets = 5000L, score_method = c("irt", "cfa", "both"), return_prepared = FALSE, print_model = TRUE, verbose = TRUE )
mydat |
Data frame containing paired Time 1 and Time 2 PROM items and one transition rating variable. |
trt |
Character. Name of the transition rating variable. If |
model |
Optional lavaan model syntax. If |
trt_cut |
Numeric. Cutpoint used to dichotomize the transition rating
variable when it has more than two observed values. Values greater than or
equal to |
auto_equalize |
Logical. If |
pair_by |
Character. Pairing method passed to |
t1_suffix |
Regex suffix identifying Time 1 items when
|
t2_suffix |
Regex suffix identifying Time 2 items when
|
min_resp |
Integer. Minimum number of responses required in each response category before collapsing. |
B |
Integer. Number of bootstrap samples. Bootstrap confidence
intervals are computed only if |
report_every |
Integer. Progress message interval during bootstrapping. |
N_ets |
Integer. Number of simulated theta values used for the IRT expected test score calculation. This argument is not used by the closed-form CFA method. |
score_method |
Character. Method used to map the latent MIC to the
observed PROM score metric. Options are |
return_prepared |
Logical. If |
print_model |
Logical. If |
verbose |
Logical. If |
The input data should contain paired Time 1 and Time 2 PROM items plus one
transition rating variable. Item pairs may be identified by column position
or by suffixes. By default, paired item levels are first equalized using
equalize_levels(), and lavaan syntax is generated using lcfa_model().
The LCFA model estimates the MIC on the latent-change scale. This latent MIC is then mapped onto the observed PROM summed-score metric using an expected score function.
The LCFA model uses both Time 1 and Time 2 item responses, together with the transition rating, to estimate the latent MIC and present-state bias. The latent MIC is then mapped onto the observed PROM summed-score metric.
at the baseline latent trait distribution, theta ~ N(0, 1);
at the shifted latent trait distribution,
theta ~ N(MIC.theta, 1).
Two expected-score mappings are available through score_method:
"irt" uses an IRT-based expected test score method via
mirt::expected.test(). This follows the published implementation of
Terluin et al. (2024).
"cfa" uses a closed-form CFA probit expected-score method based on
lavaan-estimated item loadings and thresholds. For each item, the method
computes marginal category probabilities under theta ~ N(mu, 1), first
with mu = 0 and then with mu = MIC.theta. These probabilities are used
to obtain expected item scores, which are summed across items to obtain the
expected PROM score.
"both" computes expected scores using both irt and cfa methods
Under the CFA probit method, the closed-form marginal cumulative probability
for item threshold tau_k is based on the latent response model
Y* = lambda * theta + error. If theta ~ N(mu, 1) and
error ~ N(0, 1), then Y* ~ N(lambda * mu, lambda^2 + 1). Therefore,
marginal category probabilities can be computed without simulating theta
values or fitting an additional IRT model.
A mic_lcfa object with elements including:
psb: estimated present-state bias;
MIC.theta: MIC on the latent-change scale;
MIC.ets: MIC on the observed PROM summed-score metric;
MIC.ets.irt: IRT-based expected-score MIC, if requested;
MIC.ets.cfa: CFA-based expected-score MIC, if requested;
MIC_CI: bootstrap confidence interval, if requested;
nboot: number of successful bootstrap estimates.
If return_prepared = TRUE, the returned object also contains the prepared
data, generated lavaan model, fitted LCFA object, and bootstrap values.
Terluin B, Trigg A, Fromy P, Schuller W, Terwee CB, Bjorner JB. Estimating anchor-based minimal important change using longitudinal confirmatory factor analysis. Qual Life Res. 2024;33:963-973. doi:10.1007/s11136-023-03577-w
Terluin B, Fromy P, Trigg A, Terwee CB, Bjorner JB. Effect of present state bias on minimal important change estimates: a simulation study. Quality of Life Research. 2024. doi:10.1007/s11136-024-03763-4
Terluin B, Griffiths P, Trigg A, Terwee CB, Bjorner JB. Present state bias in transition ratings was accurately estimated in simulated and real data. J Clin Epidemiol. 2022;143:128-136. doi:10.1016/j.jclinepi.2021.12.024
lcfa_model(), equalize_levels(), simdat()
set.seed(123) sim <- simdat(N = 500, add_change = TRUE) dat <- sim$datw mydat <- dat[, c( sim$item_names$t1_items, sim$item_names$t2_items, "trat" )] out_both <- mic_lcfa( mydat = mydat, trt = "trat", trt_cut = 1, auto_equalize = TRUE, pair_by = "suffix", t1_suffix = "", t2_suffix = "\\.1", min_resp = 5, score_method = "both", B = 0, print_model = FALSE, verbose = FALSE ) out_both$MIC.etsset.seed(123) sim <- simdat(N = 500, add_change = TRUE) dat <- sim$datw mydat <- dat[, c( sim$item_names$t1_items, sim$item_names$t2_items, "trat" )] out_both <- mic_lcfa( mydat = mydat, trt = "trat", trt_cut = 1, auto_equalize = TRUE, pair_by = "suffix", t1_suffix = "", t2_suffix = "\\.1", min_resp = 5, score_method = "both", B = 0, print_model = FALSE, verbose = FALSE ) out_both$MIC.ets
Minimal Important Change (MIC) obtained as predicted value from a logistic regression model.
mic_pred(data = NULL, x, y, tr)mic_pred(data = NULL, x, y, tr)
data |
data.frame that holds the scores at two time points and the transition rate in separate columns. |
x |
vector with score at time point 1, or column name in the data if
|
y |
vector with score at time point 1, or column name in the data if
|
tr |
vector with transition rates (perceived change), or column name in
the data if |
mic_pred() is a focused function for estimating the
predictive modeling-based MIC. For an all-in-one workflow that returns the
predictive MIC, adjusted predictive MIC, and improved adjusted predictive MIC
with optional bootstrap confidence intervals, see mic_iapm().
vector with the predicted MIC value
data(example) nitems <- 10 example$x <- rowSums(example[,1:nitems]) # sumscore T1 example$y <- rowSums(example[,(nitems+1):(2*nitems)]) # sumscore T2 mic_pred(x = example$x, y = example$y, tr = example$trat) mic_pred(data = example, x = example$x, y = example$y, tr = example$trat) mic_pred(data = example, x = "x", y = "y", tr = "trat") # For predictive, adjusted, and improved adjusted MICs in one workflow, # see mic_iapm().data(example) nitems <- 10 example$x <- rowSums(example[,1:nitems]) # sumscore T1 example$y <- rowSums(example[,(nitems+1):(2*nitems)]) # sumscore T2 mic_pred(x = example$x, y = example$y, tr = example$trat) mic_pred(data = example, x = example$x, y = example$y, tr = example$trat) mic_pred(data = example, x = "x", y = "y", tr = "trat") # For predictive, adjusted, and improved adjusted MICs in one workflow, # see mic_iapm().
Estimates the minimal important change (MIC) using receiver operating characteristic (ROC) analysis. The MIC is estimated as the change-score threshold that optimizes the Youden criterion.
mic_roc( data = NULL, x, y, tr, nboot = 0, report_every = 100, verbose = FALSE, max_attempts = nboot * 5 )mic_roc( data = NULL, x, y, tr, nboot = 0, report_every = 100, verbose = FALSE, max_attempts = nboot * 5 )
data |
Optional data frame containing the Time 1 score, Time 2 score, and binary anchor. |
x |
Numeric vector of scores at Time 1, or character name of the Time 1
score column in |
y |
Numeric vector of scores at Time 2, or character name of the Time 2
score column in |
tr |
Binary anchor vector, or character name of the anchor column in
|
nboot |
Integer. Number of bootstrap samples for estimating the 95%
confidence interval. Bootstrapping is performed only when |
report_every |
Integer. The interval at which bootstrap progress should be printed. |
verbose |
Logical. If |
max_attempts |
Integer. Maximum number of bootstrap attempts. This avoids an infinite loop when many bootstrap samples fail. |
Optional bootstrap confidence intervals can be requested by setting
nboot >= 100.
For reproducible bootstrap confidence intervals, call set.seed() before
calling mic_roc().
A mic_roc object containing the ROC-based MIC and, optionally,
bootstrap confidence intervals.
data(example) nitems <- 10 example$score_t1 <- rowSums(example[, paste0("v1_", seq_len(nitems))]) example$score_t2 <- rowSums(example[, paste0("v2_", seq_len(nitems))]) mic_roc( data = example, x = "score_t1", y = "score_t2", tr = "trat", nboot = 0 ) set.seed(123) mic_roc( data = example, x = "score_t1", y = "score_t2", tr = "trat", nboot = 500 )data(example) nitems <- 10 example$score_t1 <- rowSums(example[, paste0("v1_", seq_len(nitems))]) example$score_t2 <- rowSums(example[, paste0("v2_", seq_len(nitems))]) mic_roc( data = example, x = "score_t1", y = "score_t2", tr = "trat", nboot = 0 ) set.seed(123) mic_roc( data = example, x = "score_t1", y = "score_t2", tr = "trat", nboot = 500 )
mim_threshold() estimates an anchor-based interpretation threshold for
a multi-item measure (MIM) using a one-factor CFA model with an anchor/transition
rating item.
mim_threshold( mydata, var_formula, item_discretize = FALSE, item_levels = 10L, require_all_item_levels = FALSE, anchor_cut = 1, B = 0L, report_every = 50L, factor_name = "F1", item_suffix = "_ord", anchor_suffix = "_bin", std.lv = TRUE, parameterization = "theta", verbose = TRUE, ... )mim_threshold( mydata, var_formula, item_discretize = FALSE, item_levels = 10L, require_all_item_levels = FALSE, anchor_cut = 1, B = 0L, report_every = 50L, factor_name = "F1", item_suffix = "_ord", anchor_suffix = "_bin", std.lv = TRUE, parameterization = "theta", verbose = TRUE, ... )
mydata |
Data frame. |
var_formula |
Formula of the form |
item_discretize |
Logical. If |
item_levels |
Number of levels to use when |
require_all_item_levels |
Logical. Passed to |
anchor_cut |
Cutpoint for binarizing the anchor if it has more than
two unique non-missing values. Values >= |
B |
Number of bootstrap samples. Bootstrap CI is computed only if
|
report_every |
During bootstrapping, print progress every
|
factor_name |
Name of the latent factor. |
item_suffix |
Suffix appended to item names when items are discretized or internally recoded. |
anchor_suffix |
Suffix appended to the anchor variable name if the anchor has more than two unique values and is dichotomized. |
std.lv |
Passed to |
parameterization |
Passed to |
verbose |
If |
... |
Additional arguments passed to |
This technique is based on Terluin et al (2023) and Terluin et al (2024).
Briefly, the mim_threshold() function:
fits a one-factor CFA model with the MIM items and an anchor item,
computes theta_star, the latent factor value where the anchor,
threshold occurs,
maps theta_star to each item using CFA-implied category
probabilities,
multiplies item category probabilities by item values or bin midpoints, and
sums the item-level expected values to obtain the MIM threshold.
A mim_threshold object. Additional details can be retrieved with
mim_threshold_details().
Terluin, B., Koopman, J.E., Hoogendam, L. et al. Estimating meaningful thresholds for multi-item questionnaires using item response theory. Qual Life Res 32, 1819–1830 (2023)
Terluin, B., Trigg, A., Fromy, P. et al. Estimating anchor-based minimal important change using longitudinal confirmatory factor analysis. Qual Life Res 33, 963–973 (2024).
set.seed(123) sim <- simdat(N = 500) dat <- sim$datw t1_items <- sim$item_names$t1_items dat_t1 <- dat[, c(t1_items, "trat")] out <- mim_threshold( mydata = dat_t1, var_formula = trat ~ ., B = 0 ) outset.seed(123) sim <- simdat(N = 500) dat <- sim$datw t1_items <- sim$item_names$t1_items dat_t1 <- dat[, c(t1_items, "trat")] out <- mim_threshold( mydata = dat_t1, var_formula = trat ~ ., B = 0 ) out
Extract hidden details from a mim_threshold object
mim_threshold_details(x)mim_threshold_details(x)
x |
A |
A list containing hidden details.
sim_mic_lcfa() estimates the minimal important change (MIC) for a
single-item measure (SIM) using longitudinal confirmatory factor analysis (LCFA)
with an auxiliary variable. The SIM is measured
at Time 1 and Time 2, an auxiliary variable is measured at Time 1 and Time 2,
and a transition rating is included as an anchor.
sim_mic_lcfa( mydata, sim, aux, trt, trt_cut = 1, sim_levels = 10L, sim_discretize = c("auto", "yes", "no"), min_resp = 1L, aux_ordered = FALSE, B = 0L, report_every = 50L, add_lmodel = NULL, print_model = FALSE, verbose = FALSE, ... )sim_mic_lcfa( mydata, sim, aux, trt, trt_cut = 1, sim_levels = 10L, sim_discretize = c("auto", "yes", "no"), min_resp = 1L, aux_ordered = FALSE, B = 0L, report_every = 50L, add_lmodel = NULL, print_model = FALSE, verbose = FALSE, ... )
mydata |
Data frame. |
sim |
Character vector of length 2. Names of the SIM variable at Time 1 and Time 2. |
aux |
Character vector of length 2. Names of the auxiliary variable at Time 1 and Time 2. |
trt |
Character. Name of the transition rating / anchor variable. |
trt_cut |
Numeric. Cutpoint used to binarize |
sim_levels |
Integer. Number of ordered levels used when discretizing
the SIM with |
sim_discretize |
Character. One of |
min_resp |
Integer. Minimum response count per category used by
|
aux_ordered |
Logical. If |
B |
Integer. Number of nonparametric bootstrap samples. Bootstrap
confidence intervals are computed only when |
report_every |
Integer. Print bootstrap progress every |
add_lmodel |
Optional lavaan syntax appended to the generated model. |
print_model |
Logical. If |
verbose |
Logical. If |
... |
Additional arguments passed to |
If the pooled SIM values across Time 1 and Time 2 have 12 or fewer observed
levels, the original SIM values are used as ordered categories. If the pooled
SIM values have more than 12 observed levels, the SIM is discretized using
var_discretize() with common equal-width bins across Time 1 and Time 2.
In both cases, SIM response levels are equalized across time before fitting
the LCFA model.
The SIM loading and SIM thresholds are constrained equal over time so that the Time 1 and Time 2 latent factors are on the same measurement scale.
The latent MIC is estimated as MIC.theta = tau_trt / f2, where tau_trt is
the transition-rating threshold and f2 is the transition-rating loading on
the Time 2 factor.
The SIM-scale MIC is first calculated on the transformed SIM scale as
sqrt(var(SIM_T1_transformed) * Rel_SIM_T1) * MIC.theta, where
Rel_SIM_T1 is the model R-squared of the Time 1 SIM. If var_discretize()
is used, the transformed-scale MIC is then back-transformed to the original
SIM metric using the equal-width bin width.
Continuous SIMs with many distinct values are not used directly as ordered
indicators. When discretization is needed, sim_mic_lcfa() applies
var_discretize() to the pooled Time 1 and Time 2 SIM values. This ensures
that the same equal-width binning rule is applied at both time points.
The LCFA model is fitted to the discretized/equalized ordered SIM variables.
Therefore, the reliability of the SIM, Rel_SIM_T1, is the reliability of
the transformed SIM used in the CFA model. For this reason, the SIM MIC is
first computed on the transformed SIM scale:
MIC_SIM_transformed = sqrt(var(SIM_T1_transformed) * Rel_SIM_T1) * MIC.theta.
If discretization was used, this transformed-scale MIC is then converted back to the original SIM metric using:
MIC_SIM_original = MIC_SIM_transformed * backtransform_factor,
where backtransform_factor is the equal-width bin width, calculated as the
pooled original SIM range divided by the number of requested discretized
levels. If no discretization is used, backtransform_factor = 1.
A sim_mic_lcfa object with elements including:
MIC.theta: MIC on the latent-change scale;
MIC.sim.transformed: MIC on the transformed SIM scale;
MIC.sim: MIC on the original SIM metric;
rel_SIM1: model R-squared / reliability of SIM at Time 1;
rel_trt: model R-squared / reliability of the transition rating;
psb: estimated present-state bias;
ci: bootstrap confidence intervals, if requested;
sim_prepared: information about discretization, equalization, and
back-transformation.
Terluin B, Pua YH, Fromy P, Trigg A, van der Zwaard B, Bjorner JB. Estimating the minimal important change of single-item measures using the adjusted predictive modeling method or the longitudinal confirmatory factor analysis method. Quality of Life Research. 2026. doi:10.1007/s11136-025-04134-3
Terluin B, Trigg A, Fromy P, Schuller W, Terwee CB, Bjorner JB. Estimating anchor-based minimal important change using longitudinal confirmatory factor analysis. Qual Life Res. 2024;33:963-973. doi:10.1007/s11136-023-03577-w
var_discretize(), equalize_levels(), mic_lcfa(), simdat()
set.seed(123) sim <- simdat(N = 500, add_change = TRUE) dat <- sim$datw # Create a toy single-item measure from several items dat$sim_t1 <- rowSums(dat[, paste0("item", 1:8), drop = FALSE]) dat$sim_t2 <- rowSums(dat[, paste0("item", 1:8, ".1"), drop = FALSE]) out <- sim_mic_lcfa( mydata = dat, sim = c("sim_t1", "sim_t2"), aux = c("item9", "item9.1"), trt = "trat", sim_discretize = "auto", sim_levels = 10, aux_ordered = TRUE, B = 0, print_model = TRUE ) out # Inspect how the SIM was prepared out$sim_prepared$used_discretization out$sim_prepared$backtransform_factor out$sim_prepared$original_levelsset.seed(123) sim <- simdat(N = 500, add_change = TRUE) dat <- sim$datw # Create a toy single-item measure from several items dat$sim_t1 <- rowSums(dat[, paste0("item", 1:8), drop = FALSE]) dat$sim_t2 <- rowSums(dat[, paste0("item", 1:8, ".1"), drop = FALSE]) out <- sim_mic_lcfa( mydata = dat, sim = c("sim_t1", "sim_t2"), aux = c("item9", "item9.1"), trt = "trat", sim_discretize = "auto", sim_levels = 10, aux_ordered = TRUE, B = 0, print_model = TRUE ) out # Inspect how the SIM was prepared out$sim_prepared$used_discretization out$sim_prepared$backtransform_factor out$sim_prepared$original_levels
sim_threshold() estimates an interpretation threshold for a continuous or ordinal
single-item measure using a confirmatory factor analysis (CFA) approach developed by Terluin et al (2026)
sim_threshold( mydata, var_formula, sim_levels = 10L, ordered = NULL, add_lmodel = NULL, tr_cut = 1, B = 0L, report_every = 50L, require_all_sim_levels = FALSE, factor_name = "F1", sim_suffix = "_ord", std.lv = TRUE, parameterization = "theta", verbose = FALSE, ... )sim_threshold( mydata, var_formula, sim_levels = 10L, ordered = NULL, add_lmodel = NULL, tr_cut = 1, B = 0L, report_every = 50L, require_all_sim_levels = FALSE, factor_name = "F1", sim_suffix = "_ord", std.lv = TRUE, parameterization = "theta", verbose = FALSE, ... )
mydata |
Data frame. |
var_formula |
Formula of the form |
sim_levels |
Number of equal-width levels for discretizing the SIM. Must be between 2 and 12. |
ordered |
Additional auxiliary variables to treat as ordered in lavaan. |
add_lmodel |
Optional lavaan syntax appended to the generated model. |
tr_cut |
Cutpoint for binarizing the transition variable when it has
more than two unique non-missing values. Values >= |
B |
Number of bootstrap samples. Bootstrap CI is computed only if
|
report_every |
During bootstrapping, print progress every
|
require_all_sim_levels |
Passed to |
factor_name |
Name of the latent factor. |
sim_suffix |
Suffix appended to the SIM variable name after discretization. |
std.lv |
Passed to |
parameterization |
Passed to |
verbose |
If |
... |
Additional arguments passed to |
The sim_threshold() function:
discretizes a continuous SIM into equal-width ordered categories;
fits a one-factor CFA model with an anchor transition item;
computes theta_star, the latent factor value where the anchor
threshold occurs;
maps theta_star back to the original SIM scale using CFA-implied
category probabilities and bin midpoints.
A sim_threshold object. The printed output is compact.
Additional details can be retrieved with sim_threshold_details().
Terluin B, Pua YH, Fromy P, Trigg A, van der Zwaard B, Bjorner JB. Estimating the minimal important change of single-item measures using the adjusted predictive modeling method or the longitudinal confirmatory factor analysis method. Quality of Life Research. 2026. doi:10.1007/s11136-025-04134-3
set.seed(123) sim <- simdat(N = 500) dat <- sim$datw t1_items <- sim$item_names$t1_items dat_t1 <- dat[, c(t1_items, "trat")] dat_sim <- dat_t1 dat_sim$sim8 <- rowSums(dat_sim[, t1_items[1:8], drop = FALSE]) dat_sim$aux9 <- dat_sim[[t1_items[9]]] dat_sim$aux10 <- dat_sim[[t1_items[10]]] dat_sim <- dat_sim[, c("sim8", "aux9", "aux10", "trat")] out <- sim_threshold( mydata = dat_sim, var_formula = trat ~ sim8 + aux9 + aux10, sim_levels = 10, ordered = c("aux9", "aux10"), B = 0 ) outset.seed(123) sim <- simdat(N = 500) dat <- sim$datw t1_items <- sim$item_names$t1_items dat_t1 <- dat[, c(t1_items, "trat")] dat_sim <- dat_t1 dat_sim$sim8 <- rowSums(dat_sim[, t1_items[1:8], drop = FALSE]) dat_sim$aux9 <- dat_sim[[t1_items[9]]] dat_sim$aux10 <- dat_sim[[t1_items[10]]] dat_sim <- dat_sim[, c("sim8", "aux9", "aux10", "trat")] out <- sim_threshold( mydata = dat_sim, var_formula = trat ~ sim8 + aux9 + aux10, sim_levels = 10, ordered = c("aux9", "aux10"), B = 0 ) out
sim_threshold_details() extracts additional details from an object returned
by sim_threshold(), including the fitted lavaan model, CFA data,
latent anchor location, model-implied category probabilities, and bootstrap
results.
sim_threshold_details(x)sim_threshold_details(x)
x |
A |
A list of additional model details.
simdat() simulates Time 1 and Time 2 item responses for a 10-item
patient-reported outcome measure (PROM) and a binary anchor / transition
rating from an item response theory context. Each item has four ordered
response categories scored 0, 1, 2, and 3, so the total PROM score ranges
from 0 to 30 at each time point.
simdat( N = 2000, mn_imic = 0.37425, sd_imic = 0.05, cor_t1_change = -0.5, mean_tetch = 0.3, sd_tetch = 1, rel_trt = 0.7, return_latent = TRUE, add_change = FALSE )simdat( N = 2000, mn_imic = 0.37425, sd_imic = 0.05, cor_t1_change = -0.5, mean_tetch = 0.3, sd_tetch = 1, rel_trt = 0.7, return_latent = TRUE, add_change = FALSE )
N |
Integer. Sample size for simulation. |
mn_imic |
Numeric. Mean individual minimal important change (MIC) on
the latent theta-change scale. For context, |
sd_imic |
Numeric. Standard deviation of individual MICs on the latent theta-change scale. |
cor_t1_change |
Numeric. Correlation between baseline theta and latent change. |
mean_tetch |
Numeric. Mean latent change. |
sd_tetch |
Numeric. Standard deviation of latent change. |
rel_trt |
Numeric. Target reliability of perceived change used to generate the binary anchor / transition rating. |
return_latent |
Logical. If |
add_change |
Logical. If |
The returned data frame includes item-level responses, the binary anchor
trat, the Time 1 summed PROM score score_t1, and the Time 2 summed PROM
score score_t2. If add_change = TRUE, the observed change score
change = score_t2 - score_t1 is also added.
For reproducible simulations, call set.seed() before calling simdat().
The R code is adapted from supplementary materials of Terluin et al. Qual Life Res. 2024;33:963-973.
A list containing:
Simulation settings.
Names of Time 1 items, Time 2 items, and anchor.
Truth / diagnostic quantities, including target_rel_trt
and observed_rel_trt.
The simulated wide-format data frame.
If return_latent = TRUE, the output also includes item parameters,
latent variables, perceived change, and individual MICs.
set.seed(123) sim <- simdat(N = 200, add_change = TRUE) names(sim$datw) sim$truthset.seed(123) sim <- simdat(N = 200, add_change = TRUE) names(sim$datw) sim$truth
tr_reliability() estimates the reliability of an anchor or transition
rating item using confirmatory factor analysis. The reliability estimate is
the R-squared value of the anchor item from the fitted CFA model.
tr_reliability( data, model = NULL, anchor = NULL, xsec = FALSE, pair_by = c("position", "suffix"), t1_suffix = NULL, t2_suffix = NULL, factor_names = NULL, item_type = NULL, continuous_items = NULL, modification = TRUE, mi_cut = 0.3, complete_cases = TRUE, print_model = TRUE, verbose = TRUE, ... )tr_reliability( data, model = NULL, anchor = NULL, xsec = FALSE, pair_by = c("position", "suffix"), t1_suffix = NULL, t2_suffix = NULL, factor_names = NULL, item_type = NULL, continuous_items = NULL, modification = TRUE, mi_cut = 0.3, complete_cases = TRUE, print_model = TRUE, verbose = TRUE, ... )
data |
A data frame containing items and an anchor variable. |
model |
Optional lavaan model syntax. If |
anchor |
Character. Name of the anchor variable. If |
xsec |
Logical. If |
pair_by |
Pairing method for longitudinal data. |
t1_suffix |
Regex suffix identifying Time 1 items when
|
t2_suffix |
Regex suffix identifying Time 2 items when
|
factor_names |
Optional character vector of factor name(s). If |
item_type |
Optional character. Type of items used as indicators.
If |
continuous_items |
Optional character vector of item names to treat as
continuous. If supplied and |
modification |
Logical. If |
mi_cut |
Numeric. Cutoff for standardized latent-variable modification indices. |
complete_cases |
Logical. If |
print_model |
Logical. If |
verbose |
Logical. If |
... |
Additional arguments passed to |
This function follows the CFA approach for estimating transition-rating reliability described by Griffiths et al. (2022). For longitudinal data, Time 1 items load on the first factor, Time 2 items load on the second factor, and the anchor loads on both factors. Residuals of corresponding items across time-points are allowed to correlate. No constraints are placed on loadings or thresholds.
For cross-sectional data, a one-factor CFA model is used, with the anchor item included as an indicator of the latent factor.
An object of class tr_reliability.
Griffiths P, Terluin B, Trigg A, Schuller W, Bjorner JB. A confirmatory factor analysis approach was found to accurately estimate the reliability of transition ratings. J Clin Epidemiol. 2022;141:36-45. doi:10.1016/j.jclinepi.2021.08.029
set.seed(123) sim <- simdat(N = 300) dat <- sim$datw rel <- tr_reliability( data = dat[, c(sim$item_names$t1_items, sim$item_names$t2_items, "trat")], anchor = "trat", xsec = FALSE, pair_by = "suffix", t1_suffix = "", t2_suffix = "\\.1", item_type = "ordinal", modification = FALSE, print_model = FALSE ) relset.seed(123) sim <- simdat(N = 300) dat <- sim$datw rel <- tr_reliability( data = dat[, c(sim$item_names$t1_items, sim$item_names$t2_items, "trat")], anchor = "trat", xsec = FALSE, pair_by = "suffix", t1_suffix = "", t2_suffix = "\\.1", item_type = "ordinal", modification = FALSE, print_model = FALSE ) rel
tr_reliability_model() generates lavaan syntax for estimating the
reliability of an anchor or transition rating item using confirmatory factor
analysis.
tr_reliability_model( data, anchor = NULL, xsec = FALSE, pair_by = c("position", "suffix"), t1_suffix = NULL, t2_suffix = NULL, factor_names = NULL, print_model = TRUE )tr_reliability_model( data, anchor = NULL, xsec = FALSE, pair_by = c("position", "suffix"), t1_suffix = NULL, t2_suffix = NULL, factor_names = NULL, print_model = TRUE )
data |
A data frame containing items and an anchor variable. |
anchor |
Character. Name of the anchor variable. If |
xsec |
Logical. If |
pair_by |
Pairing method for longitudinal data. |
t1_suffix |
Regex suffix identifying Time 1 items when
|
t2_suffix |
Regex suffix identifying Time 2 items when
|
factor_names |
Optional character vector of factor name(s). If |
print_model |
Logical. If |
The generated model follows the approach described by Griffiths et al. J Clin Epidemiol. 2022;141:36-45. No constraints are placed on loadings or thresholds. For longitudinal data, residuals of corresponding items across time-points are allowed to correlate to account for item non-independence.
For cross-sectional data, a one-factor model is generated. For longitudinal data, a two-factor model is generated, with the anchor loading on both Time 1 and Time 2 factors.
An object of class tr_reliability_model. The lavaan syntax can be
accessed using $model.
set.seed(123) sim <- simdat(N = 200) dat <- sim$datw tr_reliability_model( data = dat[, c(sim$item_names$t1_items, sim$item_names$t2_items, "trat")], anchor = "trat", xsec = FALSE, pair_by = "suffix", t1_suffix = "", t2_suffix = "\\.1" )set.seed(123) sim <- simdat(N = 200) dat <- sim$datw tr_reliability_model( data = dat[, c(sim$item_names$t1_items, sim$item_names$t2_items, "trat")], anchor = "trat", xsec = FALSE, pair_by = "suffix", t1_suffix = "", t2_suffix = "\\.1" )
var_discretize() converts a continuous numeric vector into equal-width
integer categories. It is intended for creating ordered indicators for
lavaan-based CFA workflows. To be used with sim_threshold_cfa
var_discretize( x, n_levels = 10L, min_level = 1L, require_all_levels = FALSE, warn_unused = TRUE )var_discretize( x, n_levels = 10L, min_level = 1L, require_all_levels = FALSE, warn_unused = TRUE )
x |
Numeric vector to discretize. |
n_levels |
Integer. Number of equal-width categories to create. Must be between 2 and 12. |
min_level |
Integer. Lowest score value. Defaults to |
require_all_levels |
Logical. If |
warn_unused |
Logical. If |
Missing values are allowed. They are ignored when computing the
discretization range and are preserved as NA_integer_ in the returned
score vector.
A list containing the discretized score, bin midpoints, bin edges, used levels, and unused levels.
x <- 0:24 out <- var_discretize(x, n_levels = 10) out$score out$midpointsx <- 0:24 out <- var_discretize(x, n_levels = 10) out$score out$midpoints