INLAvaan 0.3.0
New features
loo() computes leave-one-out cross-validation from a
single fit without refitting nor sampling, via a Taylor approximation of
the case-deletion posterior: per-subject (LOSO) for single-level models,
per-cluster (LOCO) for two-level models. Reports first- and second-order
estimates and pointwise contributions, with opt-in parallelism
(cores) and theta/Sigma overrides
for scoring conditioned posterior summaries in user-built model-search
workflows.
waic() computes the widely applicable information
criterion from posterior draws, with pointwise contributions and
reliability warnings.
- Both criteria score fits with exogenous covariates on the likelihood
they were fitted with: jointly with the covariates
(
fixed.x = FALSE) or conditionally on them
(fixed.x = TRUE, the lavaan default; exact, no additional
approximation), for any covariate placement, including cluster-level and
within-level covariates in two-level models. The two flavours are never
comparable, as conditional comparisons may differ in their covariate
sets, which enables covariate selection.
loo() and waic() support multigroup
models. Groups are independent, so each unit is scored against its own
group’s implied moments, under either mean treatment and either
covariate flavour, with cross-group equality constraints
(group.equal) flowing through automatically. Units are
identified by case number and carry a group column, so
results keep their identity across fits that stack groups differently.
Multigroup two-level models are not supported yet.
loo() and waic() support fits estimated by
full-information maximum likelihood (missing = "ml").
Single-level units are scored on the entries they actually have – the
observed-data predictive log p(y_i,obs | D_-i) – with
casewise kernels evaluated per missing pattern, so a unit with fewer
observed entries self-weights in the elpd. Two-level fits are scored per
cluster (LOCO), each cluster on its observed-data marginal likelihood
via lavaan’s raw-data cluster kernels (no per-cluster sufficient
statistics, since LOCO deletes whole clusters). This shares the
missing-at-random assumption of the FIML fit itself. Multigroup
two-level models remain unsupported under missingness.
- On two-level models
loo() and waic() gain
type = "loso", scoring the conditional predictive
(leave-one-unit-out: a new observation within an observed cluster)
instead of the default marginal predictive
(type = "loco", leave-one-cluster-out: a new cluster).
These are the two estimands of Merkle, Furr & Rabe-Hesketh (2019);
they answer different questions and are easily conflated, so the
marginal is the default and the conditional warns. loo()
uses the Taylor expansion and waic() the posterior draws,
computing the same estimand two ways; both work with and without missing
data. (waic() previously had no type.)
fitmeasures() gains elpd_loo,
se_loo, p_loo, looic and
elpd_waic, se_waic, p_waic,
waic: included in "all" when stored with the
fit, computed on demand when requested by name.
compare() gains loo = TRUE. Models sorted
by descending ELPD, with p_loo and ELPD differences with
paired standard errors (mixed-flavour comparisons are refused). Pairing
matches units by id rather than row order, so a pooled fit can be
compared against a multigroup fit of the same data, and the
measurement-invariance ladder (configural, metric, scalar) is compared
on a proper predictive scale.
- Both criteria can be computed at fit time and stored with the fit.
The default
test = "standard" does so automatically for
supported models with a mean structure. The WAIC reuses the fit’s own
posterior draws (when nsamp >= 100), and the LOO runs
when its predicted serial cost is within a 10-second budget.
test = "loo" forces the LOO regardless of the budget,
test = "none" skips everything, and
fit <- add_loo(fit) stores it post hoc. Stored results
are reused by loo(), waic(),
fitmeasures(), and compare().
fitted() (and fitted.values()) return the
model-implied moments of an INLAvaan fit, evaluated at the
posterior means, matching the lavaan and blavaan output structure.
type = "ov" gives casewise predicted values.
predict() gains a summary argument;
summary = TRUE collapses the posterior draws and returns
point estimates directly, equivalent to
summary(predict(...)) in one call. Default
FALSE, so existing code is unaffected.
residuals() (and resid()) return the
observed-minus-fitted moments of an INLAvaan fit, matching
the lavaan and blavaan output structure and supporting all lavaan
residual types (raw, cor,
cor.bentler, normalized,
standardized) plus type = "casewise".
anova() on an INLAvaan fit now errors,
pointing to compare(). Unlike
fitted()/residuals()/predict(),
this is a deliberate departure from blavaan (which silently inherits
lavaan’s frequentist likelihood-ratio test): there is no direct Bayesian
analogue of that test, and compare() already provides the
appropriate tools (Bayes factors, DIC/pD, LOO/WAIC).
logLik() returns the Laplace-approximated marginal
log-likelihood (log evidence) by default, printed with a note that it is
not comparable to a classical log-likelihood;
type = "plugin" instead returns the classical
log-likelihood at the posterior mean, with
df/nobs attributes so it supports
AIC()/BIC() at the point estimate.
deviance() is new for INLAvaan fits
(lavaan has no deviance() at all). Follows the
BUGS/JAGS/Stan convention: type = "mean" (default) returns
the posterior mean deviance with pD/DIC
attached as attributes; type = "plugin" returns the
deviance at the posterior mean (matching
-2 * logLik(type = "plugin")). Both require
test != "none".
AIC()/BIC() on an INLAvaan
fit now error, documented alongside logLik(). Both are
large-sample asymptotic approximations to quantities INLAvaan already
computes directly – AIC approximates predictive accuracy
(loo()/waic()), BIC approximates
-2 * log(marginal likelihood) (logLik()) – so reporting
them at the posterior mean would be a cruder proxy for numbers already
available. The point estimate remains available for reporting-convention
purposes via AIC(logLik(object, type = "plugin")) /
BIC(...).
- Fits now self-check their diagnostics:
inlavaan() warns
once, at the end of the fit, if the optimiser did not converge, the
gradient at the reported mode is materially non-zero (Newton step >
0.1 posterior SD), a skew-normal marginal fits poorly (NMAD > 0.1),
the VB correction shifted a posterior mean by more than 1 posterior SD,
or the Hessian is near-singular – naming the offending parameters. A
healthy fit stays silent; suppress via the
inlavaan_diagnostics_warning condition class.
diagnostics() gains the scale-free
mode_shift_max (global) and mode_shift_sigma
(per-parameter) measures backing the gradient check. (#18)
Minor improvements and fixes
- Saturated-means fast path: when the mean structure is saturated (all
intercepts free and unconstrained with normal priors, no nonzero latent
means), the posterior is exactly block-diagonal between the intercepts
and the covariance parameters at the mode. The Hessian intercept block
is now computed analytically with an exact zero cross block (finite
differences run over the covariance columns only), and the skew-normal
marginal scans skip the intercept axes, emitting their exact Gaussian
marginals directly. About 25% faster on typical CFA/SEM fits, with
results identical to within finite-difference noise.
- Improved messaging for
inlavaan() fit calls.
Bug fixes
standardisedsolution() and
summary(standardized = TRUE) no longer silently drop their
arguments under lavaan >= 0.7-1, which renamed several exported
arguments (e.g. cov.std to cov_std,
GLIST to glist). INLAvaan now resolves the
spelling the installed lavaan expects once per session at load, working
across lavaan versions.
- Two-level FIML
loo()/waic() scores are now
correct for clusters containing a case fully missing on the within-level
variables. lavaan retains such cases but its analytic gradient kernel
mishandles the zero-observed pattern; INLAvaan drops these rows before
the cluster kernels (exact for the marginal likelihood). Two-level FIML
fitting also inherits the upstream gradient issue (fixed in lavaan PR
#581), so inlavaan() warns when such cases are present on
lavaan versions before the fix.
- Models fitted with
meanstructure = FALSE now use a
proper Bayesian likelihood. See “Mean structures” vignette for details,
including when model comparisons across the two mean treatments are
meaningful.
- The saturated means are given flat priors and marginalised
analytically (closed form), replacing lavaan’s profiled likelihood,
which is not a valid Bayesian object.
- Posterior modes recalibrate by the factor n/(n-1) on the covariance
side.
loo() and waic() score such fits on the
exact exchangeable case-deletion conditionals. The previous zero-mean
fallback and its warning are gone, and absolute ELPD values are
meaningful and comparable with meanstructure = TRUE
fits.
- Posterior predictive draws include the saturated means and their
mean-uncertainty.
- Requesting
meanstructure = FALSE for a two-level model
now warns and fits with meanstructure = TRUE (the mean
structure is required there).
- The conditional (
fixed.x = TRUE) flavour — the default
for SEM with exogenous covariates — is fully supported: the mean
marginalisation factorises blockwise, so each unit is scored by the
difference of two exchangeable conditionals, with the frozen-covariate
term entering as an exact constant.
predict() now centres the conditioning data on the
model-implied means (or the saturated sample means when the model has no
mean structure) when drawing factor scores and predicted observed
variables. Previously the kernels conditioned on raw data, offsetting
every factor score by a constant that grows with the variable
means.
sampling() and simulate() draws of
observed variables from models without a mean structure now include the
saturated (sample) means, so posterior predictive replicates live on the
data scale instead of being centred at zero.
sampling() and simulate() no longer error
for models with a single latent variable, and their saturated-mean
recovery is now robust to missing data (replicate columns were
previously NA under
missing = "pairwise").
- The PPP’s observed discrepancy now uses the unbiased (divisor n-1)
sample covariance, matching the scale of the Wishart-replicated
covariances it is compared against; previously the divisor-n form made
the PPP very slightly optimistic (an O(1/n) effect, all models).
coef() (and the merged parameter table, fitted values,
and implied moments) now reports covariance parameters on the covariance
scale. Previously these slots carried the posterior-mean
correlation, while summary() showed the correct
sample-based covariance; the discrepancy is visible whenever the
relevant standard deviations are far from 1.
INLAvaan 0.2.5
Minor improvements and
fixes
- INLAvaan now works with both the current lavaan 0.6 series and the
upcoming lavaan 0.7, which renames many of its internal functions. The
lavaan internals INLAvaan relies on are now resolved when the package
loads, under whichever naming scheme is available. lavaan (>= 0.6-19)
is now declared explicitly, and the package is checked against the
oldest supported, current CRAN, and development versions of lavaan on
CI.
- Fixed the trapezoid rule used by
compare_mcmc() for
density normalisation, overlap, and KL divergence computations.
compare_mcmc() and diagnostics() are now
robust to NA values in density and diagnostic
computations.
- The
dp argument of inlavaan() and friends
is now documented in terms of priors_for().
INLAvaan 0.2.4
New features
bfit_indices() computes per-sample Bayesian fit index
vectors (BRMSEA, BCFI, BTLI, BNFI), with summary() and
print() methods. Summary statistics are also available via
fitmeasures().
compare() compares two or more fitted models side by
side, reporting marginal log-likelihood, Bayes factors, and DIC, with
optional fit measures from fitmeasures().
diagnostics() computes global and per-parameter
convergence and approximation-quality diagnostics for fitted
models.
get_inlavaan_internal() is now exported and documented,
providing access to the internal list stored in a fitted
INLAvaan object.
predict() generates predictions for observed data and
missing data imputation, respecting multilevel structure if
present.
sampling() draws from the posterior (or prior) SEM
generative model, returning parameter vectors, latent variables, or
observed variables.
simulate() generates complete replicate datasets from a
fitted model, useful for simulation-based calibration and posterior
predictive checks.
timing() extracts wall-clock timings for individual
computation stages of a fitted model.
Minor improvements and
fixes
- Cholesky factorisation of the precision matrix replaces raw
solve() for covariance and log-determinant
calculations.
- Copula sampling with NORTA (NORmal To Anything) correlation
adjustment is now the default (
samp_copula = TRUE),
ensuring posterior samples have correct skew-normal marginals and
correct Pearson correlations.
- Pre-computed Owen-scrambled Sobol sequences are used by default,
with fallback to
{qrng} for larger sequences. QMC sample
size now scales with model dimension.
- Skew-normal fitting now runs in parallel automatically when the
number of marginals exceeds 120, using all available cores.
- Small optimisations to the skew-normal volume correction.
acfa(), asem(), and agrowth()
gain a vb_correction argument.
{ggplot2} is now optional; plots fall back to base R
graphics when it is not installed.
inlavaan() gains an sn_fit_ngrid argument
to control the number of grid points per dimension when fitting
skew-normal marginals (default 21).
inlavaan() now supports
sn_fit_sample = TRUE for defined parameters, fitting a
skew-normal approximation to their posterior marginals based on drawn
samples.
plot() method gains improved visualisation
options.
priors_for() now supports the [prec] scale
qualifier for variance parameters (theta,
psi), placing the prior on the precision scale with
automatic Jacobian adjustment.
sampling() and simulate() gain a
silent argument to suppress informational messages.
summary() now includes 25th and 75th percentile
columns.
vcov() now returns the covariance matrix of the
lavaan-side parameters and supports a type argument for
choosing between sample and Laplace covariance.
Bug fixes
marginal_correction = "shortcut" no longer produces
incorrect volume corrections.
qsnorm_fast() no longer incorrectly handles sign
symmetries.
INLAvaan 0.2.3
- Improved axis scanning, skewness correction, and VB mean correction
routine.
- Bug fixes for CRAN.
- Updated README example.
INLAvaan 0.2.2
- Under the hood, use lavaan’s MVN log-likelihood function to compute
single- and multi-level log-likelihoods.
- Added support for multi-level SEM models.
- Added support for binary data using PML estimator from lavaan. NOTE:
Ordinal is possible in theory, but the package still lacks proper prior
support for the thresholds.
- Added support for
missing = "ML" to handle FIML for
missing data.
INLAvaan 0.2.1
- Support for lavaan 0.6-21.
- Implemented variational Bayes mean correction for posterior
marginals.
- Defined parameters are now available, e.g. mediation analysis.
- Prepare for CRAN release.
INLAvaan 0.2
- INLAvaan has been rewritten from the ground up specifically for SEM
models. The new version does not call R-INLA directly, but instead uses
the core approximation ideas to fit SEM models more efficiently.
- Features are restricted to normal likelihoods only
and continuous observations for now.
- Support for most models that lavaan/blavaan can fit, including CFA,
SEM, and growth curve models.
- Support for multigroup analysis.
- Added PPP and DIC model fit indices.
- Added prior specification for all model parameters.
- Added support for fixed values and parameter constraints.
- Initial CRAN submission.
INLAvaan 0.1
- Used
rgeneric functionality of R-INLA to implement a
basic SEM framework.