The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.
brsmm() adds Gaussian random effects to the mean
submodel of brs() for clustered or longitudinal scores
(patients with repeated visits, raters, centers). This vignette covers
the model, the integration methods, random-effect summaries and tests,
marginal and conditional predictions, residuals, group-level
cross-validation and simulation. The interval-censored likelihood and
the interval maps are those of vignette("brs-intro"). The
data sets are small so that the vignette builds quickly; the results are
illustrative.
For observation \(i\) of group \(g\), \[
g_1(\mu_{gi}) = x_{gi}^\top \beta + w_{gi}^\top b_g, \qquad
g_2(\sigma_{gi}) = z_{gi}^\top \gamma, \qquad b_g \sim N(0, D),
\] where \(w_{gi}\) is the row
of the random-effects design given by
random = ~ terms | group (~ 1 | id for a
random intercept, ~ 1 + x | id for an intercept and a slope
with a free correlation). The dispersion submodel has no random effects.
Given \(b_g\), each score contributes
the cell probability of brs(), and the marginal
log-likelihood is \[
\ell(\theta) = \sum_g \log \int \prod_i P(S_{gi} = s_{gi} \mid b)\,
\varphi(b; 0, D)\, db .
\] \(D = LL^\top\) is
parameterized by its lower Cholesky factor: the log of each diagonal
entry and the off-diagonal entries. These are the
(re_chol_logsd)_ and (re_chol)_ entries of
coef(); for a random intercept the single entry is \(\log \mathrm{SD}(b)\).
The integral is computed around the mode \(\hat b_g\) of the log integrand, with curvature \(H_g\) there:
int_method = "laplace" (default): the Laplace
approximation, fast and accurate when groups are not tiny.int_method = "aghq": adaptive Gauss–Hermite quadrature
with n_points nodes per dimension (default 11) on a product
grid centered at \(\hat b_g\) and
scaled by \(H_g^{-1/2}\) (at most
500000 nodes).int_method = "qmc": importance sampling from \(N(\hat b_g, H_g^{-1})\) on
qmc_points Halton points (default 1024). It is
deterministic and, with two or more random effects, slightly
underestimates the log-likelihood; prefer "aghq" for up to
three random effects.optim() uses the compiled gradient of the chosen
approximation; the Hessian for vcov() is a central
difference of that gradient.
brsmm_sim() simulates scores from this model, given the
random-effect standard deviations and correlation. Here: 30 patients
with four visits on an NRS-11 scale, a centered time tc, a
treatment arm trt, and a random intercept and time
slope:
d <- expand.grid(visit = 1:4, id = 1:30)
d$tc <- (d$visit - 2.5) / 1.5
d$trt <- as.integer(d$id %% 2 == 0)
set.seed(7)
s <- brsmm_sim(~ tc + trt, random = ~ 1 + tc | id, data = d,
beta = c(-0.3, -0.4, -0.5), phi = qlogis(0.15),
re_sd = c(0.8, 0.3), re_cor = 0.3, ncuts = 10)
head(s)
#> left right yt y delta visit id tc trt
#> 1 0.85000 0.95 9e-01 9 3 1 1 -1.0000000 0
#> 2 0.85000 0.95 9e-01 9 3 2 1 -0.3333333 0
#> 3 0.55000 0.65 6e-01 6 3 3 1 0.3333333 0
#> 4 0.55000 0.65 6e-01 6 3 4 1 1.0000000 0
#> 5 0.25000 0.35 3e-01 3 3 1 2 -1.0000000 1
#> 6 0.00001 0.05 1e-05 0 1 2 2 -0.3333333 1
attr(s, "truth")$par
#> (Intercept) tc
#> -0.3000000 -0.4000000
#> trt (phi)_(Intercept)
#> -0.5000000 -1.7346011
#> (re_chol_logsd)_(Intercept)|id (re_chol)_tc:(Intercept)|id
#> -0.2231436 0.0900000
#> (re_chol_logsd)_tc|id
#> -1.2511281The attribute "truth" holds the true parameters in the
order and on the scale of coef(), and "ranef"
the simulated random effects.
fit_ri <- brsmm(y ~ tc + trt, random = ~ 1 | id, data = s)
fit_rs <- brsmm(y ~ tc + trt, random = ~ 1 + tc | id, data = s)
summary(fit_rs)
#>
#> Call:
#> brsmm(formula = y ~ tc + trt, random = ~1 + tc | id, data = s)
#>
#> Randomized Quantile Residuals:
#> Min 1Q Median 3Q Max
#> -2.5557 -0.6204 0.0051 0.4749 2.9221
#>
#> Coefficients (mean model with logit link):
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) 0.08037 0.29963 0.268 0.7885
#> tc -0.36923 0.11697 -3.157 0.0016 **
#> trt -0.73612 0.42983 -1.713 0.0868 .
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Coefficients (dispersion model for sigma with logit link):
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) -1.7568 0.2094 -8.391 <2e-16 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Random effects (SD and Corr; 95% Wald CI on the log / atanh scale; no tests, see anova()):
#> Estimate Lower Upper
#> SD (Intercept) 1.0686 0.7727 1.4779
#> SD tc 0.1970 0.0095 4.0923
#> Corr tc,(Intercept) 0.1645 -0.8639 0.9276
#> ---
#> Mixed beta interval model (Laplace)
#> Observations: 120 | Groups: 30
#> Log-likelihood: -253.8471 on 7 Df | AIC: 521.6942 | BIC: 541.2066
#> Pseudo R-squared: 0.0788
#> Number of iterations: 64 (BFGS)
#> Censoring: 102 interval | 10 left | 8 rightThe random-effects block reports the standard deviations and the
correlation with intervals built on the log scale (SD) and the \(\mathrm{atanh}\) scale (correlation) and
mapped back. It gives no \(z\)-test: a
variance of zero lies on the boundary of the parameter space, so test it
with anova() (below). The same numbers are in
summary(fit)$varcorr.
The three integration methods give nearly the same estimates on this data:
fit_aghq <- brsmm(y ~ tc + trt, random = ~ 1 | id, data = s,
int_method = "aghq", n_points = 7)
fit_qmc <- brsmm(y ~ tc + trt, random = ~ 1 | id, data = s,
int_method = "qmc", qmc_points = 256)
round(cbind(laplace = c(coef(fit_ri), logLik = logLik(fit_ri)),
aghq = c(coef(fit_aghq), logLik = logLik(fit_aghq)),
qmc = c(coef(fit_qmc), logLik = logLik(fit_qmc))), 4)
#> laplace aghq qmc
#> (Intercept) 0.0744 0.0751 0.0741
#> tc -0.3611 -0.3599 -0.3602
#> trt -0.7140 -0.7163 -0.7160
#> (phi)_(Intercept) -1.7110 -1.7104 -1.7100
#> (re_chol_logsd)_(Intercept)|id 0.0545 0.0665 0.0641
#> logLik -253.9230 -253.3591 -253.4076ranef() returns the conditional modes \(\hat b_g\), one row per group;
fit$random$D and fit$random$sd_b hold the
estimated covariance matrix and standard deviations:
head(ranef(fit_rs))
#> (Intercept) tc
#> 1 0.8194782 -0.029209224
#> 2 -1.5034029 -0.100498130
#> 3 -1.5294370 -0.087103237
#> 4 -0.5462813 -0.002064519
#> 5 -0.7803565 -0.001166131
#> 6 -0.9184533 -0.055430537
fit_rs$random$D
#> [,1] [,2]
#> [1,] 1.14198316 0.03463383
#> [2,] 0.03463383 0.03881125brsmm_re_study() summarizes the random effects: the
covariance, the shrinkage of the modes and the intraclass correlation of
\(\mathrm{logit}(Y)\), computed with
the beta level-1 variance:
brsmm_re_study(fit_rs)
#>
#> Random-effects study
#> Groups: 30
#>
#> Random-effects (VarCorr):
#> Name Std.Dev. Corr
#> (Intercept) 1.0686
#> tc 0.1970 0.1645
#>
#> ICC (logit(Y) scale, beta level-1 variance): 0.5704
#>
#> Summary by term (SD_model = model SD; shrinkage = Var(modes)/Var(model)):
#> term sd_model mean_mode sd_mode shrinkage_ratio shapiro_p
#> (Intercept) 1.0686 0.0138 0.9665 0.8179 0.6541
#> tc 0.1970 0.0026 0.0725 0.1354 0.7503autoplot.brsmm() also draws
"ranef_density", "ranef_pairs",
"ranef_qq", "shrinkage",
"calibration", "score_dist" and
"residuals_by_group"; plot.brsmm() gives the
panels of plot.brs() with response or Pearson residuals,
plus random-effect panels.
Adding a random-effect term puts its variance on the boundary of the
parameter space under the null hypothesis, so the likelihood ratio does
not follow \(\chi^2_d\). When a model
adds one random-effect term, anova() uses the mixture \(\frac12\chi^2_{d-1} + \frac12\chi^2_d\)
(Self and Liang, 1987; Stram and Lee, 1994): \(\frac12\chi^2_0 +
\frac12\chi^2_1\) from brs() to a random intercept,
and \(\frac12\chi^2_1 +
\frac12\chi^2_2\) from a random intercept to an intercept and
slope. The heading of the table says which rows use it.
fit_0 <- brs(y ~ tc + trt, data = s)
anova(fit_0, fit_ri, fit_rs)
#> Likelihood-ratio comparison of brs/brsmm models
#> Rows M2, M3: one added random effect (variance on the boundary); Pr(>Chisq) from the chi-bar-square mixture 1/2 chi2(Df - 1) + 1/2 chi2(Df).
#>
#> Df logLik AIC BIC Chisq Chi Df Pr(>Chisq)
#> M1 (brs) 4 -279.98 567.97 579.12
#> M2 (brsmm) 5 -253.92 517.85 531.78 52.1196 1 2.611e-13 ***
#> M3 (brsmm) 7 -253.85 521.69 541.21 0.1517 2 0.8119
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1When a variance is essentially zero, brsmm() warns
“Variance component at the boundary”; rely on the likelihood-ratio test
for that term.
AIC() and BIC() accept several fits,
brs and brsmm alike, and return a
df/criterion table as stats::AIC() and
stats::BIC() do. There is no single sample size for a mixed
model: BIC() counts observations by default and groups with
n = "groups". Compare BIC values only under the same
convention.
predict() gives conditional quantities: a group of the
fit uses its mode \(\hat b_g\), a new
group gets \(b = 0\):
nd <- data.frame(tc = 0, trt = 1, id = c(1, 999))
predict(fit_ri, newdata = nd, type = "expected_score")
#> [1] 5.448165 3.453218Under a nonlinear link, the prediction at \(b = 0\) describes a subject whose random
effects are zero, which is not the population average.
brs_marginal() integrates the random effects out to give
population-averaged score probabilities, expected scores and exceedance
probabilities \(P(S \ge k)\), with
delta-method standard errors over all parameters;
conditional = TRUE sets \(b =
0\) instead. The grouping variable is not needed in
newdata:
nd <- data.frame(tc = c(-1, 1), trt = 1)
brs_marginal(fit_ri, nd, type = "expected_score")
#> tc trt row estimate std.error lower upper
#> 1 -1 1 1 4.433166 0.6380770 3.242149 5.693117
#> 2 1 1 2 3.048002 0.5686567 2.057841 4.259101
brs_marginal(fit_ri, nd, type = "expected_score", conditional = TRUE)
#> tc trt row estimate std.error lower upper
#> 1 -1 1 1 4.308536 0.7745227 2.895803 5.843569
#> 2 1 1 2 2.684320 0.6323208 1.633255 4.081798Differences between rows, such as the change from the first to the
last visit in \(P(S \ge 5)\), are given
by contrast:
For type = "rqr" (randomized quantile residuals) and
"cell_deviance", re selects how the random
effects enter:
"conditional" (default): at the modes \(\hat b_g\). The modes are shrunk toward
zero, so the rqr residuals are not exactly \(N(0, 1)\) under the model, especially for
small groups."marginal": from the marginal distribution of each
observation, with the random effects integrated out. The rqr residuals
are \(N(0, 1)\) under the model;
residuals of the same group are correlated."posterior": at one draw of each \(b_g\) from its posterior (importance
resampling with n_draws proposals per group, default 200).
The rqr residuals are close to independent \(N(0, 1)\) under the model, without the
shrinkage of the modes.set.seed(1)
res <- sapply(c("conditional", "marginal", "posterior"), function(r)
residuals(fit_ri, type = "rqr", re = r))
round(rbind(mean = colMeans(res), sd = apply(res, 2, sd)), 3)
#> conditional marginal posterior
#> mean -0.013 0.007 0.003
#> sd 0.883 1.019 0.999The other types of residuals.brs()
("response", "pearson", …) are evaluated at
the conditional modes.
brsmm_cv() forms folds by group, so all observations of
a group are held out together. Each held-out group is scored by its
marginal log-likelihood at the training estimates: its random effects
are integrated out, so no estimate of them is needed. seed
makes the folds reproducible, and attr(, "folds") returns
them for reuse with another model:
cv_ri <- brsmm_cv(y ~ tc + trt, random = ~ 1 | id, data = s, k = 3, seed = 1)
cv_ri
#> repeat fold n_groups_train n_groups_test n_train n_test log_score converged
#> 1 1 1 20 10 80 40 -84.01975 TRUE
#> 2 1 2 20 10 80 40 -85.92605 TRUE
#> 3 1 3 20 10 80 40 -87.83580 TRUE
#> error warning
#> 1 <NA> <NA>
#> 2 <NA> <NA>
#> 3 <NA> <NA>
folds <- attr(cv_ri, "folds")[[1]]
cv_rs <- brsmm_cv(y ~ tc + trt, random = ~ 1 + tc | id, data = s,
folds = folds)
c(ri = attr(cv_ri, "log_score"), rs = attr(cv_rs, "log_score"))
#> ri rs
#> -257.7816 -259.1025The total held-out log score is the log probability of the held-out
scores (higher is better). For score data it is on the same scale as the
held-out log-likelihood of any other model that gives a pmf on \(0, \ldots, K\), evaluated on the same
folds. brs_loglik_contrib() returns the per-group
contributions of a fit, also for new data.
simulate() draws new scores from a fitted model. For
brsmm fits, re = "marginal" (default) draws
new random effects and re = "conditional" keeps the
conditional modes. A quick check compares the observed share of each
border score with its simulated distribution:
sims <- simulate(fit_ri, nsim = 50, seed = 1)
obs <- c(p0 = mean(s$y == 0), p10 = mean(s$y == 10))
sim_p <- rbind(p0 = colMeans(sims == 0), p10 = colMeans(sims == 10))
cbind(observed = obs, t(apply(sim_p, 1, quantile, c(0.05, 0.95))))
#> observed 5% 95%
#> p0 0.08333333 0.041666667 0.1333333
#> p10 0.06666667 0.008333333 0.0675000cutpoints = "boundary" works in brsmm() as
in brs() (see vignette("brs-intro")), with the
same requirements and the same 2-df likelihood-ratio test against the
fixed map; brsmm_sim() simulates from given boundary
cutpoints.
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.