A function for calculating tolerance limits for the difference between two measurements (difference = x-y). This is a procedure that should produce results similar to the Bland-Altman limits of agreement. See vignettes for more details.
Usage
tolerance_limit(
data,
x,
y,
id = NULL,
condition = NULL,
time = NULL,
pred_level = 0.95,
tol_level = 0.95,
conf_level = 0.95,
tol_method = c("analytic", "boot_cal"),
bound_type = c("joint", "iu"),
prop_bias = FALSE,
log_tf = FALSE,
log_tf_display = c("ratio", "sympercent"),
model = c("gls", "lme"),
cor_type = c("sym", "car1", "ar1", "none"),
correlation = NULL,
weights = NULL,
keep_model = TRUE,
replicates = 999
)Arguments
- data
A data frame containing the variables.
- x
Name of the column for the first measurement.
- y
Name of the column for the second measurement.
- id
Name of the column for the subject ID. With
model = "lme", two column names can be given for nested random intercepts, outer level first (e.g.,c("golfer", "club")for clubs within golfers); see "Model assumptions" in details.- condition
Name of the column indicating different conditions subjects were tested under. This can be left missing if there are no differing conditions to be tested. Supplying a condition adds a separate residual variance for each condition (
nlme::varIdent); see "Model assumptions" in details.- time
Name of the column indicating the time points. Only necessary if the data is from time series or repeated measures collection.
- pred_level
Prediction level for the prediction interval, which is also the content (the proportion of differences to be covered, beta) for the tolerance limits. Default is 95%.
- tol_level
Confidence level (gamma) for the tolerance limits. Default is 95%. See
bound_typefor what this confidence refers to.- conf_level
Confidence level for the confidence interval of the bias (
lower.CL/upper.CL). Default is 95%. This does not affect the prediction or tolerance limits. For a two one-sided tests (TOST) procedure on the bias at level alpha, useconf_level = 1 - 2 * alpha(e.g., 0.90).- tol_method
Method for calculating the tolerance limits. Options are "analytic" (default) for closed-form limits and "boot_cal" (experimental) for the closed-form limits calibrated by a parametric bootstrap. The analytic limits are recommended; see details. Both target the interval set by
bound_type. The previous names, "approx" and "perc", are deprecated aliases for "analytic" and "boot_cal".- bound_type
Which claim the tolerance limits (
lower.TL/upper.TL) support. "joint" (default) gives a beta-content, gamma-confidence tolerance interval: with confidencetol_level, at leastpred_levelof all differences lie within the limits. "iu" gives equal-tailed bounds: a one-sidedtol_levelconfidence bound on each of the (1 -pred_level)/2 and (1 +pred_level)/2 percentiles of the differences. The "iu" bounds are intended for an intersection-union test of agreement against a maximal allowable difference and are not a jointtol_levelinterval. See details.- prop_bias
Whether to include a proportional bias term in the model. Determines whether proportional bias should be considered for the prediction/tolerance limits calculations. Note that a slope of the differences on the average can appear without any true proportional bias when the two methods have unequal measurement error variances; see "Model assumptions" in details.
- log_tf
Calculate limits of agreement using log-transformed data.
- log_tf_display
The type of presentation for log-transformed results. The differences between methods can be displayed as a "ratio" or "sympercent".
- model
The type of model for the differences. "gls" (default) is a marginal generalized least squares model (
nlme::gls()) with the correlation structure set bycor_type. "lme" is a linear mixed model (nlme::lme()) with a random intercept for eachid, which requiresid; with twoidcolumns it has nested random intercepts (outer level, and inner level within outer). With compound symmetry and no variance function both give the same fit; "lme" differs when serial correlation (cor_type = "ar1"or"car1") or a variance function (conditionorweights) is added, because it keeps a persistent subject effect and applies the variance function to the residuals only. See "Model assumptions" in details.- cor_type
The type of correlation structure. "sym" is for Compound Symmetry, "car1" is for continuous autocorrelation structure of order 1, or "ar1" for autocorrelation structure of order 1. The autoregressive options ("ar1" and "car1") have no persistent subject effect; see "Model assumptions" in details. With
model = "lme", "sym" fits the random intercept alone, "ar1" and "car1" add serial correlation of the residuals withinidon top of the random intercept, and "none" is not allowed.- correlation
an optional corStruct object describing the within-group correlation structure that overrides the default setting. See the documentation of corClasses for a description of the available corStruct classes. If a grouping variable is to be used, it must be specified in the form argument to the corStruct constructor. Defaults to NULL. With
model = "lme", this is the correlation of the residuals within the innermost level ofid(on top of the random intercepts) and must be grouped by it using the internal names:idfor one level (e.g.,nlme::corAR1(form = ~ time | id)) orid/id_2for two (e.g.,nlme::corAR1(form = ~ time | id/id_2)).- weights
an optional varFunc object or one-sided formula describing the within-group heteroskedasticity structure that overrides the default setting. If given as a formula, it is used as the argument to varFixed, corresponding to fixed variance weights. See the documentation on varClasses for a description of the available varFunc classes. Variance covariates must use the internal column names (
avg,condition,x,y,time) orfitted(.). CurrentlyvarIdent,varFixed,varExp, andvarPowerare supported. If the variance depends onavg, the limits are reported at the minimum, median, and maximum ofavgeven whenprop_bias = FALSE. Withmodel = "lme", the variance function applies to the residuals only; the random-intercept variance is common to all observations.- keep_model
Logical indicator to retain the fitted model (
glsorlme). Useful when working with large data and the model is very large.- replicates
The number of bootstrap replicates. Passed on to the boot function. Default is 999.
Value
Returns single tolerance_delta class object with the results of the agreement analysis with a prediction interval and tolerance limits.
limits: A data frame containing the prediction/tolerance limits. Columns includebias(estimated mean difference),SEM(standard error of the bias),SD(residual standard deviation of a single difference at that row, from the variance function if one is in the model),SEP(standard error of prediction,sqrt(SD^2 + SEM^2)),SD.df(degrees of freedom of the residual variance),SD.upper(one-sidedtol_levelupper confidence bound for SD),SD.between/SD.nested/SD.within(between-subject, nested (inner level), and within-subject components of SD, under compound symmetry or withmodel = "lme";NAotherwise, andSD.nestedisNAwithout nesting),lower.CL/upper.CL(confidence limits for the bias),lower.PL/upper.PL(prediction limits),lower.TL/upper.TL(tolerance limits), and, fortol_method = "boot_cal",lower.TL.level/upper.TL.level(the calibrated nominal levels at which the analytic limits were computed).model: The fittedglsorlmemodel; NULL if keep_model set to FALSE. The data (with the internal column namesx,y,delta,avg,id(the row number if not supplied), and, if supplied,conditionandtime), correlation structure, and variance function are stored with the model, soupdate()andnlme::getData()can be used on it directly.call: The matched call.
Details
The tolerance limits calculated in this function are based on the papers by Francq & Govaerts (2016), Francq, et al. (2019), and Francq, et al. (2020). The formulas, including how the degrees of freedom are determined for clustered data, are given in vignette("agreement_analysis", package = "SimplyAgree").
The output contains three kinds of interval, which support different claims:
Prediction interval (
lower.PL/upper.PL): a beta-expectation tolerance interval. It is expected to contain a single future difference, from a new subject, with probabilitypred_level, averaged over repeated studies. It is not a confidence statement: any single prediction interval may cover less thanpred_levelof the differences.Tolerance limits,
bound_type = "joint"(lower.TL/upper.TL): a beta-content, gamma-confidence tolerance interval. With confidencetol_level, at leastpred_levelof all differences lie within the limits. If the limits lie within a maximal allowable difference of plus or minus delta, one can conclude (at level 1 -tol_level) that at leastpred_levelof the differences lie within plus or minus delta (Lin, 2000). The tails do not need to be split equally.Tolerance limits,
bound_type = "iu": a one-sidedtol_levelbound on the lower and on the upper (1 -/+pred_level)/2 percentile of the differences, i.e., on the limits of agreement. If both bounds lie within plus or minus delta, one can reject, at level 1 -tol_level, that either limit of agreement lies outside plus or minus delta (intersection-union test). Both bounds hold together only about 1 - 2 * (1 -tol_level) of the time, so they should not be reported as a jointtol_levelinterval. These target the same quantities as the default confidence bounds fromagreement_limit()(numerically close but not identical, because the models differ).
The "joint" and "iu" limits test different hypotheses (coverage versus each tail), so they do not nest: the "iu" bounds can be wider than the "joint" limits, and a skewed distribution can pass one test and fail the other.
With tol_method = "analytic":
"joint" uses the approximation of Howe (1969) as described by Francq et al. (2020), with the standard error of prediction (SEP) in place of the sample standard deviation. For independent data this matches Howe's approximation exactly.
"iu" uses the exact noncentral t bound for independent data, and otherwise the MOVER bound of Zou (2013).
Both use a one-sided upper confidence bound for the residual standard deviation (SD). With independent data this is the usual chi-square bound. With compound symmetry (
cor_type = "sym"), the between- and within-subject variance components are combined with the MOVER, as inagreement_limit()withdata_type = "nest". With other correlation structures, the bound uses an effective degrees of freedom (SD.df) from the approximate covariance of the variance parameters.
The analytic limits are exact for independent data (without a variance function). In simulations (95% target), they were close to nominal with compound symmetry (about 0.95, with 10 to 20 subjects) and with AR(1) correlation (about 0.95). With condition (a separate residual variance per condition) combined with compound symmetry, the variance components for each condition are bounded using that condition's cluster sizes and degrees of freedom; coverage was then about 0.96. Other variance functions (weights) use the cluster sizes of the whole data set and have not been checked by simulation.
With tol_method = "boot_cal", the analytic limits are calibrated by a parametric bootstrap (Loh, 1987; Beran, 1987). New data are simulated from the fitted model (Francq et al., 2019), the model is refit to each replicate, and the analytic limits are computed for each replicate over a range of nominal confidence levels. The level is chosen at which the replicates achieve the target confidence for the fitted model:
"joint": a proportion
tol_levelof the replicate intervals contain at leastpred_levelof the fitted distribution of the differences."iu": separately for each bound, a proportion
tol_levelof the replicate bounds lie beyond the fitted limit of agreement.
The limits for the observed data are the analytic limits at the calibrated levels, which are returned as lower.TL.level and upper.TL.level. tol_method = "boot_cal" is experimental. The calibration is judged against the fitted model, so it treats the estimated correlation as known. With few subjects this adds its own error: in simulations with 10 to 15 subjects (95% target), coverage ranged from about 0.93 (compound symmetry, with or without condition) to about 0.96 (AR(1) correlation), while the analytic limits were 0.95 to 0.965 in the same settings. The analytic limits are therefore recommended; the bootstrap may be useful as a check for models whose analytic limits have not been evaluated (e.g., other variance functions or prop_bias = TRUE).
For clustered data (id supplied), all limits refer to a new difference from a new subject: the SD includes both the between- and within-subject variance.
Model assumptions
With model = "gls" (default), the model is a marginal (generalized least squares) model fit with nlme::gls(). For the default setup (cor_type = "sym" without a variance function), compound symmetry with a non-negative correlation gives the same likelihood as a random-intercept model, and targets the marginal distribution of a single difference from a randomly chosen subject. With model = "lme", the model is a linear mixed model fit with nlme::lme() with a random intercept for each id; with compound symmetry and no variance function it gives the same fit and limits. With model = "lme", the degrees of freedom for the bias are the containment degrees of freedom (subjects - 1 for the models fit here), because the Satterthwaite approximation for lme models in emmeans can fail. The other options carry assumptions that should be checked:
Autoregressive correlation (
cor_type = "ar1"or"car1"): withmodel = "gls", the correlation between two measurements from the same subject decays towards zero as they get further apart in time. A persistent subject-specific bias (e.g., a subject by method interaction) instead makes all measurements from that subject equally correlated. When such an effect exists, the autoregressive structures miss most of the long-range correlation, so the standard error of the bias is too small and the limits are too narrow; the bootstrap does not correct this, because it simulates from the same model. In one simulation (20 subjects with 19 measurements each, and a random subject effect), the 95% confidence interval for the bias covered the true value 74% of the time with AR(1), versus 96% with compound symmetry. Withmodel = "lme", the serial correlation is added to the residuals on top of a random intercept, which keeps the persistent subject effect. In simulations with a random subject effect and AR(1) residuals (15 subjects, 8 measurements each; 95% target), coverage of the joint tolerance limits was about 0.89 withmodel = "gls", cor_type = "ar1"and about 0.95 withmodel = "lme", cor_type = "ar1". Use the autoregressive options withmodel = "gls"only when no persistent subject effect is expected. The fits can be compared withAIC()on the returned models (a REML comparison is valid because the fixed effects are the same).Variance functions with compound symmetry (
condition, orweightstogether withcor_type = "sym"): ingls, the correlation applies to the standardized residuals, so the covariance between two measurements from the same subject is rho * sigma_i * sigma_j. The between-subject variance therefore scales with the variance function (e.g., it is larger in a condition with a larger residual SD), rather than being common to all conditions. The marginal variance of each condition, which drives the limits, is not directly affected, but the standard error of each condition's bias and the reported variance components are. Withmodel = "lme", the variance function applies to the residuals only, and the between-subject variance is common to all conditions. In simulations where that was true (12 subjects; 95% target), the joint tolerance limits had coverage of about 0.96 withmodel = "gls"and about 0.97 withmodel = "lme".Clustering levels:
model = "gls"has a single grouping factor (id). Withmodel = "lme",idcan name two nested levels (e.g.,id = c("golfer", "club")for shots with several clubs per golfer), giving random intercepts for the outer level and for the inner level within the outer level. Settingidto the inner level alone (e.g., a golfer-by-club identifier) should be avoided: it drops the correlation across inner levels within the same outer level, which understates the uncertainty. In simulations (15 subjects with 2 to 4 settings each; 95% target), the joint tolerance limits had coverage of about 0.88 to 0.91 with the inner level asid, versus about 0.96 with nested random intercepts. Grouping by the outer level only (id = "golfer") also gave close to nominal coverage (about 0.96), because the inner-level variance is then absorbed into the residual; the nested model additionally reports the inner-level variance component (SD.nested). More than two levels are not supported.Variance components: the SD of a single difference splits into between-subject (
SD.between), inner-level (SD.nested, with twoidcolumns), and within-subject (SD.within) components. Withmodel = "gls"and compound symmetry these are sqrt(rho) * SD and sqrt(1 - rho) * SD; withmodel = "lme",SD.betweenis the random-intercept SD andSD.withinthe residual SD. They can be used, for example, for the point estimate of limits for the mean of m measurements per subject, bias +/- z * sqrt(SD.between^2 + SD.within^2 / m) (without residual correlation), or for the between-subject floor of agreement. They are point estimates only.Proportional bias (
prop_bias = TRUE): a non-zero slope of the differences on the average can appear without any true proportional bias. Whenever the two methods have unequal measurement error variances, cov(difference, average) = (var(x) - var(y)) / 2 is not zero (Bland & Altman, 1999), so the slope should not be read as proportional bias on its own. Errors-in-variables methods such asdem_reg()orpb_reg()are better suited to assessing proportional bias.
References
Francq, B. G., & Govaerts, B. (2016). How to regress and predict in a Bland–Altman plot? Review and contribution based on tolerance intervals and correlated‐errors‐in‐variables models. Statistics in mMdicine, 35(14), 2328-2358.
Francq, B. G., Lin, D., & Hoyer, W. (2019). Confidence, prediction, and tolerance in linear mixed models. Statistics in Medicine, 38(30), 5603-5622.
Francq, B. G., Berger, M., & Boachie, C. (2020). To tolerate or to agree: A tutorial on tolerance intervals in method comparison studies with BivRegBLS R Package. Statistics in Medicine, 39(28), 4334-4349.
Howe, W. G. (1969). Two-sided tolerance limits for normal populations—some improvements. Journal of the American Statistical Association, 64(326), 610-620.
Lin, L. I. (2000). Total deviation index for measuring individual agreement with applications in laboratory performance and bioequivalence. Statistics in Medicine, 19(2), 255-270.
Loh, W. Y. (1987). Calibrating confidence coefficients. Journal of the American Statistical Association, 82(397), 155-162.
Beran, R. (1987). Prepivoting to reduce level error of confidence sets. Biometrika, 74(3), 457-468.
Zou, G. Y. (2013). Confidence interval estimation for the Bland–Altman limits of agreement with multiple observations per individual. Statistical Methods in Medical Research, 22(6), 630-642.
Examples
data('reps')
# Simple
tolerance_limit(x = "x", y ="y", data = reps)
#> Agreement between Measures (Difference: x-y)
#> 95% CI for Bias; 95% Prediction Interval
#> Tolerance Limits: at least 95% of differences with 95% confidence
#> Model: GLS, independent differences
#>
#> Bias Bias CI Prediction Interval Tolerance Limits
#> 0.4383 [-0.1669, 1.0436] [-2.1998, 3.0764] [-2.993, 3.8697]
#>
#> SD of Differences = 1.217
#>
# Nested
tolerance_limit(x = "x", y ="y", data = reps, id = "id")
#> Agreement between Measures (Difference: x-y)
#> 95% CI for Bias; 95% Prediction Interval
#> Tolerance Limits: at least 95% of differences with 95% confidence
#> Model: GLS, compound symmetry within id
#>
#> Bias Bias CI Prediction Interval Tolerance Limits
#> 0.7046 [-1.5571, 2.9663] [-4.4686, 5.8778] [-8.3471, 9.7562]
#>
#> SD of Differences = 1.4581
#> Between-Subject SD = 1.4048; Within-Subject SD = 0.3906
#>
# Nested, random intercept (mixed) model
tolerance_limit(x = "x", y ="y", data = reps, id = "id", model = "lme")
#> Agreement between Measures (Difference: x-y)
#> 95% CI for Bias; 95% Prediction Interval
#> Tolerance Limits: at least 95% of differences with 95% confidence
#> Model: random intercept for each id (lme)
#>
#> Bias Bias CI Prediction Interval Tolerance Limits
#> 0.7046 [-1.5511, 2.9603] [-4.4548, 5.864] [-8.3471, 9.7562]
#>
#> SD of Differences = 1.4581
#> Between-Subject SD = 1.4048; Within-Subject SD = 0.3906
#>
# Nested random intercepts (e.g., measurements within settings within subjects)
# \donttest{
reps2 = reps
reps2$setting = rep(1:2, length.out = nrow(reps2))
tolerance_limit(x = "x", y = "y", data = reps2, id = c("id", "setting"),
model = "lme")
#> Agreement between Measures (Difference: x-y)
#> 95% CI for Bias; 95% Prediction Interval
#> Tolerance Limits: at least 95% of differences with 95% confidence
#> Model: nested random intercepts for id and setting within id (lme)
#>
#> Bias Bias CI Prediction Interval Tolerance Limits
#> 0.7046 [-1.5511, 2.9603] [-4.4548, 5.864] [-8.3473, 9.7565]
#>
#> SD of Differences = 1.4581
#> Between-Subject SD = 1.4048; Nested SD = 0; Within-Subject SD = 0.3906
#>
# }
