Test-portability release. No executable estimator or API behaviour changed, and no number a fit returns changed; 0.9.0 was rejected at CRAN's incoming checks and never reached the archive.
The frozen r1.1 regression test compared vbfa() output to its stored
reference with expect_identical(). That assertion is not portable: the
estimator runs through compiled BLAS/LAPACK, whose SIMD and FMA choices
differ between platforms, so a reference frozen under one BLAS reproduces
only to within an ulp or two under another. CRAN's Debian pre-test
disagreed in the sixteenth significant digit on four values and failed.
The test now asserts agreement at a strict relative tolerance = 1e-8,
which remains sensitive to material regression, and keeps the iteration
count exact, since a different stopping point means the path itself
changed. The frozen-reference output is no longer asserted bitwise; that
property depends on the build.
The pefa and bifactor vignettes replace their illustrative count rule
with a declared analysis-side reader: dual ELBO/BIC count paths, three named
minimum-congruence persistence profiles, and the companion study's L1/L2/L3
corroboration layering. On a real fitted path the deepest profile fails to
resolve while the other two agree, so the sensitivity to declared depth is
shown rather than asserted. Vignette prose and local code only.
?vbmimic no longer explains why pefa() refuses vbmimic fits in terms of
factor-number selection, which pefa() has not performed since 0.9.0. The
refusal itself is unchanged; only the stated reason was stale.
This is the sole change to R/ since 0.9.0 and it is comment-only: one
roxygen sentence rewritten, no executable line. src/ and data/ are
byte-identical to 0.9.0, so every number a fit returns is unchanged and
results produced under 0.9.0 reproduce exactly under 0.9.1 in the same
computational environment.
First public release. It was submitted to CRAN but not accepted, so it never
reached the archive; see 0.9.1. pefa() is now a measurement-only
factor-count sweep:
it fits every K in a fixed consecutive window, reports candidate and
between-candidate quantities, and chooses nothing. This completes an arc
running through 0.8.0 (removed the stable verdict), 0.8.1 (removed
absolute-fit gating), and 0.8.2 (removed automated raw/BIC selection). 0.8.3
was withdrawn and never reached CRAN.
Every structural number in a pefa() object changes, because the
column-correspondence rule changed. Congruence, pooled and maximum RMSD, ARI,
PIP RMSD, unmatched-column size, and the collision flag are not comparable
across 0.8.x and 0.9.0, and neither is any threshold calibrated on them.
The following existed in 0.8.3 and are gone. Removing them is intentional pre-1.0 breakage, not a deprecation cycle.
select_K_elbow() is removed, together with pefa()'s cuts, sustain,
stability_eps, $selected_K, and $boundary, and the boundary vocabulary
and console advice that went with them. A count rule is now ordinary analysis
code over the stored path, and it must say which variant it is:
## 20% cut on the ELBO path, full look-ahead, no boundary fallback.
g <- x$transitions$ELBO_gain_pct
ok <- vapply(seq_along(g), function(i) all(g[i:length(g)] < 20), logical(1))
if (any(ok)) x$transitions$K_from[which(ok)[1]] else NA_integer_
A capped-look-ahead variant and a variant with a boundary fallback give
different answers on the same path; vbpm no longer supplies either, so the
analysis names the one it used.
Soft criteria and their counts (t_S, AIC_S, BIC_S) and the nominal
count t_nom are no longer retained on $sweep. t is the effective hard
parameter count at the resolved tau, rank-adjusted when
rank_adjust = TRUE. An analysis that needs a soft criterion must refit;
fit_stats() still returns all of them for a single fit.
The per-candidate and per-pair status/reason taxonomy is removed. A fit
error, malformed candidate, or nonfinite loading, PIP, ELBO, AIC, BIC,
or t now aborts the whole call with an error naming the affected K, and
no partial object is returned. RMSEA, SRMR, CFI, and TLI stay
descriptive and may be NA without error.
Eligibility machinery is removed: there is no salience screen, no
stability_eps, no source/target eligibility count, no reduced-pair flag,
and no target capacity policy. Every column participates in every
comparison, so a comparison always holds exactly K_from scored pairs and
no pair-count field is needed.
s = 1 when a'b >= 0 and -1 otherwise, each source column takes the
target minimizing the squared length of a - s b. That is Equation 17 of
Chen (2023) made invariant to whole-column reflection, and it is
magnitude-sensitive on purpose. Absolute Tucker congruence is now only an
exact-tie breaker and a reported shape diagnostic; a finite congruence
outranks an undefined one, and the smaller target index settles anything
still tied. No tolerance widens a near-tie into a tie.phi_min becomes NA_real_ rather than silently
dropping the column."pefa" object now has exactly sweep, transitions, persistence,
loadings, pips, Q0, and settings, and its class is exactly "pefa".$sweep is K, ELBO, AIC, BIC, RMSEA, SRMR, CFI, TLI, t,
iter, converged — one row per candidate.$transitions is one row per adjacent pair: K_from, K_to,
ELBO_gain_pct, BIC_gain_pct, and the seven structural facts phi_min,
rmsd, rmsd_max, ari, pip_rmsd, unmatched_ssl, collision. Raw
gains are no longer duplicated here; the raw criteria are in $sweep. Each
percentage divides by the largest positive gain on its own path, or is
NA_real_ when that path has none.$persistence is now three upper-triangular matrices over the fitted K
values, named exactly phi, rmsd, and collision, covering every ordered
pair. phi holds phi_min and rmsd holds rmsd_max — not the pooled
$transitions$rmsd. They are read directly as x$persistence$phi. No
accessor function is provided: selecting a stored triangle would be a synonym
for $, and a two-metric accessor would arbitrarily omit the collision mask.$settings holds only resolved fit-generating and fit-statistic controls:
bifactor, general, v0, max_it, convChk, tolVal, tau,
rank_adjust, rank_max_J. No analysis rule, no threshold, no verbose.$provenance component and no hashing anywhere in the package.
pefa() computes no schema, engine, data, Q0, settings, evidence, or
lineage fingerprint, stores no build identity, and makes no cross-version
object-reuse promise. The package version and session used for an analysis
belong in that analysis's own run record. The digest dependency is dropped.pefa() over the whole wider window.max_it is retained with
converged = FALSE and keeps every matrix and every comparison it takes part
in. pefa() issues at most one aggregate warning per call, naming all
affected K values. Convergence never blanks a defined number and never
removes a candidate from the path.pefa(), ssl(), and the print(),
summary(), and plot() methods.ssl() is colSums(Lam^2) per candidate, returned as a character-K-keyed
list of double vectors, with the general column first in bifactor mode. It
screens nothing and enters no correspondence rule. It is the companion to
phi_min, which is a weakest-link statistic over every column: a near-empty
column matches the smallest-norm target, contributes an arbitrary congruence,
and often raises collision, and ssl() is what tells that case apart from
structure actually moving.summary() returns exactly window, sweep, transitions, persistence,
ssl, settings, and nonconverged_K. Its print method shows phi before
rmsd and suffixes a persistence value with * where the parallel collision
cell is TRUE; that is display formatting only and never touches the stored
matrices.plot() keeps the descriptive objective, gain, and fit views. Soft
criteria, threshold lines, and selection markers are gone, and a
one-candidate gain view is an empty panel rather than an error.pefa() imposes no upper bound on K relative to J. The Ledermann bound
orients the choice of Kmax but is derived for an unrestricted model and
does not constrain a regularized partially confirmatory candidate. Past it,
expect nonconvergence and NA fit indices — RMSEA and TLI are undefined
at non-positive degrees of freedom — and read both as evidence about the
window.v0, convChk, and tolVal are explicit pefa() arguments and ... is
gone: every control that changes a returned number is recorded in
$settings.K0 = 0) are supported.vignette("pefa") now walks one sweep table by table on a deliberate
overextraction example; vignette("bifactor") covers bifactor structures, AO
versus AZ backbones, a Step-2 comparison derived from the Step-1 result, and
an ordinary-versus-bifactor sweep illustration.This release makes the compact PEFA object easier to inspect and removes two uninformative PIP summaries. It is a breaking refinement of the 0.8.2 schema; the 0.8.2 release history below is unchanged.
$sweep is now an ordinary scalar-only data frame. Candidate loading and
PIP matrices move to top-level, K-named $loadings and $pips lists, so a
candidate is read directly as x$loadings[["4"]] or x$pips[["4"]].
Their names always equal as.character(x$sweep$K); full candidate fits are
still not retained.pip_sum and pip_product are removed from $transitions. Both thresholded
PIPs at .5 and were diluted by fixed design cells; the product also returned
zero whenever all changes ran in one direction. The replacement pip_rmsd
is continuous: after loading-column matching, it is the RMSD between PIPs
over matched cells that are selectable at both endpoints. Fixed backbone
cells and unmatched columns are excluded. Loading-based metrics remain
available when only a PIP endpoint is malformed.$transitions$collision flag reports when a larger-fit column is selected
more than once; it is not silently resolved by a one-to-one assignment.K + 1 matrices remain available for candidate-level inspection.
This supersedes the earlier blanket implementation warning: relative sweep
reliability is now assessed empirically rather than decided from model class.unmatched_max stays a separate continuous,
descriptive size diagnostic. It is not a loading-salience cutoff or an
automatic selection rule.K = 2:5 window. In this
reproducible example the ordinary sweep selects the true three groups at
both cuts, whereas the bifactor sweep selects its lower boundary. A derived
SS-loading table (colSums(Lam^2)) exposes weak columns without adding a
result field or selection rule; replicated R6 work will test whether this
example generalizes.summary() derives a per-candidate ssl component -- the sums of squared
loadings colSums(Lam^2) -- from $loadings at summary time, and its print
method displays one row per candidate. In bifactor mode the values cover
all K + 1 columns with the general factor first, unlike the group-block
transition diagnostics. SS loadings are descriptive column sizes, not Gram
eigenvalues, and enter no selection rule; the sweep object itself stores no
derived state.$sweep, $transitions, $loadings,
$pips, $selected_K, $boundary, $Q0, and $settings. The lean
$settings list contains only cuts, sustain, bifactor, general,
tau, stability_eps, and rank_adjust. The requested window is derived
from $sweep$K, and the backbone size from ncol(x$Q0).This release deliberately simplifies PEFA before 1.0 and changes both its object schema and its adjacent-solution diagnostics.
fit_stats.vbfa() now uses the deterministic nominal hard-selected
parameter count by default (rank_adjust = FALSE). The numerical
Jacobian-rank count remains available only by explicit request and requires
the suggested package numDeriv; rank_max_J is an explicit computational
guard. pefa() exposes and records the same controls, computing the chosen
statistics while each candidate fit still exists.pefa result now has exactly two data frames. $sweep retains scalar
candidate statistics and adds loading and pip matrix list-columns;
$transitions retains adjacent ELBO/BIC gains and the revised diagnostics.
Named $selected_K and parallel $boundary vectors report the ELBO-gain
result for every named cut. Raw ELBO/BIC and BIC-gain selections are no
longer returned automatically, but remain directly reproducible from the
retained scalar paths.$selection, and selected_fit() are removed. Extract
loading/PIP matrices by matching a desired K to $sweep$K; explicitly
refit that design when downstream work needs a complete vbfa object.Q0 columns fixed by
position, screens free columns by squared L2 norm, and matches the retained
free columns by minimum SSE. The transition fields are now rmsd,
rmsd_max, signed phi_min, ari, pip_sum, pip_product, and the
separate size diagnostic max_unmatched_loading; the previous phi_mean
and ambiguous max_loading fields are removed.max_unmatched_loading is not a .3 salient-loading rule. Bifactor edges,
failed endpoints, and inapplicable or incomplete comparisons return typed
NA diagnostics.extend.pefa(), vblvm(), MIMIC sweeping, ESEM
support, or a generic sweep engine.pefa() is reduced to a compact, in-memory factor-number sweep. This is a
breaking cleanup of the 0.8.0 interface; the 0.8.0 release history below is
left unchanged.
bifactor = TRUE mode remains,
with K consistently denoting the number of group factors. Standalone
orthogonal and local-dependence models remain available through vbfa() but
are no longer PEFA sweep modes.$sweep
as a descriptive fit statistic but is not automated as a gain rule.
select_K_elbow() and pefa() now use the same full sustained-drop scan: a
stop requires all sustain following gains, including near the right edge.$sweep, edge-level
$transitions, and rule-level $selection. Raw adjacent ELBO and BIC gains
are stored with their transitions, while each rule records its own gain
normalizer and threshold.$fits, and selected_fit() retrieves
the primary selected fit. Removed are keep_fits, duplicate selected-fit
fields, compatibility aliases and schema fields, the secondary
vbpm_sweep class, and transition_detail().delta, fit_cut,
cutoffs, orthogonal, and keep_fits arguments. Named cuts remains the
only multi-rule interface; PEFA rejects local-dependence and orthogonal mode
controls passed through ....phi_min, phi_mean, ari, rmsd, and the unmatched column's
max_loading. There is no assignment search, tie policy, fixed stability
cutoff, status vocabulary, or public matching detail.pefa() has no persistence, checkpoint, resume, interrupt, or overlap-reuse
subsystem. A boundary selection asks for a complete rerun with a better
bracketed window.$sweep AIC/BIC (and
potentially a BIC selection) can differ from a 0.8.0 run that silently used
the optional Jacobian rank adjustment, and the new column alignment changes
every stability column relative to 0.8.0's matcher.special_effects(), and a
two-step first-order-count then matched BIC comparison.pefa() is reorganized around what each quantity is indexed by, and gains
adjacent-count stability diagnostics. This release changes results as well
as structure -- see the two breaking notes below.
$sweep keeps one row per candidate (ELBO, Objective, AIC, BIC, fit
indices, convergence, timing) and loses all six gain columns;$transitions is new -- one row per adjacent pair, holding the gains
(which are properties of a step, not of a candidate) together with the
stability indexes below;$selection becomes a long-form table with one row per criterion x
form x cut x eligibility scope, recording the normalizer gain_max, the
absolute threshold, selected_K, and both boundary readouts.
No percentage-of-largest-gain column is stored anywhere: the normalizer
depends on the eligibility scope, so percentages are computed for display.K-factor solution survives inside the K+1-factor solution the sweep
already fitted: phi_min (the binding index), phi_mean, n_phi90, ari,
rmsd, surplus_max, per-endpoint assignment-coverage counts, and
stability_status. Columns are matched by bottleneck (max-min) Tucker
congruence, which -- unlike the greedy stored-order matching used in
hand-rolled versions -- is permutation invariant and actually maximizes the
binding statistic. transition_detail() returns the per-column material
behind one row.stable column,
no comparison against any threshold. The manuscript's .85 operating point
is documented as provisional and specific to its own workflow.orthogonal/bifactor sweeps the padded columns are rotationally
indeterminate, and a non-converged endpoint is not a solution; in both cases
the loading-based columns are NA with the reason in stability_status.
Gains remain valid in every mode.cuts argument replaces the overloaded scalar delta: a named
vector of gain thresholds with exactly one primary entry, so a primary
rule and any number of sensitivity rules are evaluated in one call. The
default stays c(primary = 10); delta continues to work as a deprecated
alias, and supplying both is an error.keep_fits ("all", "selected", "none") for callers who want
the tables without the fit payload. selected_fit is always returned.
summary.pefa() now carries the three tables and no fit payload.ELBO_gain, BIC_gain, or any
*_gain_pct column from $sweep must read $transitions instead;
summary()$comparison is now summary()$sweep; $selection is a data
frame rather than a named vector. $boundary and $delta survive as
deprecated aliases of $window_boundary and $cuts[["primary"]].selected_K can differ from 0.7.1 on any sweep
with an interior gap. Sweeps without a gap are unaffected.save_path checkpoint/resume subsystem. pefa() performs
one in-memory sweep and returns one complete object; it writes nothing and
cannot resume an interrupted call. Saving a result is ordinary
saveRDS() workflow. The manifest that made resuming trustworthy is gone
with it and is not replaced, because a single in-memory sweep cannot pool
candidates fit under different settings in the first place.print.pefa() inferred the fitting mode from $fits[[1]], so its
orthogonal weak-identification caution silently vanished whenever the first
candidate's fit was absent. Both print methods now read explicit top-level
mode settings. Reported also in ?pefa.pefa vignette: the checkpoint walkthrough is replaced by a section on
saving a sweep and widening its window, which shows that re-running a wider
window reproduces the shared candidates bit-for-bit rather than wasting
them, and that Kmin cannot go below K0.bifactor vignette, Section 6: three corrections to the two-step process.
Cut disagreement is no longer described as the workflow's protection
against absorbed local dependence -- it is not a dependence detector, and
the hard case leaves every count-level indicator clean; residual inspection
is now an unconditional step, labelled as the exploratory sensitivity it is.
The doublet repair no longer adds a two-item factor, which perturbs the two
competitors differently (the oblique arm estimates the new column's
correlations while bifactor = TRUE forces it orthogonal); it now frees the
same residual edge in both arms with ld = TRUE and a shared Qe, which
reverses the contaminated verdict at both anchoring depths. The thin -9
LPC margin is read against the vignette's own small-margin warning, and the
accompanying manuscript is now cited by name rather than as "the
accompanying manuscript."vbmimic() has no direct
covariate-to-item paths, so it models impact but cannot express or detect
differential item functioning. A quick-start comment that named the
$design/$settings fields removed in 0.7.0 was reworded.stats::rnorm, stats::var,
stats::quantile).Slimming release: fitted objects now store each quantity exactly once. No estimator changes -- every number a model produces is unchanged.
$coefficients, $design, $posterior, and
$settings from vbfa() and vbmimic() fits. Each of their elements
duplicated a top-level component (Lam, Phi, pi, Lam_var,
eta_cov, Q, Qe, orthogonal, ld, bifactor, v0); they had no
consumers anywhere in the package, its tests, or its vignettes, and
coef() never read them. vbmimic()'s standardize setting, the only
element that lived solely in a mirror, is now a top-level component.preprocess$missing_mask is stored only when the data actually contain
missing values (NULL otherwise). For complete data it was an all-FALSE
N x J matrix and the single largest part of a fit -- 65% of the object at
N = 3000, J = 27. preprocess$n_missing still reports the count in all
cases.pefa() no longer returns the $fit alias, which was a third copy of
the selected model (already available as $selected_fit and inside
$fits) with no consumers.vbfa fit is about half its previous size
(144 KB -> 74 KB for N = 3000, J = 27), and a saved sweep about 20%
smaller. A new test pins these invariants so the duplication cannot
silently return.pkgbuild::has_build_tools(debug = TRUE), a genuine compile test on every
platform) and, if it fails, per-platform instructions and links: Rtools
matched to the R version on Windows, xcode-select --install on macOS, and
the distribution's R development packages on Linux.Nothing was on CRAN when these fields were removed, so no released API is
affected; scripts that read the documented top-level components
(fit$Lam, fit$pi, fit$Q, ...) are unaffected.
special_effects() post-processes an orthogonal bifactor fit
into the three summaries of the bifactor vignette in one call: the
Schmid-Leiman proportionality check (per-group and item-weighted mean CV
of the group/general loading ratio), the approximate higher-order
parameterization (gamma per group factor, lambda per item), and the
testlet/special effect sizes of Zhang & Chen (2024, Eq. 16) with
posterior-inclusion gating of unspecified loadings. Group membership is
inferred from anchors and inclusion probabilities; unassigned,
cross-loaded, and no-general-loading items are flagged rather than
silently pooled. Deliberately descriptive: no cutoff argument and no
verdict -- the conventional .1 reading of the mean CV is mentioned as
suggestive, and all quantities are reported regardless. Accepts
bifactor = TRUE fits natively and legacy hand-built designs (one
all-specified column under orthogonal = TRUE). Tests pin the function
to an independent reimplementation of the vignette's plain-R code.bifactor mode in vbfa() and pefa(). Previously a bifactor
was expressed by hand-building a general column into Q under
orthogonal = TRUE, which made the user-facing factor count include the
general factor -- off by one against the comparable oblique model, a
recurring source of confusion in sweeps ("Kmin = 3 means 2 group
factors"). With bifactor = TRUE, Q (and pefa()'s Q0) supply only
the group design; the general column is added internally, and K
everywhere means group factors, directly comparable to an oblique
K-factor model. Details:
bifactor = TRUE implies orthogonal factors and overrides
orthogonal = FALSE (with a message when the latter was passed
explicitly).general argument designs the general column (1/0/-1 per
item, default all 1), so bifactor-(S-1)-style designs (some items
with no general loading) and partially exploratory general columns are
now expressible.Q that already contains an all-specified column together
with bifactor = TRUE is an error rather than a silent double count.bifactor, n_general, and general; print() shows
"1 general + K group factors". The pefa sweep table gains a K_total
column and the print methods label the window as group factors.cbind(1L, Q) with orthogonal = TRUE) still works
and is bit-identical -- the new mode is bookkeeping, not a new
estimator -- and a regression test enforces that equality.?pefa warnings keep full
force.orthogonal = TRUE fits now print as "orthogonal"
rather than "orthogonal/bifactor".Documentation-and-parallelism release preparing the CRAN submission, plus one estimator bug fix.
vb_fit(). The pre-0.4.0 name for fit_stats() survived
0.4.x as a thin wrapper; it is gone as of this release (before any CRAN
submission, so no released API is broken). Use fit_stats().sim_fa() gains gamma for direct higher-order (testlet) generation:
a scalar or per-factor vector of second-order loadings in (0, 1) makes each
group factor load on one general factor, and the data are generated from
the equivalent Schmid-Leiman orthogonal bifactor matrix (returned as MLA,
with the first-order matrix and gamma as MLA1/gamma). This replaces
the hand-built-matrix recipe previously shown in the bifactor vignette.plot.pefa() redesigned. The objective panel gives the objective and BIC
their own axes (one shared axis flattened both curves into unreadable
lines); the fit panel splits absolute misfit (RMSEA/SRMR) and incremental
fit (CFI/TLI) into side-by-side panels with conventional cutoffs; all
panels use real plotting symbols that match their legends (previously
matplot() drew digit characters while the legends showed symbols) and
mark the selected K with a dotted vertical line.print.summary.pefa() now prints one compact comparison table with the raw
objective and BIC, their marginal gains as % of the largest gain (the
quantity the selection rule actually thresholds), the fit indices, and a
marker on the selected row. Raw gains and timing remain in $comparison.-1)
systematically selects the lower window boundary regardless of the true
K: an omitted group factor's rank-one covariance block is absorbed by
the unspecified entries of the remaining columns, making the candidates
covariance-equivalent -- and the truth-sized candidate inside such a sweep
is itself not interpretable. ?pefa gains a dedicated section; the
print() methods replace the generic "extend the window" advice with an
explicit caution when an orthogonal sweep lands on the lower boundary.pefa() sweep for the first-order factor count -- interior selections
are trustworthy there even when the data are bifactor or higher-order;
(2) anchored oblique-K versus anchored bifactor-(K+1), compared by BIC
(not ELBO, whose prior mass differs across the two designs); (3) within
the winner, the Schmid-Leiman mean-CV check (bifactor side) or a triad
factorization of the fitted factor correlations (oblique side) settles
bifactor versus higher-order versus plain correlated factors. Every
population in the vignette routes correctly, and the route's power limits
(a near-homogeneous true bifactor needs a large sample at step 2) are
reported from a 3 x 3 x 3 simulation grid. ?pefa summarizes the route.Q == 0) loadings was
left at its initialization value 1 instead of 0, and the LD branch's
expected residual covariance summed loading variances over all columns --
adding roughly +1 per fixed-zero column to the residual variance of every
item that had any. On the standard test design, anchored items' residual
variances came out ~5x too large (loadings and residual-edge selection were
unaffected). Diagonal (ld = FALSE) results are unchanged -- the r1.1
bit-exactness fixture still passes -- and the reported Lam_var is now 0
for fixed-zero entries in both modes. The defect is inherited from the
reference implementation ld-vb_r.R, which computes the same term the same
way.sim_fa() gains mla: supplying a population loading matrix switches it to
matrix-driven generation (pattern arguments are then ignored), with the same
convention as sim_lvm(). Its factor-1-2 correlation argument is renamed
ph1 -> ph12 to match sim_lvm(), so ph1 now exists only in
sim_lvm(), where it is the correlation among latent predictors. Both help
pages gained matrix-driven examples and now read as a matched pair.pefa() sweep over bifactor candidates -- including an explicit
note that Kmin/Kmax count the general factor, and a demonstration of
why factor-number discovery is intrinsically hard for orthogonal bifactor
structure (an omitted group factor is absorbed by the unspecified entries
of the remaining columns) -- and the higher-order/testlet connection:
simulating a higher-order CFA directly via sim_fa(gamma = ), checking the
Schmid-Leiman constraint with a mean-CV rule (approximately higher-order
when mean CV < .1), transforming back to higher-order parameters via the
per-cluster mean ratio, and computing special-effect sizes by Eq. 16 of
Zhang & Chen (2024) with posterior-inclusion-based selection of unspecified
loadings. The testlet and higher-order models are noted as mathematically
equivalent.vbfa vignette reorganized: restricted-Qe local dependence (testlet-block
design matrices), a diagonal-vs-LD model comparison via fit_stats(),
executed v0-path comparisons, an ld_control demonstration, and a
"Missing data" section stating the missing-at-random assumption
(Chen, 2021) with simulated and empirical demonstrations. The factor-number
and bifactor material moved to the pefa and bifactor vignettes.pefa vignette expanded: the lead sweep now runs vbfa()'s default
warm-started v0 path on a true K = 4 simulation (N = 500, window 2:6 on a
K0 = 2 backbone) and recovers it in the interior; checkpoint/resume with
the provenance manifest (including the refusal on changed settings),
executed plot() panels, a boundary demonstration on the same data, and a
sweep on data with minor factors showing the gain rule ignoring dust
dimensions.vbmimic vignette: missing responses in Y demonstrated.tmp/ artifacts and the sibling vbpm.Rcheck/ removed;
references across README and vignettes made consistent, adding Chen (2021,
Multivariate Behavioral Research) and Zhang & Chen (2024, Applied
Psychological Measurement).vbmimic() now also accepts missing responses in Y. Its residual
covariance is diagonal, so the joint Gaussian conditional collapses to the
factor-model mean eta_i A', with the conditional variance 1 / V_j carried
into the residual sum of squares; the covariate information is already
carried by eta_i. Missing values in X remain an error: covariates are
conditioned on rather than modelled, so imputing them would require a
distributional assumption the MIMIC model does not make.vbfa() now handles incomplete continuous-response data by deterministic,
joint Gaussian conditional-moment updates. The model and conditional moments
match the data-augmentation formulation used by LAWBL and the related PCFA
MCMC work; rows or items with no observed responses are rejected.objective with
objective_type = "vecm"; ELBO remains NA because the residual precision
and mixing probability are point updates rather than variational factors.fit_stats() S3 generic works for all vbpm_fit objects. It computes
covariance fit statistics for diagonal and LD vbfa fits and returns a
clearly marked limited result for vbmimic; vb_fit() remains as a
compatibility wrapper.pefa() now returns a c("pefa", "vbpm_sweep") object with candidate fits,
a sweep table, summary(), plot(), and selected_fit(). Summaries report
objective, objective-gain, ELBO, ELBO-gain, BIC, and BIC-gain selections.vbfa, vbmimic, and pefa vignettes..cond_moments()) tested directly against textbook
multivariate-normal conditional means and covariances, for diagonal and full
residual covariance; missing-data fits are checked for mask integrity and
row-permutation invariance; LD fit statistics are checked to be built from
the residual covariance W rather than the precision; and missing data is
checked to compose with local dependence.Fits are now classed objects, output names are plain-language, the default is quiet, and the package has vignettes. One release, one breaking-change window, before any public release — no public API ever carried the old behaviour.
vbpm_fit. vbfa() returns c("vbfa", "vbpm_fit") and
vbmimic() returns c("vbmimic", "vbpm_fit"). A fit is still a plain named
list — fit$Lam etc. are unchanged and remain public API — but print(fit)
now shows a compact summary instead of dumping every matrix, and coef()
works. See ?vbpm_fit for exactly what the class does and does not change.Q (and Qe, Q_A, Q_B),
orthogonal, and ld are stored in the fit, which is how vb_fit() and
print() know what they are looking at without being told..q. notation stays inside the
estimators, where it mirrors the papers' derivations; the returned names are
now plain language):
sigsq.q.Lam -> Lam_var, PHI.q.eta -> eta_cov,
M.q.PHIiver -> Phi_inv_mean (in vbfa());sigsq.q.A/sigsq.q.B -> A_var/B_var (in vbmimic());plotDat dropped (always-NULL placeholder).vbfa(convChk = FALSE) is now the default, and
progress goes through message() (suppressible) instead of cat().
vbmimic() already behaved this way.vbfa(v0 = 0.001) is now proven
bit-identical to the frozen fixed-spike estimator behind the 2025 SEM paper
(fixture generated from the original source; 79 iterations, all deltas
exactly zero). This is the guarantee that lets analysis code migrate from
the historical scripts to the package.vbmimic() — regularized variational Bayes for extended multiple-indicators
multiple-causes (MIMIC) models, with spike-and-slab priors on both the
measurement (Q_A) and the structural (Q_B) part. Supports a warm-started
v0 path. The defaults (v0 = 0.001, standardize = FALSE) reproduce the
estimator of Jin & Chen (2025, Multivariate Behavioral Research)
bit-for-bit. The ELBO is not yet available, so vb_fit() and pefa() do
not accept vbmimic fits.vbfa() gains an explicit ld switch for local-dependence estimation.
ld = FALSE (default) fits diagonal residuals and ignores Qe (with a
warning if one is supplied); ld = TRUE with Qe = NULL means fully
exploratory local dependence (Qe = matrix(-1, J, J)); an explicit Qe is
validated as before. Previously the mode was toggled implicitly by whether
Qe was supplied.vb_fit() defaulted orthogonal = FALSE regardless of how the model
was fit, so calling it on a bifactor (orthogonal = TRUE) fit without
repeating the argument silently counted K(K-1)/2 nonexistent factor
correlations, biasing t, AIC, BIC, RMSEA, CFI, and TLI. vbfa() now
records orthogonal (and ld) in the fit object and vb_fit() reads it
from there; a contradictory explicit value is an error.nlsy27 — the NLSY 1997 empirical illustration dataset (3,458 x 27,
mixed-type), carried over from LAWBL.vbfa() (bifactor, local dependence,
empirical data), vb_fit(), pefa(), and select_K_elbow().sim_lvm() — latent-variable-model data generator, absorbed from the LAWBL
package, which vbpm supersedes. Simulates observed predictors (P, b)
and latent predictors (K1, ph1, b1) in addition to the factor-analytic
features. It complements sim_fa(): sim_lvm() is driven by a loading
matrix and covers predictor designs and mixed response formats, while
sim_fa() is driven by the loading pattern (items per factor, alternating
cross-loadings, minor factors).
phd argument: correlated structural disturbances, so designs with an
unrestricted factor residual covariance can be simulated.phx argument: the covariate correlation, previously tied to phi.NAs appeared in X. Missingness is now confined
to the J items.First release.
vbfa() — regularized variational Bayes factor analysis across the
confirmatory–exploratory continuum. Spike-and-slab priors on unspecified
loadings; optional warm-started dynamic regularization path (v0 a vector);
orthogonal bifactor parameterization (orthogonal = TRUE); optional sparse
residual / local-dependence estimation (Qe) via a graphical spike-and-slab
prior solved by QUIC. A scalar v0 = 0.001 reproduces the fixed-spike
estimator exactly.vb_fit() — SEM-like fit statistics (RMSEA, SRMR, CFI, TLI, AIC, BIC,
ELBO) with hard selection as the default.pefa() and select_K_elbow() — window sweep over the number of factors
with a scale-free gain rule for factor-number selection.sim_fa() — simulate factor-analytic and IRT data with cross-loadings,
local dependence, minor factors, categorical responses, and missingness.All three items below were addressed in 0.4.0; they are recorded here as the
state of 0.1.0. See "Known limitations" in README.md for current status.
vbfa() (errored on NA). Added in 0.4.0 for
continuous responses, by in-loop Gaussian conditional moments.NA). Resolved in 0.4.0 by
supplying the quantities that are actually defined — the terminal VECM
objective and the conditional bound ELBO_conditional. ELBO remains
NA by design, because the precision and mixing proportion are point
updates rather than variational factors, so no single joint mean-field
bound exists to report.vb_fit(). Added in 0.4.0 via fit_stats(), which builds the
implied covariance from the residual covariance W and counts selected
residual edges.