library(MplusAutomation)
library(posterior)
library(cmdstanr)
register_knitr_engine()Bayesian Multilevel Factor Model (Part III)
The code in this blog post was originally drafted in 2024, but then I got distracted with some other projects and personal matters. I’m finally able to get this done.
In Part I, I introduced the multilevel factor model and how to fit it using blavaan and Stan. In Part II, I explored the issue of heterogeneous latent variances across clusters and how to model them. In this Part III, I will further generalize the model to allow for random factor loadings, meaning that the relationship between the latent factor and the observed items can vary across clusters.
Simulate Data
Same set up: One-factor four items; heterogeneous latent variance; two items with random intercepts, one item with random loading
# Design parameters
set.seed(1234)
num_clus <- 500
clus_size <- 10
icc_eta <- .10
lambda <- c(.7, .9, .7, .8)
nu <- c(0, 0.3, -0.5, 0.2)
range_dpsiw <- .80
theta_lambda <- c(0, 0, 0, 0.08) # loading variances
theta_nu <- c(0, 0, 0.1, 0.05) # intercept variances
theta <- c(.51, .51, .40, .40) # uniqueness
# assume one-factor model holds for now
# Simulate latent variable
clus_id <- rep(1:num_clus, each = clus_size)
etab <- rnorm(num_clus, sd = sqrt(icc_eta / (1 - icc_eta)))
psiw <- 1 + seq(-range_dpsiw, range_dpsiw, length.out = num_clus)
# Arrange etaw and psiw so that they're negatively correlated
etab <- sort(etab)[c(
sample((num_clus / 2 + 1):num_clus),
(sample(seq_len(num_clus / 2)))
)]
etaw <- rnorm(num_clus * clus_size, sd = psiw[clus_id])
eta <- etab[clus_id] + etaw
# confirm the simulated variable behaves as expected
# lmer(eta ~ (1 | clus_id))
# Simulate items for each cluster
num_items <- 4
y <- lapply(1:num_clus, FUN = \(j) {
yj <- nu +
rnorm(nu, sd = sqrt(theta_nu)) +
tcrossprod(
lambda + rnorm(lambda, sd = sqrt(theta_lambda)),
etab[j] + etaw[clus_id == j]
) +
rnorm(num_items * clus_size, sd = sqrt(theta))
t(yj)
})
dat <- do.call(rbind, y) |>
as.data.frame() |>
setNames(paste0("y", 1:4)) |>
cbind(id = clus_id)Model Formulation
The model can be viewed as a conditional normal model. Specifically, if we condition on the cluster-specific random effects, the observations within a cluster follow a multivariate normal distribution.
Let \(\boldsymbol{\mathbf{\eta}}^b_j\) be the between-cluster latent factor and \(\log(\psi^w_j)\) be the log of the within-cluster factor variance. We also allow the factor loadings \(\boldsymbol{\mathbf{\Lambda}}_j\) to be random. Conditioned on these random effects, the within-cluster moments are:
\[ \begin{aligned} \bar{\boldsymbol{\mathbf{y}}}_j \mid \eta^b_j, \log(\psi^w_j), \boldsymbol{\mathbf{\Lambda}}_j & \sim N(\boldsymbol{\mathbf{\nu }}+ \boldsymbol{\mathbf{\Lambda}}_j \eta^b_j, \boldsymbol{\mathbf{\Sigma}}_{Wj} + \boldsymbol{\mathbf{\Theta}}^b) \\ \boldsymbol{\mathbf{y}}_{ij} - \bar{\boldsymbol{\mathbf{y}}}_j \mid \eta^b_j, \log(\psi^w_j), \boldsymbol{\mathbf{\Lambda}}_j & \sim N(\boldsymbol{\mathbf{0}}, \boldsymbol{\mathbf{\Sigma}}_{Wj}) \quad \text{for } i = 1, \ldots, n_j - 1 \end{aligned} \]
where the within-cluster covariance matrix is:
\[ \boldsymbol{\mathbf{\Sigma}}_{Wj} = \boldsymbol{\mathbf{\Lambda}}_j \psi^w_j \boldsymbol{\mathbf{\Lambda}}_j^\intercal + \boldsymbol{\mathbf{\Theta}}^w \]
Note that \(\psi^w_j = \exp(\log(\psi^w_j))\).
The between-subject random effects include the latent factor part \(\boldsymbol{\mathbf{b}}^\eta_j = (\eta^b_j, \log[\psi^w_j])\) and the item parameter part. For the items, we have random intercepts (which are standard in multilevel models) and random loadings. Let’s denote the vector of random loadings as \(\boldsymbol{\mathbf{b}}^\lambda_j = \mathop{\mathrm{vec}}(\boldsymbol{\mathbf{\Lambda}}_j)\) and random intercepts as \(\boldsymbol{\mathbf{\varepsilon}}^{b}_j\).
So assume we have four items and one factor, the full vector of random effects has a dimension of \(2 + 4 + 4 = 10\). A full \(10 \times 10\) covariance matrix is difficult to estimate. However, in factor analysis we usually assume:
- The latent factor components (\(\boldsymbol{\mathbf{b}}^\eta_j\)) are independent of the item-specific components (\(\boldsymbol{\mathbf{b}}^\lambda_j\) and \(\boldsymbol{\mathbf{\varepsilon}}^{b}_j\)).
- Local independence: The item-specific components for item \(k\) are independent of those for item \(k'\) for \(k \neq k'\).
Therefore, we only need to estimate: * The \(2 \times 2\) covariance matrix for the latent factor components (\(\eta^b_j\) and \(\log[\psi^w_j]\)). * The covariances between the random loading and random intercept for the same item (if both are random).
Using Mplus
Mplus allows for “random item parameters” using the | symbol in the %WITHIN% block. Here, lam1-lam4 | fw BY y1-y4 defines the random loadings. We then model these random variables in the %BETWEEN% block.
# Save data for Mplus
write.table(dat, file = "mcfa_rl.dat", row.names = FALSE, col.names = FALSE)
writeLines(
"
TITLE: Bayesian 1-factor model with random variance and loadings;
DATA: FILE = mcfa_rl.dat;
VARIABLE:
NAMES = y1-y4 id;
USEVAR = y1-y4;
CLUSTER = id;
ANALYSIS:
TYPE = TWOLEVEL RANDOM;
ESTIMATOR = BAYES; ! This is default
BITERATION = 500000 (10000);
BCONVERGENCE = .005; ! ESS of ~ 100
CHAINS = 3;
PROCESS = 3;
MODEL: %WITHIN%
lam1-lam4 | fw BY y1-y4;
logpsiw | fw;
%BETWEEN%
fb BY y1-y4* (l1-l4);
logpsiw with fb; ! psi_12
fb; logpsiw; ! psi_1 and psi_2
logpsiw fb WITH lam1-lam4@0;
[logpsiw@0]; ! for setting the scale
[lam1-lam4] (l1-l4);
lam1-lam4*;
y1-y4;
lam1-lam4 PWITH y1-y4;
OUTPUT: TECH1 TECH8;
SAVEDATA:
BPARAMETERS = mcfa_random_load_draws.dat;
",
con = "mcfa_random_load.inp"
)mplus_draws <- readModels("mcfa_random_load.out")$bparameters[[2]] # discard burn-in
# Drop chain and iteration columns
mplus_draws[[1]] <- mplus_draws[[1]][, -c(1, 2)]
mplus_draws[[2]] <- mplus_draws[[2]][, -c(1, 2)]
mplus_draws[[3]] <- mplus_draws[[3]][, -c(1, 2)]
mplus_draws <- posterior::as_draws_list(mplus_draws)
mplus_draws |>
posterior::summarise_draws() |>
knitr::kable(digits = 2)| variable | mean | median | sd | mad | q5 | q95 | rhat | ess_bulk | ess_tail |
|---|---|---|---|---|---|---|---|---|---|
| Parameter.1_%WITHIN%:.Y1 | 0.53 | 0.53 | 0.01 | 0.01 | 0.51 | 0.55 | 1.00 | 7865.59 | 27926.49 |
| Parameter.2_%WITHIN%:.Y2 | 0.50 | 0.50 | 0.01 | 0.01 | 0.47 | 0.52 | 1.00 | 6027.18 | 21701.41 |
| Parameter.3_%WITHIN%:.Y3 | 0.40 | 0.40 | 0.01 | 0.01 | 0.39 | 0.42 | 1.00 | 9403.61 | 28093.05 |
| Parameter.4_%WITHIN%:.Y4 | 0.38 | 0.38 | 0.01 | 0.01 | 0.36 | 0.40 | 1.00 | 13012.29 | 30204.25 |
| Parameter.5_%BETWEEN%:.MEAN.LAM1(equality/label) | 0.63 | 0.62 | 0.02 | 0.02 | 0.60 | 0.66 | 1.02 | 112.20 | 246.34 |
| Parameter.6_%BETWEEN%:.MEAN.LAM2(equality/label) | 0.81 | 0.81 | 0.02 | 0.02 | 0.78 | 0.85 | 1.02 | 105.27 | 233.98 |
| Parameter.7_%BETWEEN%:.MEAN.LAM3(equality/label) | 0.62 | 0.62 | 0.02 | 0.02 | 0.59 | 0.66 | 1.02 | 111.53 | 299.16 |
| Parameter.8_%BETWEEN%:.MEAN.LAM4(equality/label) | 0.72 | 0.72 | 0.02 | 0.02 | 0.68 | 0.76 | 1.01 | 168.93 | 498.67 |
| Parameter.9_%BETWEEN%:.MEAN.Y1 | 0.02 | 0.02 | 0.02 | 0.02 | -0.01 | 0.05 | 1.02 | 144.79 | 497.37 |
| Parameter.10_%BETWEEN%:.MEAN.Y2 | 0.31 | 0.31 | 0.02 | 0.02 | 0.27 | 0.34 | 1.01 | 158.31 | 441.26 |
| Parameter.11_%BETWEEN%:.MEAN.Y3 | -0.49 | -0.49 | 0.02 | 0.02 | -0.53 | -0.45 | 1.01 | 322.34 | 1294.41 |
| Parameter.12_%BETWEEN%:.MEAN.Y4 | 0.20 | 0.20 | 0.02 | 0.02 | 0.17 | 0.24 | 1.01 | 233.03 | 785.25 |
| Parameter.13_%BETWEEN%:.FB | 0.14 | 0.14 | 0.02 | 0.02 | 0.11 | 0.18 | 1.00 | 458.60 | 1891.12 |
| Parameter.14_%BETWEEN%:.LAM1 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.01 | 1.00 | 309.96 | 727.62 |
| Parameter.15_%BETWEEN%:.LAM2 | 0.01 | 0.00 | 0.00 | 0.00 | 0.00 | 0.01 | 1.01 | 266.85 | 846.06 |
| Parameter.16_%BETWEEN%:.LAM3 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.01 | 1.01 | 425.55 | 1339.94 |
| Parameter.17_%BETWEEN%:.LAM4 | 0.06 | 0.06 | 0.01 | 0.01 | 0.05 | 0.08 | 1.00 | 635.49 | 2543.96 |
| Parameter.18_%BETWEEN%:.LOGPSIW.WITH.FB | -0.26 | -0.26 | 0.03 | 0.03 | -0.32 | -0.21 | 1.00 | 889.12 | 3440.92 |
| Parameter.19_%BETWEEN%:.LOGPSIW | 0.89 | 0.89 | 0.10 | 0.09 | 0.74 | 1.06 | 1.01 | 732.80 | 2335.54 |
| Parameter.20_%BETWEEN%:.Y1.WITH.LAM1 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 1.02 | 207.84 | 456.11 |
| Parameter.21_%BETWEEN%:.Y1 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.01 | 1.02 | 141.58 | 222.45 |
| Parameter.22_%BETWEEN%:.Y2.WITH.LAM2 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.01 | 1.02 | 214.27 | 656.68 |
| Parameter.23_%BETWEEN%:.Y2 | 0.01 | 0.01 | 0.00 | 0.00 | 0.00 | 0.01 | 1.02 | 174.99 | 206.09 |
| Parameter.24_%BETWEEN%:.Y3.WITH.LAM3 | 0.00 | 0.00 | 0.00 | 0.00 | -0.01 | 0.01 | 1.00 | 704.85 | 1721.15 |
| Parameter.25_%BETWEEN%:.Y3 | 0.11 | 0.11 | 0.01 | 0.01 | 0.09 | 0.12 | 1.00 | 16930.53 | 31074.11 |
| Parameter.26_%BETWEEN%:.Y4.WITH.LAM4 | -0.01 | -0.01 | 0.01 | 0.01 | -0.02 | 0.00 | 1.00 | 4120.75 | 11428.62 |
| Parameter.27_%BETWEEN%:.Y4 | 0.06 | 0.06 | 0.01 | 0.01 | 0.05 | 0.07 | 1.00 | 7310.45 | 13462.19 |
Using STAN
We will use a similar strategy as in Part II to efficiently sample from this model. The key is to recognize that constructing the full covariance matrix for all clusters in a straightforward way can be computationally expensive.
Instead, we can exploit the conditional structure. If we condition on the random loadings \(\boldsymbol{\mathbf{\Lambda}}_j\) and the random variance \(\psi^w_j\), the problem reduces to a more standard normal model which allows for sufficient statistics to be used. The Stan code below implements this by integrating the appropriate likelihoods.
//
// This Stan program defines a one-factor two-level CFA model with a
// marginal likelihood approach similar to one described in
// https://ecmerkle.github.io/blavaan/articles/multilevel.html.
//
// Define function for computing multi-normal log density using sufficient
// statistics
functions {
real multi_normal_suff_lpdf(matrix S,
matrix Sigma,
int n) {
return -0.5 * (n * (log_determinant_spd(Sigma) + cols(S) * log(2 * pi())) +
trace(mdivide_left_spd(Sigma, S)));
}
}
// The input data are (a) sw, cluster-specific within-cluster cross-product
// matrix, (b) ybar, vectors of cluster means, and (c) n, cluster sample
// size. It assumes complete data.
data {
int<lower=1> p; // number of items
int<lower=1> J; // number of clusters
array[J] int<lower=1> n; // cluster size
array[J] matrix[p, p] sw; // within cross-product matrices
array[J] vector[p] ybar; // cluster means
}
// The parameters accepted by the model. Our model
// accepts four parameters:
// 'lambda_star' for unscaled loadings, 'nu' for intercepts,
// 'thetaw' and 'thetab' for uniqueness at the within and the between levels,
// and 'psib' for latent factor SD
parameters {
vector[p] mu_lambda_star; // unscaled mean loading vector assuming cross-level
// invariance
vector<lower=0>[p] thetaw; // within-cluster uniqueness vector
// vector<lower=0>[p] thetab; // between-cluster uniqueness vector
array[p] cholesky_factor_corr[2] Lthetab;
matrix<lower=0>[p, 2] sd_thetab; // SD of (dlambda, eps^b) for p items
cholesky_factor_corr[2] Lphi; // cholesky of correlation matrix of (logvar, mean)
real<lower=0> sd_logpsiw; // SD of log(SD) of etaw
real<lower=0> psib; // between-cluster latent standard deviation
row_vector[J] z_logpsiw; // scaled value of log(SD) of etaw
vector[p] nu; // item means/intercepts
matrix[p, J] z_dlambda; // scaled value of deviation from mean loading
}
// The model to be estimated. We compute the log-likelihood as the sum of
// two components: cluster-mean centered variables are multivariate normal
// with covariance matrix sigmaw (and degree of freedom adjustment),
// and cluster-mean variables are multivariate normal with covariance
// matrix sigmab + sigmaw / nj
model {
matrix[p, J] lambda = rep_matrix(mu_lambda_star, J) +
diag_pre_multiply(sd_thetab[, 1], z_dlambda);
row_vector[J] psiw = exp(sd_logpsiw .* z_logpsiw);
// Conditional value of thetab given dlambda;
matrix[p, 2] cond_thetab;
for (i in 1:p) {
Lthetab[i] ~ lkj_corr_cholesky(2);
cond_thetab[i, ] = Lthetab[i][2, ];
}
// Conditional mean given log(SD) of etaw and dlambda
matrix[p, J] mu = diag_post_multiply(lambda,
(psib * Lphi[2, 1]) .* z_logpsiw) +
diag_pre_multiply(sd_thetab[, 2] .* cond_thetab[, 1],
z_dlambda);
mu_lambda_star ~ normal(0, 10);
thetaw ~ gamma(1, .5);
// thetab ~ gamma(1, .5);
psib ~ gamma(1, .5);
nu ~ normal(0, 32);
z_logpsiw ~ std_normal();
to_vector(z_dlambda) ~ std_normal();
sd_logpsiw ~ gamma(1, 0.5);
Lphi ~ lkj_corr_cholesky(2);
for (j in 1:J) {
// The following becomes cluster-specific
matrix[p, p] LLt = lambda[, j] * lambda[, j]';
matrix[p, p] sigmab = add_diag(square(psib * Lphi[2, 2]) .* LLt,
square(sd_thetab[, 2] .* cond_thetab[, 2]));
matrix[p, p] sigmawj = add_diag(square(psiw[j]) .* LLt, square(thetaw));
target += multi_normal_suff_lpdf(sw[j] | sigmawj, n[j] - 1);
ybar[j] ~ multi_normal(nu + mu[, j], sigmab + sigmawj / n[j]);
}
}
// Rotating the factor loadings
// (see https://discourse.mc-stan.org/t/latent-factor-loadings/1483)
generated quantities {
vector[p] mu_lambda; // final loading vector
real cor_logpsiw_etab;
{
int sign_l1 = mu_lambda_star[1] > 0 ? 1 : -1;
mu_lambda = sign_l1 * mu_lambda_star;
cor_logpsiw_etab = sign_l1 * Lphi[2, 1];
}
}# Prepare data for STAN
yc <- sweep(dat[1:4], MARGIN = 2, STATS = colMeans(dat[1:4])) # centered data
nj <- table(dat$id)
# Sufficient statistics
sswj <- tapply(yc, INDEX = dat$id, FUN = \(x) tcrossprod(t(x) - colMeans(x)))
ybarj <- tapply(dat[1:4], INDEX = dat$id, FUN = \(x) colMeans(x))# Run STAN
mcfa_randload_fit <- mcfa_randload$sample(
data = list(
p = 4,
J = num_clus,
n = nj,
sw = sswj,
ybar = ybarj
),
chains = 3,
parallel_chains = 3,
iter_warmup = 500,
iter_sampling = 1000,
refresh = 500
)Running MCMC with 3 parallel chains...
Chain 1 Iteration: 1 / 1500 [ 0%] (Warmup)
Chain 2 Iteration: 1 / 1500 [ 0%] (Warmup)
Chain 3 Iteration: 1 / 1500 [ 0%] (Warmup)
Chain 3 Iteration: 500 / 1500 [ 33%] (Warmup)
Chain 3 Iteration: 501 / 1500 [ 33%] (Sampling)
Chain 1 Iteration: 500 / 1500 [ 33%] (Warmup)
Chain 1 Iteration: 501 / 1500 [ 33%] (Sampling)
Chain 2 Iteration: 500 / 1500 [ 33%] (Warmup)
Chain 2 Iteration: 501 / 1500 [ 33%] (Sampling)
Chain 3 Iteration: 1000 / 1500 [ 66%] (Sampling)
Chain 1 Iteration: 1000 / 1500 [ 66%] (Sampling)
Chain 2 Iteration: 1000 / 1500 [ 66%] (Sampling)
Chain 3 Iteration: 1500 / 1500 [100%] (Sampling)
Chain 3 finished in 313.8 seconds.
Chain 1 Iteration: 1500 / 1500 [100%] (Sampling)
Chain 1 finished in 327.9 seconds.
Chain 2 Iteration: 1500 / 1500 [100%] (Sampling)
Chain 2 finished in 336.2 seconds.
All 3 chains finished successfully.
Mean chain execution time: 326.0 seconds.
Total execution time: 347.5 seconds.
mcfa_randload_fit$summary(
c(
"mu_lambda",
"thetaw",
"sd_thetab",
"sd_logpsiw",
"psib",
"cor_logpsiw_etab"
)
) |>
knitr::kable(digits = 2)| variable | mean | median | sd | mad | q5 | q95 | rhat | ess_bulk | ess_tail |
|---|---|---|---|---|---|---|---|---|---|
| mu_lambda[1] | 0.62 | 0.62 | 0.02 | 0.02 | 0.59 | 0.65 | 1.00 | 722.18 | 1138.66 |
| mu_lambda[2] | 0.80 | 0.80 | 0.02 | 0.02 | 0.77 | 0.84 | 1.00 | 655.19 | 1059.48 |
| mu_lambda[3] | 0.62 | 0.62 | 0.02 | 0.02 | 0.59 | 0.65 | 1.00 | 699.90 | 1243.34 |
| mu_lambda[4] | 0.71 | 0.71 | 0.02 | 0.02 | 0.67 | 0.74 | 1.00 | 687.53 | 1048.89 |
| thetaw[1] | 0.73 | 0.73 | 0.01 | 0.01 | 0.71 | 0.74 | 1.00 | 3629.56 | 2275.06 |
| thetaw[2] | 0.71 | 0.71 | 0.01 | 0.01 | 0.69 | 0.73 | 1.00 | 3804.96 | 1666.89 |
| thetaw[3] | 0.64 | 0.64 | 0.01 | 0.01 | 0.62 | 0.65 | 1.00 | 3833.02 | 2167.82 |
| thetaw[4] | 0.62 | 0.62 | 0.01 | 0.01 | 0.60 | 0.63 | 1.01 | 2952.01 | 2159.50 |
| sd_thetab[1,1] | 0.05 | 0.05 | 0.02 | 0.02 | 0.01 | 0.08 | 1.01 | 362.74 | 846.65 |
| sd_thetab[2,1] | 0.04 | 0.04 | 0.02 | 0.03 | 0.00 | 0.08 | 1.01 | 384.40 | 637.47 |
| sd_thetab[3,1] | 0.02 | 0.02 | 0.02 | 0.01 | 0.00 | 0.05 | 1.00 | 461.68 | 961.37 |
| sd_thetab[4,1] | 0.24 | 0.24 | 0.02 | 0.02 | 0.21 | 0.27 | 1.00 | 858.77 | 1654.67 |
| sd_thetab[1,2] | 0.03 | 0.03 | 0.02 | 0.02 | 0.00 | 0.07 | 1.00 | 1502.14 | 1313.26 |
| sd_thetab[2,2] | 0.05 | 0.05 | 0.03 | 0.03 | 0.00 | 0.10 | 1.00 | 1162.27 | 1513.45 |
| sd_thetab[3,2] | 0.32 | 0.32 | 0.02 | 0.02 | 0.30 | 0.35 | 1.00 | 4011.86 | 2062.37 |
| sd_thetab[4,2] | 0.23 | 0.23 | 0.02 | 0.02 | 0.21 | 0.26 | 1.00 | 4040.65 | 2309.13 |
| sd_logpsiw | 0.47 | 0.47 | 0.03 | 0.03 | 0.43 | 0.51 | 1.00 | 1235.37 | 1782.72 |
| psib | 0.39 | 0.39 | 0.03 | 0.03 | 0.34 | 0.43 | 1.00 | 1585.03 | 1892.98 |
| cor_logpsiw_etab | -0.72 | -0.72 | 0.06 | 0.06 | -0.81 | -0.61 | 1.00 | 1488.01 | 1947.50 |
data.frame(
model = c("Mplus (Gibbs)", "STAN"),
ess = c(
posterior::summarise_draws(mplus_draws, "ess_bulk")[[2]][21],
mcfa_randload_fit$summary(
variables = "sd_thetab[4,1]",
posterior::ess_bulk
)[[2]]
),
time = c(
mplus_sec,
mcfa_randload_fit$time()$total
)
) |>
knitr::kable(
digits = 2,
col.names = c(
"Model",
"ESS (Bulk) for the variance/SD of $b^\\lambda_4$",
"Time (seconds)"
)
)| Model | ESS (Bulk) for the variance/SD of \(b^\lambda_4\) | Time (seconds) |
|---|---|---|
| Mplus (Gibbs) | 141.58 | 1152.0 |
| STAN | 858.77 | 347.5 |
The sampling is much more efficient for STAN than for Mplus in this model, as the ESS is larger with the STAN approach for most parameters.
sessioninfo::session_info()─ Session info ───────────────────────────────────────────────────────────────
setting value
version R version 4.5.2 (2025-10-31)
os Ubuntu 24.04.4 LTS
system x86_64, linux-gnu
ui X11
language (EN)
collate C.UTF-8
ctype C.UTF-8
tz Asia/Macau
date 2026-02-13
pandoc 3.6.3 @ /home/marklai/.positron-server/bin/4b472e9bf40ca88f886fe9c7fcfbb9ee702fccd0/quarto/bin/tools/x86_64/ (via rmarkdown)
quarto 1.8.27 @ /home/marklai/.positron-server/bin/4b472e9bf40ca88f886fe9c7fcfbb9ee702fccd0/quarto/bin/quarto
─ Packages ───────────────────────────────────────────────────────────────────
package * version date (UTC) lib source
abind 1.4-8 2024-09-12 [1] RSPM (R 4.5.0)
backports 1.5.0 2024-05-23 [1] RSPM (R 4.5.0)
boot 1.3-32 2025-08-29 [4] CRAN (R 4.5.2)
checkmate 2.3.4 2026-02-03 [1] RSPM (R 4.5.0)
cli 3.6.5 2025-04-23 [1] RSPM (R 4.5.0)
cmdstanr * 0.9.0 2025-03-30 [1] https://stan-dev.r-universe.dev (R 4.5.2)
coda 0.19-4.1 2024-01-31 [1] RSPM (R 4.5.0)
data.table 1.18.2.1 2026-01-27 [1] RSPM (R 4.5.0)
digest 0.6.39 2025-11-19 [1] RSPM (R 4.5.0)
distributional 0.6.0 2026-01-14 [1] RSPM (R 4.5.0)
dplyr 1.2.0 2026-02-03 [1] RSPM (R 4.5.0)
evaluate 1.0.5 2025-08-27 [1] RSPM (R 4.5.0)
farver 2.1.2 2024-05-13 [1] RSPM (R 4.5.0)
fastDummies 1.7.5 2025-01-20 [1] RSPM (R 4.5.0)
fastmap 1.2.0 2024-05-15 [1] RSPM (R 4.5.0)
generics 0.1.4 2025-05-09 [1] RSPM (R 4.5.0)
ggplot2 4.0.2 2026-02-03 [1] RSPM (R 4.5.0)
glue 1.8.0 2024-09-30 [1] RSPM (R 4.5.0)
gsubfn 0.7 2018-03-16 [1] RSPM (R 4.5.0)
gtable 0.3.6 2024-10-25 [1] RSPM (R 4.5.0)
htmltools 0.5.9 2025-12-04 [1] RSPM (R 4.5.0)
htmlwidgets 1.6.4 2023-12-06 [1] RSPM (R 4.5.0)
httr 1.4.7 2023-08-15 [1] RSPM (R 4.5.0)
jsonlite 2.0.0 2025-03-27 [1] RSPM (R 4.5.0)
knitr 1.51 2025-12-20 [1] RSPM (R 4.5.0)
lattice 0.22-7 2025-04-02 [4] CRAN (R 4.5.2)
lifecycle 1.0.5 2026-01-08 [1] RSPM (R 4.5.0)
magrittr 2.0.4 2025-09-12 [1] RSPM (R 4.5.0)
matrixStats 1.5.0 2025-01-07 [1] RSPM (R 4.5.0)
MplusAutomation * 1.2 2025-09-02 [1] RSPM (R 4.5.0)
otel 0.2.0 2025-08-29 [1] RSPM (R 4.5.0)
pander 0.6.6 2025-03-01 [1] RSPM (R 4.5.0)
pillar 1.11.1 2025-09-17 [1] RSPM (R 4.5.0)
pkgconfig 2.0.3 2019-09-22 [1] RSPM (R 4.5.0)
plyr 1.8.9 2023-10-02 [1] RSPM (R 4.5.0)
posterior * 1.6.1.9000 2026-02-01 [1] https://stan-dev.r-universe.dev (R 4.5.2)
processx 3.8.6 2025-02-21 [1] RSPM (R 4.5.0)
proto 1.0.0 2016-10-29 [1] RSPM (R 4.5.0)
ps 1.9.1 2025-04-12 [1] RSPM (R 4.5.0)
R6 2.6.1 2025-02-15 [1] RSPM (R 4.5.0)
RColorBrewer 1.1-3 2022-04-03 [1] RSPM (R 4.5.0)
Rcpp 1.1.1 2026-01-10 [1] RSPM (R 4.5.0)
rlang 1.1.7 2026-01-09 [1] RSPM (R 4.5.0)
rmarkdown 2.30 2025-09-28 [1] RSPM (R 4.5.0)
S7 0.2.1 2025-11-14 [1] RSPM (R 4.5.0)
scales 1.4.0 2025-04-24 [1] RSPM (R 4.5.0)
sessioninfo 1.2.3 2025-02-05 [1] RSPM (R 4.5.0)
tensorA 0.36.2.1 2023-12-13 [1] RSPM (R 4.5.0)
texreg 1.39.5 2025-12-22 [1] RSPM (R 4.5.0)
tibble 3.3.1 2026-01-11 [1] RSPM (R 4.5.0)
tidyselect 1.2.1 2024-03-11 [1] RSPM (R 4.5.0)
vctrs 0.7.1 2026-01-23 [1] RSPM (R 4.5.0)
withr 3.0.2 2024-10-28 [1] RSPM (R 4.5.0)
xfun 0.56 2026-01-18 [1] RSPM (R 4.5.0)
xtable 1.8-4 2019-04-21 [1] RSPM (R 4.5.0)
yaml 2.3.12 2025-12-10 [1] RSPM (R 4.5.0)
[1] /home/marklai/R/x86_64-pc-linux-gnu-library/4.5
[2] /usr/local/lib/R/site-library
[3] /usr/lib/R/site-library
[4] /usr/lib/R/library
* ── Packages attached to the search path.
──────────────────────────────────────────────────────────────────────────────