NEWS
psychonetrics 0.16
- Version 0.16 is a major feature release that consolidates the 0.15.x
development series. It bundles a large internal code audit (numerous
bug fixes, robustness and performance improvements across all model
families, and a new automated test suite) together with substantial new
modeling functionality, all validated against lavaan. The detailed
per-version changelog is retained below.
- NEW: a fast sufficient-statistics two-level maximum-likelihood estimator for
multi-level latent variable models (ml_lvm(estimator = "ML")), supporting
complete and within-cluster-missing data.
- NEW: robust maximum-likelihood estimators MLM, MLMV, MLMVS and MLR
(robust/sandwich standard errors, scaled chi-square statistics and robust
RMSEA/CFI/TLI), with Satorra-Bentler-family scaled chi-square difference
tests in compare().
- NEW: lavaan interoperability via fromlavaan() and tolavaan(), standardized
(std.all) solutions with delta-method standard errors, the wishart
likelihood, and fixed exogenous covariates (fixed_x).
- The estimators, fit measures, standard errors and converters listed above
were validated against lavaan as a numerical oracle. Several of the new
methods carry an experimental note (shown when verbose = TRUE) and may still
change.
psychonetrics 0.15.31
- This release adds new modeling functionality validated against lavaan: a
fast two-level maximum-likelihood estimator for multi-level latent variable
models, robust maximum-likelihood estimators (MLM/MLMV/MLMVS/MLR) with
scaled test statistics and robust fit indices, scaled chi-square difference
tests, converters to and from lavaan, standardized solutions, the wishart
likelihood, and fixed exogenous covariates. All new features are opt-in;
existing analyses are unaffected. Several new methods carry an experimental
note (shown when verbose = TRUE).
- MULTI-LEVEL: ml_lvm() gains a sufficient-statistics two-level ML estimator
(estimator = "ML"; McDonald & Goldstein, 1989; Muthen, 1990), which
optimizes the numerically identical objective as the wide-format FIML
estimator but is dramatically faster for large clusters (e.g. a 100-cluster
model with cluster sizes 18-25 fits in under a second versus ~90 seconds
with FIML). Standard errors and modification indices use an analytic
expected Fisher information (verified against lavaan's kernel to ~1e-13),
with full C++ implementations. Within-cluster missing data (MCAR/MAR) is
supported (matching lavaan::sem(..., missing = "ml"); R-based, numeric
information). The estimator argument now defaults to "default", which picks
"ML" for complete data with largest cluster > 5 units and "FIML" otherwise;
explicit estimator = "FIML" reproduces previous results exactly. Saturated
and baseline reference models, chi-square, df, CFI and TLI agree with
lavaan's two-level SEM; sample-size-dependent measures (RMSEA, BIC) use the
number of clusters J (so RMSEA is larger than lavaan's by a factor
sqrt(N/J)).
- ROBUST ML: added robust maximum-likelihood estimators selectable via the
model constructors or setestimator(): "MLM", "MLMV", "MLMVS" (Browne-1984
robust.sem sandwich standard errors with the Satorra-Bentler,
scaled-and-shifted, and mean-and-variance-adjusted scaled chi-square,
respectively) and "MLR" (Huber-White sandwich standard errors with the
Yuan-Bentler-Mplus scaled chi-square), plus robust RMSEA/CFI/TLI. Point
estimates are unchanged maximum likelihood. "MLR" supports missing data via
FIML, with the FIML-corrected (Savalei, 2010) robust fit indices, matching
lavaan's estimator = "MLR", missing = "fiml". The Satorra-Bentler family is
complete-data only. All quantities were validated against lavaan.
- The multi-group scaled (WLSMV / Satorra-Bentler) test statistic now
accumulates trace((U Gamma)^2) on the global block-diagonal stack rather
than per group. The per-group form was only correct without cross-group
equality constraints; multi-group equality-constrained scaled chi-squares
now match lavaan (the same correction lavaan made in 0.6-13). Single-group
output is unchanged.
- compare() now reports a Satorra-Bentler-family scaled chi-square difference
test (columns Chisq_diff_scaled, p_value_scaled) for nested models fitted
with a robust or scaled estimator. A new scaled.test.method argument selects
"satorra.bentler.2001" (default), "satorra.bentler.2010", or "satorra.2000"
(scaled-and-shifted; appropriate for MLMV/WLSMV). Output for non-robust
estimators is unchanged. Reproduces lavaan's lavTestLRT().
- LAVAAN INTEROPERABILITY: added fromlavaan(), which converts a fitted lavaan
object (or model syntax + data) into an equivalent psychonetrics lvm
(latent = "cov", residual = "cov"), reproducing estimates, standard errors,
chi-square, df and log-likelihood for standard ML fits (multi-group,
equality constraints, std.lv and FIML supported; unsupported features raise
informative errors). Added tolavaan(), which converts a psychonetrics lvm
(cov/cov) into a fitted lavaan object or a lavaan parameter table.
- parameters(x, standardized = TRUE) now returns the completely standardized
(std.all) solution with delta-method standard errors for the lvm and varcov
families (new std and se_std columns), matching lavaan::standardizedSolution().
- Added likelihood = "wishart" to lvm() and varcov() (complete-data Gaussian
ML): uses the unbiased (n-1) sample covariance with (n-1)-scaled chi-square
and standard errors, matching lavaan(likelihood = "wishart"). The default
likelihood = "normal" (n denominator) is unchanged.
- Added fixed_x to lvm() and varcov(): fixes declared exogenous covariates'
means and mutual (co)variances to their sample values (conditioning on x),
matching lavaan(fixed.x = TRUE); estimates, standard errors, chi-square, df
and the conditional log-likelihood match lavaan. Incremental fit indices use
psychonetrics' unconditional baseline.
- usecpp() keeps the two-level ML estimator with missing data on the R path
(it has no C++ implementation) instead of failing with a cryptic error.
psychonetrics 0.15.30
- This release implements the remaining findings of a large internal code
audit: bug fixes across all model families, robustness and performance
improvements, a documentation overhaul, and a new automated test suite.
- ESTIMATION AND OPTIMIZATION:
- Fixed an inverted positive-definiteness flag and re-enabled the optimizer
penalty for improper (non-positive-definite) implied covariance matrices.
Previously the ML objective was unbounded below at such points, which
could make optimizers (notably nlminb, the default) diverge to absurd
estimates on panel models (panelvar/panelgvar) and SEM models. Both the
C++ and R fit paths now return a large penalty instead.
- Fixed the experimental "LBFGS++" optimizer reporting convergence at
infeasible points (non-finite objective, penalty bound, NA parameters, or
iteration limit); such runs now trigger runmodel's emergency restart or a
clear error. Also fixed runmodel's recovery logic for the C++ optimizer
branches: NA optimizer output now triggers an emergency restart, and
failed recoveries stop with an informative error.
- Fixed emergencystart() constraining the wrong edges to zero in its glasso
restart (free edges were zeroed instead of structurally fixed ones).
- latentgrowth() (and lvm models with fully fixed nu and free nu_eta) now
uses least-squares starting values for the latent means, fixing frequent
non-convergence on growth data with nonzero means.
- Fixed elastic-net penalized ML (0 < alpha < 1) applying the ridge
component twice in the proximal-gradient optimizer; LASSO (alpha = 1)
results are unaffected. The default beta_min for penalized-model lambda
selection is now "numerical" (1e-05), matching the documentation.
- Fixed an operator-precedence bug in the internal spectral shift used for
start values (it added 0.001 to every element instead of shifting the
diagonal and could leave matrices exactly singular). Start values of
dlvm1/panelgvar-type models on difficult data may change slightly
(typically improving convergence). The GGM correlation-input runtime path
no longer applies a spectral shift, aligning the R and C++ paths.
- solve_symmetric: the log-determinant plugin for non-positive-definite
matrices now also catches NaN. Fixed a potential stale-cache issue in the
C++ optimization workspace (cached model now protected from garbage
collection). refit()-ed models now select the same default optimizer as
freshly specified models; runmodel() warns when bounded = TRUE is ignored
by ucminf.
- GENERAL BUG FIXES:
- fullFIML = TRUE (row-wise FIML) errored with "Not a matrix." for all
standard model families; the C++ FIML fit, gradient, expected Hessian and
log-likelihood now handle both single implied matrices and per-row lists.
- Fixed equal = "omega_zeta" being silently ignored (and "lowertri_zeta"
wrongly applied instead) in lvm/lnm/lrnm models with a latent network.
- factorscores() now correctly accounts for structural regressions (beta)
in the regression method and adds the model-implied latent means to both
methods, fixing wrong scores in SEM-style and multi-group models.
- Fixed varcov(covtype = "choose"): detected unbiased (n-1) input
covariances are now correctly rescaled to ML.
- Fixed multiple-comparison p-value adjustment (prune/adjust): the
correction is now over unique parameters rather than parameter-table
rows, so equality-constrained parameters are no longer over-penalized.
- Fixed prune(recursive = TRUE) resetting alpha to 0.01 (and dropping
mode/startreduce/limit) in passes after the first.
- Likelihood-based fit measures (logl, AIC, BIC, ebic) are now retained
when chisq/baseline measures are unavailable (e.g. baseline_saturated =
FALSE). Fixed the ebic.75 fit measure (used gamma 0.7 instead of 0.75).
- Fixed getmatrix(..., diag = FALSE) zeroing the wrong elements.
- runmodel(analyticFisher = FALSE) now actually uses the numeric Fisher
information on the default C++ path. Fixed the identification check in
greedy stepup() inspecting the previous model's information.
- Standard errors: inversion-failure/pseudoinverse warnings are no longer
suppressed when verbose = FALSE; negative inverse-information variances
now yield NA standard errors with a warning (R and C++ paths).
- setestimator() now resets the computed flag, propagates the estimator to
baseline/saturated models, and validates required sample statistics.
- varcov-family models (ggm, corr, ...) now give an informative error when
corinput = TRUE is combined with a meanstructure under (penalized) ML,
instead of crashing during optimization.
- Fixed parequal() argument handling (named runmodel arguments leaked into
the constraint set). fixpar() and friends now error on mistyped variable
labels instead of silently doing nothing. compare() reports NA p-values
for equal-df models and warns on negative chi-square differences.
- Fixed lvm/lnm/meta_lvm with latent = "ggm" and a single latent variable
crashing at construction. lvm() now warns when there are more latent
than observed variables. bifactor() uses deterministic starting values.
- ISING / BLUME-CAPEL:
- The partition function and moment computations now use log-sum-exp
accumulation: extreme parameter values no longer produce Inf/NaN fits,
gradients, or Fisher information (results unchanged in the normal
regime). Long state-space enumerations can now be interrupted.
- Raw data are now validated against the responses argument (stray values
error instead of silently biasing the fit); only missing = "listwise" is
supported and other options error at construction; the free inverse
temperature beta is now bounded at 0; groupequal("delta") now correctly
frees the group-specific beta in Blume-Capel models.
- TIME SERIES / PANEL MODELS:
- Fixed sigma_within (and sigma_crosssection) reporting in dlvm1-family
models: the between-person residual covariance was added instead of the
within-person residual covariance (estimation was not affected).
- ri_clpm stationarity constraints ("innovation"/"contemporaneous") now
select parameters structurally per wave; pruned/fixpar'd models
previously produced corrupted equality constraints and now constrain
correctly or stop with an informative error. Unpruned behavior unchanged
(wave 1 and the random-intercept block remain free).
- tsData(): grouped data previously ignored lags, scale, center and
centerWithin; lags other than 1 erroneously produced lag-1 columns. Both
fixed. var1()/gvar() gain a centerWithin argument (default FALSE).
- ml_lvm: variable names containing regex metacharacters (e.g. "y.1") are
no longer silently dropped (same fix in dlvm1/ri_clpm wide matching);
the C++ implied structure now correctly subsets by the design matrix for
incomplete designs, matching the R implementation; the default vars now
excludes the groups column. dlvm1-family constructors error informatively
with fewer than two waves. dlvm1 "version2" start values now use the
between-person residual covariance estimate for epsilon_between.
- META-ANALYTIC MODELS:
- meta_varcov now genuinely supports covariance matrices as input (covs=
defaults to corinput = FALSE as documented); previously covariances were
silently modeled as correlations and corinput = FALSE crashed.
- New Vmethod options "OS_individual" and "OS_pooled" in meta_varcov: the
Olkin-Siotani (1976) sampling covariance of sample correlations
(validated against metaSEM::asyCov), recommended when heterogeneity
(random-effects) variances are of substantive interest. The default
Vmethod is unchanged for backward compatibility with published analyses.
- Fixed meta_varcov/meta_ggm with estimator = "ML" and meta_lvm with
residual = "prec" crashing at the first gradient evaluation. Fixed
bivariate (single-correlation) meta_varcov crashing at construction.
Meta models now error informatively when two variables are never
observed together in any study. meta_lvm uses the C++ Jacobian for the
Fisher information when cpp = TRUE.
- PERFORMANCE:
- The joint equality score test used by addMIs/runmodel on multi-group
models factorizes the bordered information system once: up to several
hundred times faster on large equality-constrained models (identical
results). The matrices argument of addMIs() is now implemented.
- Commutation matrices are constructed directly in sparse form and, with
the other algebra helper matrices, cached per dimension: substantially
faster model construction (about 3x for a 30-node GGM). The sparse
Fisher-information path is now reachable for sparse model Jacobians.
- Ising state enumeration avoids per-state matrix copies and the
log-likelihood reuses the prepared partition function (about 1.3-1.6x
faster Ising runmodel). Plus assorted micro-optimizations.
- INFRASTRUCTURE, DOCUMENTATION AND TESTS:
- Added an automated test suite (tinytest) covering the psychonetrics.org
examples plus targeted regression tests for the fixes in this release,
with reference values cross-checked against lavaan. tinytest added to
Suggests; removed unused dependencies GA, combinat, VCA and abind.
- loop_psychonetrics() and replicator() gain a seed argument for
reproducible (parallel) simulations; aggregate_bootstraps()'s
remove_problematic argument is now respected.
- Large documentation cleanup: fixed a broken example in partialprune.Rd,
corrected the documented MIs() type values, documented lambda = "full",
corrected Cholesky equations and stale paragraphs in the family pages,
added missing \value sections, fixed broken examples, removed the
unimplemented "MUML" option from ml_lvm(), added a ggplot2 guard to
CIplot(), and fixed many typos.
- Added inst/COPYRIGHTS and a DESCRIPTION Copyright field documenting the
bundled LBFGS++ (MIT) headers; removed a linker flag that stripped debug
symbols (CRAN policy); dev-checklist files excluded from the build.
psychonetrics 0.15.29
- Bugfix: the "loadings" identification used by lvm(), ml_lvm(), dlvm1() and
tsdlvm1() could silently overwrite a user-fixed (or built-in) nonzero
factor loading. To identify a factor's scale the identifier fixed the first
"eligible" loading in each column to 1, where "eligible" included loadings
already FIXED at a nonzero value. When the first such loading was fixed at a
value other than 1, it was overwritten -- changing the model. This notably
affected latentgrowth() with non-default time scores: e.g.
latentgrowth(vars, data, time = c(0,2,4,6)) had its slope loadings altered
from 0,2,4,6 to 0,1,4,6, fitting a different growth model. The identifier
now skips a factor whose column already contains a fixed nonzero loading
(the scale is already identified) and otherwise fixes the first FREE loading
to 1. Standard CFA behaviour (first free loading fixed to 1) is unchanged,
and a user-supplied marker loading fixed at any nonzero value is now
preserved.
psychonetrics 0.15.28
- Bugfix: the multi-level latent variable model (ml_lvm) analytic Jacobian
omitted the derivative of the implied means with respect to the
between-subject regressions (beta_between). The implied mean is
mu = nu + lambda %*% (I - beta_between)^-1 %*% nu_eta, which depends on
beta_between whenever the latent intercepts nu_eta are nonzero, but the
d/d-beta_between block of the mean rows was never filled (in both the R and
C++ derivative paths). This produced an incorrect gradient/Jacobian column
for any ml_lvm with free between-subject regressions and free (nonzero)
latent means -- reachable since the 0.15.18 nu_eta identification fix. The
analogous block is now filled via d_mu_beta_lvm() / d_mu_beta_lvm_cpp(),
matching the single-level lvm. Within-cluster latents have mean zero, so
beta_within correctly contributes no mean term.
psychonetrics 0.15.27
- Bugfix: runmodel(addInformation = FALSE) crashed the whole call in the C++
standard-error path with "Mat::operator(): index out of bounds". addSEs_cpp()
read the @information slot unconditionally, but with addInformation = FALSE
that slot is empty (a 0x0 matrix), so indexing into the inverted matrix went
out of bounds. It now recomputes the Fisher information on demand when the
slot is absent, mirroring getVCOV(). The R getVCOV() path had the same latent
bug -- its is.null() check never fired for the default empty "matrix" slot,
silently returning all-NA standard errors -- and now also treats a zero-size
information matrix as absent. Both paths produce identical, correct SEs.
psychonetrics 0.15.26
- Bugfix: changedata() unconditionally accessed colnames(data), so the
documented summary-statistics usage changedata(x, covs=, nobs=) crashed
with 'argument "data" is missing'. The data-dependent label check is now
guarded, and the covs/means/nobs input path is forwarded to samplestats()
and the baseline/saturated constructors, mirroring how the model
constructors accept either raw data or summary statistics.
- Bugfix: changedata() always recomputed @sample@nobs including the mean
block (nVar*(nVar+1)/2*nGroup + nVar*nGroup), which corrupted the degrees
of freedom for models built with meanstructure = FALSE (e.g. a saturated
model reported df = 4 instead of 0). The mean block is now counted only
when the model has a mean structure, matching bootstrap() and the model
constructors.
psychonetrics 0.15.25
- Bugfix: transmod() attached incorrect standard errors and p-values to the
transformed model (e.g. transmod(type="ggm") on a covariance model, or the
reverse). The transformed estimates were correct, but addSEs() reused the
Fisher information copied verbatim from the original model - which still
described the pre-transformation parameterization - because getVCOV()
reuses a model's stored @information whenever it is non-NULL. transmod()
now recomputes the Fisher information on the transformed model before
adding standard errors, so the SEs and p-values match those of an
equivalent model fit directly. Point estimates are unchanged.
psychonetrics 0.15.24
- Bugfix: in multi-group models, the type="free" and type="equal" modification
index passes in addMIs() wrote their expected parameter changes to the
"normal" epc column instead of epc_free and epc_equal. As a result, in any
multi-group runmodel() (which runs normal -> free -> equal passes) the normal
EPC column was overwritten twice while epc_free and epc_equal remained NA.
MIs(type="free") and MIs(type="equal") now report their correct EPC columns,
and the normal EPC is no longer clobbered. Single-group results are unchanged.
psychonetrics 0.15.23
- Bugfix: bootstrap() resampled rows from the pooled data, ignoring the
grouping structure of multi-group models. This had three consequences:
(1) resampling was not stratified, so group sizes drifted between
replications; (2) the rebuilt sample derived its group order from the
first resampled row, which - whenever that row belonged to a different
group than the original first group - permuted the group ids relative to
x@parameters, so a group's parameters were fit to another group's data
(e.g. a two-group model with very different group means produced nonsense
estimates and failed to converge); and (3) the recomputed @sample@nobs
always included a mean block, corrupting the degrees of freedom for
meanstructure = FALSE models. bootstrap() now resamples within each group
(preserving group sizes), rebuilds the data in the original group order so
group ids stay aligned with the parameter table, and computes nobs as
nVar*(nVar+1)/2*nGroup + meanstructure*nVar*nGroup (matching the model
constructor). Single-group models are unaffected.
psychonetrics 0.15.22
- Bugfix: for multi-group Ising() and BlumeCapel() models specified as
Ising(data, groups = "group") without an explicit 'vars' argument, the
grouping column was included when auto-detecting the response options (and
in the 'min_sum' sum-score check). With numeric group codes this silently
added the codes as extra response options - e.g. binary 0/1 data with a
numeric group column was fit as a 3-state {0,1,2} model, giving wrong
thresholds - and with character group labels it failed later with an
opaque type error. The grouping column is now excluded from the response
detection and the min_sum check (matching how samplestats() derives
'vars'), and a non-numeric response set is now rejected up front with a
clear error message. Single-group models, and models with explicit 'vars'
or 'responses' arguments, are unaffected.
psychonetrics 0.15.21
- Bugfix: the C++ derivative routine for variance-covariance models
(used by the gradient and Fisher information when cpp = TRUE) wrote the
precision-matrix (kappa) derivative block into the wrong Jacobian columns:
it reused the row offset of the variance block instead of the parameter-
column offset. When the mean structure was present this happened to
coincide with the correct columns, but for precision() models fitted
without a mean structure (e.g. precision(covs = S, nobs = n) or
precision(data, meanstructure = FALSE)) the column indices exceeded the
width of the Jacobian and Armadillo aborted with "Mat::submat(): indices
out of bounds" for every estimator. The column range now matches the
sigma/cholesky blocks (and the R implementation). precision() models
without a mean structure now run; mean-structure models are unaffected.
psychonetrics 0.15.20
- Bugfix: standard errors for the ULS and DWLS estimators (used e.g. for
ordinal data via estimator = "DWLS"/"WLSMV") were computed with the naive
(Delta'WDelta)^-1 / n formula, which is only correct when the weight
matrix W equals the inverse of the asymptotic covariance Gamma of the
sample statistics. For ULS and (especially) DWLS this is not the case, so
the reported standard errors were biased (e.g. ordinal DWLS loading and
threshold SEs deviated by roughly 10-12% from lavaan's robust WLSMV
standard errors, while the point estimates matched). psychonetrics now
computes the robust sandwich covariance
(Delta'WDelta)^-1 (Delta'W Gamma W Delta) (Delta'WDelta)^-1 / n
for ULS and DWLS, matching lavaan's se = "robust" output. Full WLS
(W = Gamma^-1) and the ML/FIML estimators are unaffected, as the sandwich
reduces to the naive form there. When Gamma is unavailable (for instance
when a model is fit to a covariance matrix rather than raw data) the
non-robust standard errors are returned together with a warning. The
robust covariance also propagates to confidence intervals, prune() and all
other consumers of the parameter standard errors.
psychonetrics 0.15.19
- Bugfix: for multi-group models with ordinal data (e.g. ggm() and other
models with ordered = ... , groups = ... and estimator = "WLS"/"DWLS"),
the per-group polychoric correlations, thresholds and WLS weight
matrices were computed on the full pooled sample instead of each group's
own rows. Every group therefore received identical (pooled) sample
statistics, so multi-group ordinal models were silently fit to pooled
data. Each group is now correctly computed from its own observations.
Single-group ordinal models and continuous (non-ordinal) multi-group
models were unaffected.
psychonetrics 0.15.18
- Bugfix: the identification routine for the multi-level latent variable
models (ml_lvm() and the ml_lnm/ml_rnm/ml_lrnm wrappers) referred to the
latent-intercept matrix by the wrong name ("mu_eta" instead of
"nu_eta"), so the latent intercepts were never fixed for identification.
This left the mean structure rank-deficient: latent-intercept standard
errors were nonsensical (~1e6) and the model degrees of freedom were
inflated. The latent intercepts are now correctly fixed (in group 1 only
for multi-group models, as for lvm()), matching the corresponding
two-level SEM. Estimates and log-likelihood of previously fitted models
are unaffected; only the standard errors and degrees of freedom change.
psychonetrics 0.15.17
- BlumeCapel() now gives an error (instead of a warning) when fewer than
three response options are supplied. With only two responses the
quadratic 'delta' term is not identifiable (x^2 is constant across the
two states), so it is confounded with the thresholds; use Ising() for
binary data.
psychonetrics 0.15.16
- New function allequal(): constrains all free elements of a model
matrix to be equal within each group (a single shared parameter per
group), separately per group (not across groups; use groupequal() in
addition for that). Useful e.g. to model all thresholds (tau) or all
Blume-Capel quadratic potentials (delta) equal, or to fit an
equal-edge-weight network (omega). Optional 'row', 'col' and 'group'
arguments restrict which elements/groups are affected.
psychonetrics 0.15.15
- New model: BlumeCapel(). The Ising distribution has been generalized
to the 'Spin' distribution, which adds a per-node quadratic potential
delta:
P(X = x) propto exp( sum_i tau_i x_i - sum_i delta_i x_i^2
+ sum_{i<j} omega_ij x_i x_j ).
Two models are built on this distribution: the Ising model (delta
fixed to zero, exactly reproducing the previous Ising behavior) and
the new BlumeCapel model (delta freely estimated). BlumeCapel()
shares the Ising() engine, fit, gradient, expected Hessian and
standard errors. For the ternary -1/0/1 coding this is the usual
Blume-Capel model; any ordered response set is allowed (a warning is
raised when the coding departs from the conventional one).
- Ising() now warns when 'responses' is not -1/1 or 0/1, since the
behavior is then not exactly that of the classical Ising model.
- Internally, the model distribution previously labelled "Ising" is now
labelled "Spin"; both the Ising and BlumeCapel models use it. This is
transparent to users.
psychonetrics 0.15.14
- Ising() no longer requires the response options to be integers. Any
set of two or more distinct ordered response values is accepted in
'responses' (or detected from the data), integer or not.
psychonetrics 0.15.13
- The Ising model has been generalized to more than two response
options. Ising() now accepts any number of ordered integer response
options in 'responses' (e.g., c(-1,0,1) or seq(-5,5)), encoded
identically across all variables and detected automatically from the
data when not supplied. The parameterization is unchanged (one
threshold per variable in 'tau' and a pairwise network in 'omega');
only the partition function, expected values and (expected) Hessian
now sum over all length(responses)^nNode response patterns. With two
response options the results are identical to before. Only
non-integer responses raise an error.
- New argument 'maxStates' to Ising() (default 2^maxNodes) caps the
number of response patterns the exact ML estimator may enumerate,
since this cost grows as length(responses)^nNode. For binary data
the default reproduces the historical 'maxNodes' limit; raise it to
fit larger multi-state models when feasible.
psychonetrics 0.15.12
- The random intercept cross-lagged panel model (RI-CLPM) framework
has been finalized. ri_clpm() now accepts the same data input as
the dlvm1 family: wide-format data with a design matrix, or
long-format data via 'idvar' (and optionally 'beepvar'), with the
format auto-detected. A 'standardize' argument ("z", "z_per_wave"
or "quantile") was added. ri_clpm() models now also build an
independence baseline so that incremental fit indices (CFI/TLI)
are available.
- New function ri_clpm_search() runs a sequential stationarity
search: starting from the unconstrained model it adds, one at a
time, stationarity of intercepts, temporal effects, contemporaneous
relations and innovation variances, and finally the panelVAR model
(wave 1 endogenous), retaining each constraint only if it does not
worsen fit according to AIC, BIC, or a chi-square difference test.
- Fix: parequal() constrained parameters incorrectly (a misplaced
parenthesis passed the indices as unlist()'s 'recursive'
argument), which broke the temporal / contemporaneous / innovation
branches of ri_clpm_stationary().
psychonetrics 0.15.11
- New convenience setters update_baseline() and update_saturated()
overwrite the @baseline_saturated$baseline / $saturated slots of
a psychonetrics model. Together with 'baseline_saturated = FALSE'
on the model constructor they make it straightforward to attach
hand-built reference models before calling runmodel().
psychonetrics 0.15.10
- Fix: the internal saturated reference model no longer inherits
cross-group equality from the target. Previously the saturated
builders in 10 model files (varcov / ggm, lvm, ml_lvm, var1,
dlvm1, tsdlvm1, Ising, plus changedata() / bootstrap()) passed
'equal = equal' into the saturated, so a multi-group model fit
with e.g. 'equal = "omega"' produced a saturated whose covariance
structure was constrained equal across groups too. The saturated
is meant to be a fully-unrestricted per-group reference, so this
inflated chi-square pvalues and could produce nonsensical
negative df (the target was no longer nested in the saturated:
e.g. ggm(equal="omega") on 4 vars / 2 groups gave df = -4).
The recommended pipeline of imposing cross-group equality via
groupequal() after model construction was unaffected by this bug;
only the 'equal = ...' shortcut at construction time triggered it.
After the fix, both pipelines produce sensible chi-square tests
and incremental fit indices.
- Fix (issue #51, jwe4ec): baseline.df was not adjusted for the
case where the saturated reference model has fewer free
parameters than nobs. The target df already had this adjustment;
baseline.df now mirrors it. With the saturated-equal fix above
this correction is usually zero, but it now also handles the
case where the user supplies a custom non-fully-saturated
saturated via @baseline_saturated$saturated. Affects the chi-
square reference for the baseline test and any incremental fit
indices that depend on baseline.df (CFI, TLI, NFI, RFI, IFI,
RNI, NNFI).
psychonetrics 0.15.9
- Performance: the expected Fisher information for Ising models is
now computed entirely in C++ in a single 2^N state-enumeration pass
(psychonetrics_FisherInformation_cpp -> expected_hessian_Ising_full_cpp).
The previous path made an R callback to expected_hessian_Ising_group,
which itself called IsingSampler::IsingLikelihood (one full state
enumeration plus materialization of a 2^N x N states matrix) and
then expHessianCpp (which made three more passes through all
2^N states: expHcpp, expH2cpp, and the main moment-accumulation
loop). The new single-pass implementation also fills only the
canonical lower-tri shapes of the 3rd- and 4th-order moment
tensors that the Hessian assembly actually reads, eliminating the
O(2^N N^4) dense fill in favour of O(2^N N^4 / 8).
Per-call benchmarks (multi-group Ising):
N=10 G=2 : 11.8x
N=12 G=2 : 21.2x
N=14 G=2 : 28.2x
End-to-end partialprune() at N=15 G=2 (sample 800/group) drops
from ~700s (0.15.7) to ~43s. Output is bit-identical (max abs
diff in the @information slot: 6.7e-16, machine epsilon).
The R-level expected_hessian_Ising / expected_hessian_Ising_group
functions and the Rcpp::export expHessianCpp are kept as-is for
callers outside the cpp Fisher-information path.
psychonetrics 0.15.8
- partialprune() gains an 'identify' argument (default TRUE) that is
threaded through prune(), unionmodel(), intersectionmodel(),
groupequal(), and groupfree(). Setting identify = FALSE prevents
identify() from being called on the intermediate models, so user-
fixed parameters (e.g. fixpar(\"beta\", 1, 1, value = 1) in a multi-
group Ising) are not re-freed when omega equality constraints are
introduced. Default behavior (identify = TRUE) is unchanged.
psychonetrics 0.15.7
- Performance: partialprune() inner refits skip addMIs(). The
repeat-loop only consumes the BIC and the joint score-test
statistic mi_free_joint, so addMIs's three Fisher-information
builds (type = normal / free / equal) are wasted on every
iteration. mi_free_joint is repopulated directly via
.equalityScoreTestInner(joint_only = TRUE) on each candidate fit
so the next selection still has the data it needs.
addSEs and addInformation are kept enabled because the post-loop
prune() reads p-values (from addSEs) and @information (from
addInformation) on the final curMod; disabling them changed the
post-loop prune behavior on multi-group cases where the inner
loop iterated more than once.
For useMIs = "simple" we fall back to a full addMIs() call inside
the loop so the legacy per-parameter mi_free column is still
populated (the simple branch reads it).
Final partialprune() output is unchanged: the user-facing returned
model is produced by the regular post-loop runmodel() / prune()
chain, which uses default flags and populates MIs / SEs / @information.
psychonetrics 0.15.6
- Performance: addMIs() now skips the univariate score-test loop in
.equalityScoreTestInner() (added in 0.15.4), which was computed on
every runmodel() call but never consumed -- only the joint score-test
statistic populates the parameter table. The univariate output is
still produced on demand when equalityScoreTest() or MIs(type="free")
are called. partialprune() no longer pays this cost on every inner
refit. Speedup scales with the number of groups: ~1.2x for G=2,
~1.5x for G=3, ~2.5x for G=4 on a small Ising example. Final
partialprune() output is bit-identical to 0.15.5.
psychonetrics 0.15.5
- partialprune() gains a new argument release_model = c("pruned",
"saturated"), defaulting to "pruned". This changes the alternative
model used in the stepwise BIC comparison when deciding whether to
release an equality constraint: with "pruned" the just-released
parameter is immediately fixed to zero in any group where the
initial per-group prune (Step 1) had already removed it, before
refitting and comparing BIC. The equality-constrained model is
therefore tested against the asymmetric structure suggested by the
initial per-group prune rather than against a fully saturated
per-group alternative, which improves specificity at low sample
sizes. Set release_model = "saturated" to reproduce the 0.15.4
behavior.
psychonetrics 0.15.4
- New function equalityScoreTest() that computes the score (Lagrange
multiplier) test for cross-group equality constraints in a multi-group
model. Returns both per-group univariate score statistics and a joint
multivariate score statistic (one per equality-constrained parameter,
df = G - 1). With the default method = "jacobian" the test
reproduces lavaan::lavTestScore exactly (to numerical precision)
for both balanced and unbalanced multi-group samples. A faster
method = "schur" alternative is available for complex models; it
inverts only the smaller "currently free" block of the information
matrix and matches lavaan in balanced samples.
- The parameter table now carries three new columns: mi_free_joint,
pmi_free_joint, and df_free_joint, populated whenever modification
indices of type "free" are computed.
- MIs() now prints two tables for type = "free": the existing summed
per-group univariate statistics (header updated to "Top N summed
equality-free modification indices") and a new "Top N joint
equality-free modification indices" table based on the multivariate
score test.
- partialprune() gains a new argument useMIs = c("joint","simple"). The
default "joint" ranks candidate equality releases by the joint score
test (correctly accounting for cross-group covariance for G >= 3).
Set useMIs = "simple" to reproduce the psychonetrics <= 0.15.3
behavior of summing per-group univariate MIs. The two options are
equivalent for two-group models.
psychonetrics 0.15.3
- Fixed meta_ggm() crashing with "non-numeric argument to mathematical
function" when using Vestimation = "per_study". The SRMR computation
in addfit() assumed sigma was a numeric matrix, but for meta_varcov
models with per-study estimation it is a list of per-study matrices.
SRMR now gracefully returns NA for such models.
- Fixed stepup() breaking equality constraints set by groupequal() in
multi-group models (GitHub issue #50). stepup() previously relied on
the @equal slot to detect cross-group equality constraints, but
groupequal() never populated it, so stepup() always freed parameters
for a single group - silently breaking the constraint. stepup() now
infers equality constraints directly from the parameters table
(shared non-zero par indices), which is the canonical representation.
groupequal() and groupfree() also now maintain the @equal slot for
backwards compatibility with model reconstruction code.
- stepup(greedy = TRUE) now falls back to the non-greedy path when
any searched matrix has cross-group equality constraints. The greedy
branch previously bypassed freepar()/groupequal() and assigned unique
par indices to each added parameter, which would silently break
equality constraints. Single-group models and multi-group models
without equality constraints take the fast greedy path as before.
psychonetrics 0.15.2
- Fixed incorrect (negative) chi-square and fit indices for FIML
estimation with high-dimensional panel data. By default, the saturated
model is still estimated numerically (using nlminb for robustness), but
if the optimizer fails (saturated LL < model LL), the saturated
log-likelihood is now automatically computed analytically from
pattern-specific sample statistics, following the approach used by
lavaan and Mplus.
- Added 'saturated' argument to runmodel() with options "default"
(model-based with automatic analytical fallback), "analytic" (skip
optimization, use analytical formula directly -- recommended for
high-dimensional panel data), and "model" (force numerical optimization
with no fallback).
psychonetrics 0.15.1
- panelgvar() and panelvar() now accept both wide-format (design matrix)
and long-format (character vector of variable names) data, matching the
flexibility of the underlying dlvm1(). Previously these convenience
wrappers only accepted wide-format data. For long-format usage, pass
vars as a character vector and provide idvar (and optionally beepvar)
via ... (GitHub issue #48).
- Updated dlvm1 family documentation to reflect long-format support in
panelgvar() and panelvar(), with examples.
- Replaced all Rf_error() calls in C++ code with Rcpp::stop() for proper
C++ stack unwinding and destructor calls, avoiding potential memory
leaks (GitHub issue #49).
psychonetrics 0.15 (2026-02-27)
- The package has gone through a large overhaul in the 0.14.x series of
package versions. The most important changes are listed below.
- Major C++ optimization of the estimation pipeline, resulting in ~45%
cumulative speedup. This includes direct parameter-to-matrix
mapping, OptimWorkspace caching of constant data across iterations,
cache-aware prepareModel to skip redundant computations, and the new
LBFGS++ pure C++ optimizer.
- Added ordinal data support and WLSMV estimation for the lvm framework.
lvm() now accepts ordinal variables via the 'ordered' argument, with
WLS, DWLS, and ULS estimators. The WLSMV scaled test statistic
(Asparouhov & Muthen, 2010) provides scaled chi-square, scaled RMSEA,
and scaled incremental fit indices matching lavaan output.
- Restructured dlvm1() to accept both wide-format and long-format data,
unifying the interface for panel data models. Deprecated ml_tsdlvm1(),
ml_gvar(), ml_var(), and related wrappers in favor of dlvm1() and
panelvar() / panelgvar().
- Penalized maximum likelihood (PML) and penalized FIML (PFIML) estimation
with automatic lambda selection via EBIC-based grid search. Includes
beta_min thresholding, warm starts across the lambda path, and refit()
for refitting parameters without regularization.
- New meta_lvm() model family for single-stage meta-analytic latent variable
modeling (extending the MAGNA framework to CFA/SEM), and new meta_var1()
/ meta_gvar() for meta-analytic VAR(1) network models pooling temporal
and contemporaneous structures across studies.
- New write_psychonetrics() function for exporting comprehensive
Mplus/LISREL-style output to a text file, intended for supplementary
materials in publications.
- Standardized input arguments across all model families: new 'cors',
'groupvar', and 'corinput' arguments with consistent validation via
standardize_input(). Meta-analytic models now accept raw data input.
- Added SRMR (Standardized Root Mean Square Residual) fit measure using
Bentler (1995) standardization. Ising models now also compute
model-implied means and covariances enabling SRMR computation.
- Improved starting values: OLS-based beta starting values for SEM models,
fixed the 'equal' argument in multi-group lvm() models.
- New loop_psychonetrics() convenience function for parallel bootstrapping
and simulations with automatic variable export and progress bars.
- Changed default missing data handling to missing = "auto", automatically
switching to FIML/PFIML when missing data is detected.
- Automatic fallback to approximate standard errors when the Fisher
information matrix cannot be inverted.
- lvm() now accepts lambda = "full" to specify a full loading matrix where
all cross-loadings are free. Requires the 'latents' argument to be
specified. FA-based starting values use promax-rotated loadings directly
(no permutation matching needed for full lambda).
- penalize() now accepts a matrix for the 'lambda' argument, enabling
per-element penalty specification: 0 = don't penalize, NA = auto-select
via EBIC grid search, numeric > 0 = fixed penalty strength. Parameters
that are fixed at zero in the model are automatically freed when a
non-zero penalty is specified, combining freepar() and penalize() in
one step. Works for both symmetric (e.g., sigma_epsilon) and
non-symmetric (e.g., lambda) matrices.
- Various bugfixes for Ising Fisher information, dlvm1 Jacobian
construction, dplyr/ggplot2 deprecation warnings, and CRAN compliance.
psychonetrics 0.14.28
- Ising models now compute model-implied means (mu) and covariances (sigma)
from the exact Ising expectations after optimization. This enables SRMR
and other residual-based fit measures for Ising models (previously returned
NA). The computation uses the existing isingExpectation() C++ function:
mu = E[X] and sigma = E[XX'] - E[X]E[X]'.
psychonetrics 0.14.27
- Removed roptim-based C++ optimizers (cpp_L-BFGS-B, cpp_BFGS, cpp_CG,
cpp_SANN, cpp_Nelder-Mead) from the public interface. These optimizers
are no longer available via setoptimizer() or documented in help pages.
Users should use "nlminb" (default), "ucminf", "nloptr_TNEWTON", or
"LBFGS++" instead.
- Replaced deprecated ggplot2::aes_string() calls with
ggplot2::aes(.data[[]]) in CIplot() and ESA plot methods.
- Fixed delta matrix starting values when using delta_* = "zero":
diagonal elements fixed at zero now correctly show est = 0 in the
parameter table instead of data-derived starting values.
- Added SRMR (Standardized Root Mean Square Residual) fit measure.
Uses Bentler (1995) standardization, matching lavaan's default
srmr_bentler. Includes mean residuals when a mean structure is
present. Under FIML, uses the saturated model's EM-estimated
covariance as the observed statistic (matching lavaan's h1 approach).
Multi-group models use an N-weighted average across groups.
- The 'beta_min' argument in find_penalized_lambda() and runmodel() now
accepts "numerical" (default, uses 1e-05), "theoretical"
(uses sqrt(log(p)/n)), or a numeric value. The previous default was
the theoretical formula; the new default is the fixed numerical threshold.
- Added C++ pipeline for meta_var1/meta_gvar models. The C++ implementation
(cpp=TRUE, the default) uses OptimWorkspace caching and
formModelMatrices_direct for efficient parameter-to-matrix mapping.
- C++ implied model (implied_meta_var1_cpp) computes BetaStar, Sigma0,
Sigma1, and meta-analytic mu/sigma/kappa entirely in C++.
- C++ derivatives (d_phi_theta_meta_var1_cpp) reuse existing var1 C++
derivative helpers for the mean part and varcov helpers for the
random effects variance part.
- C++ prepare (prepare_meta_var1_cpp) follows the meta_lvm_prepare_cpp
pattern with D_c override for the ML gradient.
- Registered meta_var1 in all C++ dispatchers: prepareModel_cpp,
impliedModel_cpp, psychonetrics_gradient_cpp_inner, and
psychonetrics_FisherInformation_cpp_inner.
psychonetrics 0.14.26
- New meta_var1() model family: meta-analytic VAR(1) model for pooling
temporal and contemporaneous network structures across multiple studies.
Uses a one-stage random effects framework. Accepts either raw time-series
data (via data + studyvar) or pre-computed Toeplitz covariance matrices
(via covs + nobs).
- New meta_gvar() wrapper: convenience function that calls meta_var1()
with contemporaneous = "ggm" for graphical VAR estimation.
- meta_var1/meta_gvar model the stationary covariance (Sigma0) and lag-1
cross-covariance (Sigma1) blocks, excluding the nuisance exogenous
covariance block from the Toeplitz matrix.
- Derivatives for meta_var1 reuse existing var1 Jacobian helpers
(d_sigma0_beta_var1_cpp, etc.) for the mean part, and meta_lvm random
effects Jacobians for the variance part.
- R-only implementation (no C++ yet); model@cpp is set to FALSE.
- If studyvar is not assigned but idvar is, studyvar is set to idvar
with a warning. If both are assigned, idvar functions within each study
for collating time series.
psychonetrics 0.14.24
- Standardized input arguments across model families. All model constructors
(varcov, lvm, var1, tsdlvm1, dlvm1, meta_varcov, meta_lvm) now accept a
consistent set of input arguments:
- New 'cors' argument: supply correlation matrices directly (with 'nobs').
In varcov/meta_varcov, 'corinput' defaults to TRUE. In other families,
correlations are treated as covariances with a warning.
- New 'groupvar' argument: replaces the deprecated 'groups' argument. If both
are supplied, 'groupvar' takes precedence with a deprecation warning.
- New 'corinput' argument added to lvm, var1, tsdlvm1, and dlvm1 signatures
(errors if set to TRUE, as correlation input is only supported in varcov
and meta_varcov).
- Meta-analytic models (meta_varcov, meta_lvm) now support raw data input via
'data' + 'studyvar' arguments. Correlation/covariance matrices and sample
sizes are computed internally per study.
- meta_varcov() now also accepts covariance matrices via 'covs' argument.
- meta_lvm() now also accepts correlation matrices via 'cors' argument.
- Shared input validation consolidated into standardize_input() helper function,
providing consistent error messages for mutual exclusion violations, missing
required arguments, and unsupported combinations.
- Fixed pre-existing bug in meta_varcov() baseline/saturated model construction
where R's missing() function was passed as the 'missing' argument to varcov()
instead of a string value.
psychonetrics 0.14.23
- Strip debug symbols from compiled shared library via linker flags in Makevars,
reducing installed libs/ size on CRAN macOS builds (from ~29MB to ~2MB).
- Added C++ pipeline for meta_lvm (implied, prepare, derivatives), providing
~2.8x speedup over the R-only implementation.
- Fixed meta_lvm.Rd documentation to match actual function signature.
psychonetrics 0.14.22
- meta_lvm() now accepts covariance matrices (not just correlations). The data vector
includes diagonal elements (variances) alongside off-diagonal covariances, with free
sigma_epsilon diagonal. V matrix computation, implied model, and derivatives updated
to handle the full vech (with diagonal).
- Fixed checkJacobian() and checkFisher() to respect the model's @cpp slot: models
with cpp=FALSE (e.g., meta_lvm) now correctly use R-only fit/gradient/Fisher functions
instead of always defaulting to C++ versions.
- Fixed R CMD check --as-cran issues:
- panelvar()/panelgvar() now handle missing data argument correctly
- dlvm1() guards against missing data in auto-detect and samplestats calls
- penalty_lambda documentation updated to match code default (NA, not 0)
- Removed stray browser() calls from var1 implied and identify functions
- Fixed non-ASCII character in NA2020.Rd reference
- Removed continuous-data corinput from lvm(): correlations passed as covs are now
treated as covariances with free diagonal. Ordinal corinput retained internally.
psychonetrics 0.14.20
- Added meta_lvm() function for single-stage meta-analytic latent variable modeling.
Combines a latent variable model (CFA/SEM) for the mean structure of pooled
covariance/correlation matrices with a random-effects model for between-study
heterogeneity. Extends the MAGNA framework to latent variable models. Supports all
standard parameterizations for latent (cov/chol/prec/ggm), residual (cov/chol/prec/ggm),
and random effects (chol/cov/prec/ggm/cor) structures.
psychonetrics 0.14.18
- Standard errors now auto-fallback to approximate SEs when the Fisher information matrix
cannot be inverted (e.g., due to zero cells in crosstables in multi-group Ising models).
Previously this returned NA standard errors; now approximate SEs are computed automatically
with an informative warning
psychonetrics 0.14.17
- Fixed the 'equal' argument in multi-group lvm() models:
- The equal= argument (e.g., equal="lambda") now correctly constrains parameters across groups
- Previously, the equality flag was passed as a positional argument to fixMatrix, causing it to
be silently ignored
- Additionally fixed corrupted starting values when equal= is used: the lambda structure matrix
contains integer codes (>1) for equality constraints, but these were used as multipliers for
FA-derived starting values, producing extreme values that prevented convergence
- Both loadings and variance identification now work correctly with equal=
- Results from equal= now match those from the groupequal() workflow
- Added OLS-based starting values for the beta (structural) matrix in lvm()/sem():
- For each endogenous latent, OLS regression on FA-implied latent covariance provides initial
beta coefficients
- Disturbance covariance (sigma_zeta) is adjusted via (I-B)*Cov(eta)*(I-B)' instead of using
the raw FA factor correlations
- Guard rails for near-singular covariance, ill-conditioned predictor blocks, extreme
coefficients, and near-singular (I-B)
- Falls back to previous zero-initialization on any numerical issue
- New 'start' argument: "default" for OLS-based, "simple" for zero-initialization
- Added loop_psychonetrics() convenience function for parallel bootstrapping and simulations:
- Replaces boilerplate of makeCluster / clusterExport / parLapply / stopCluster
- Variables referenced in the expression are automatically detected and exported to workers
- Uses pbapply progress bars and safe cluster cleanup via on.exit
- Works with aggregate_bootstraps() and CIplot() for bootstrap analysis
- Added the NA2020 dataset: 8 self-report items (n=501) from Isvoranu (2020) for GGM examples
psychonetrics 0.14.16
- Added automatic lambda selection for PML/PFIML estimation via EBIC-based grid search:
- New function find_penalized_lambda() searches over a log-spaced grid of 50 lambda values
- Lambda now defaults to NA in all model constructors, signaling automatic selection
- runmodel() automatically triggers the grid search when NA lambdas are detected
- Lambda_max computed analytically from KKT conditions at the null-penalized model
- Warm starts across the lambda path for efficient computation
- Supports criterion selection: "bic", "ebic.25", "ebic.5" (default), "ebic.75", "ebic1"
- Users can still specify a fixed lambda (e.g., penalty_lambda = 0.1) for manual control
- Added beta_min thresholding for penalized estimates:
- Near-zero estimates below beta_min are set to exactly zero after convergence
- Default threshold: sqrt(log(p)/n) where p = number of penalized parameters
- Applied both during EBIC evaluation (for degree-of-freedom counting) and on the final model
- Reduces numerical noise from ISTA optimizer, improving specificity
- Exposed as user argument in runmodel() and find_penalized_lambda()
- EBIC for penalized model selection uses unpenalized log-likelihood at penalized estimates
(Convention A), with npar = unpenalized free params + nonzero penalized params after threshold
- Print and write methods now display lambda search results (selected lambda, criterion value,
beta_min) and show "auto (not yet selected)" for models with NA lambda before estimation
- NA-safe comparisons throughout the codebase for penalty_lambda column (print, write, refit,
penalize, unpenalize, C++ workspace builder)
psychonetrics 0.14.15
- Added PFIML (Penalized Full Information Maximum Likelihood) estimator:
- Combines FIML with L1/L2 penalty for penalized estimation with missing data
- Available for all Gaussian models (varcov, lvm, var1, dlvm1, tsdlvm1)
- Uses proximal gradient optimizer (same as PML)
- refit() switches PFIML back to FIML for post-selection inference
- Changed default missing data handling to missing = "auto" for all Gaussian models:
- ML automatically switches to FIML when missing data is detected
- PML automatically switches to PFIML when missing data is detected
- LS estimators (ULS, WLS, DWLS) default to listwise deletion
- Users can still override with explicit missing = "listwise" or missing = "pairwise"
- No change for Ising models (missing = "listwise" default remains)
- Penalty setup in model constructors now supports PFIML:
- estimator = "PML" and estimator = "PFIML" both trigger penalty initialization
- Baseline/saturated models use the appropriate unpenalized estimator (ML or FIML)
- Added experimental feature warnings for features introduced in the 0.14.x branch:
- PML, PFIML, LBFGS++ optimizer, ordinal data in lvm()/varcov(), WLSMV scaled test statistic
- Warnings appear once per session and only while version < 0.15
- Users are directed to report unexpected behavior to GitHub issues
psychonetrics 0.14.14
- Unified dlvm1() to accept both wide-format and long-format data:
- New 'datatype' argument with options "auto" (default), "wide", and "long"
- Auto-detects format from 'vars': matrix = wide, character vector = long
- New 'idvar' and 'beepvar' arguments for long-format data
- Long-to-wide conversion handled internally (previously required ml_tsdlvm1)
- Added 'standardize' argument to dlvm1() with options:
- "none" (default): no standardization
- "z": global z-scores per variable across all waves (does not impose stationarity)
- "quantile": quantile normalization across all waves
- "z_per_wave": independent z-scores per wave column (imposes stationarity)
- Added panellvgvar() as the canonical wrapper for panel latent variable GVAR models
- Deprecated functions (still work but emit warnings):
- ml_tsdlvm1() - use dlvm1() with long-format data instead
- ml_gvar() - use dlvm1() with within_latent="ggm", between_latent="ggm"
- ml_var() - use dlvm1() with desired within_latent and between_latent
- ml_ts_lvgvar() - use panellvgvar() instead
- panel_lvgvar() - use panellvgvar() instead
psychonetrics 0.14.13
- Added write_psychonetrics() function for exporting comprehensive model output to a text file:
- Produces Mplus/LISREL-style output with logo, sample info, model specification, parameter
estimates, fit measures, model matrices, modification indices, and logbook
- Intended for sharing as supplementary materials to papers
- Supports uncomputed models: shows starting values with clear warnings, omitting SEs, p-values,
fit measures, and modification indices
- Model matrices section shows only modeled matrices (not derived/implied quantities)
- Matrix output uses 2-digit rounding with dots for fixed zeros
- Fixed prune() error when no parameters to test in the requested matrix:
- adjust_p_values() now correctly returns a numeric vector (not the S4 model object) when nTest == 0
- Fixed backward compatibility for models saved without the WLS.Gamma slot:
- Added .hasSlot() guard to prevent "no slot of name 'WLS.Gamma'" warnings
psychonetrics 0.14.12
- Added WLSMV scaled test statistic (mean-and-variance adjusted chi-square) for WLS/DWLS/ULS estimators:
- Implements the scaled-shifted test (Asparouhov & Muthen, 2010) matching lavaan's WLSMV output
- New fit measures: chisq.scaled, df.scaled, pvalue.scaled, chisq.scaling.factor
- Scaled incremental fit indices: cfi.scaled, tli.scaled, nfi.scaled, rfi.scaled, ifi.scaled, rni.scaled, nnfi.scaled
- Scaled RMSEA: rmsea.scaled
- Baseline model also receives scaled test statistic (baseline.chisq.scaled, etc.)
- Added WLS.Gamma slot to store the full asymptotic covariance matrix (Gamma) per group
- Added LS_Gamma() helper function for computing Gamma from raw data
- Scaling factors match lavaan to 6+ decimal places for both ordinal and continuous data
- Supports single-group and multi-group models
- Changed the default estimator for ordinal data from WLS to DWLS, matching lavaan's convention
- Added "WLSMV" as an estimator alias for "DWLS" in lvm() and varcov()
- Fixed the DWLS weight matrix for ordinal data:
- Previously used diag(Gamma^{-1}) (diagonal of the full inverse), which differs from standard
- Now uses 1/diag(Gamma) (reciprocal of the diagonal), matching the lavaan convention
- Fixed (n+1)/n scaling bug in ULS fit function, gradient, and expected Hessian (R and C++):
- The weight matrix normalisation factor was (n+1)/n instead of 1, causing small systematic bias
psychonetrics 0.14.11
- Added corinput support to the lvm framework:
- lvm() now accepts a 'corinput' argument to specify that the input is a correlation matrix
- When corinput=TRUE, residual variances (diagonal of sigma_epsilon) are derived from the constraint diag(sigma) = 1
- Diagonal sigma_epsilon parameters are fixed and derived in the implied function, not freely estimated
- Supports WLS, DWLS, and ULS estimators; defaults to WLS when corinput=TRUE
- Variance identification is automatically enforced
- Both R and C++ code paths updated (implied and derivatives)
- Fixed a bug in the identity weight matrix dimension in samplestats for corinput/meanstructure combinations
- Fixed a bug in lvm Jacobian row removal when both corinput=TRUE and meanstructure=FALSE for continuous data
psychonetrics 0.14.10
- Added ordinal data support to the lvm framework (Muthen, 1984 three-stage estimator):
- lvm() now accepts an 'ordered' argument (character vector or TRUE) to specify ordinal variables
- Supports WLS, DWLS, and ULS estimators for ordinal CFA/SEM
- Polychoric correlations and thresholds are estimated from ordinal data
- Variance identification is automatically enforced for ordinal models
- Both R and C++ code paths (implied, prepare, derivatives) handle ordinal/continuous seamlessly
- Added nloptr_TNEWTON as a new optimizer, available via setoptimizer(x, "nloptr_TNEWTON"):
- Uses NLopt's preconditioned truncated Newton with restarts (NLOPT_LD_TNEWTON_PRECOND_RESTART)
- Builds a local quadratic model and solves the Newton system via preconditioned conjugate-gradient
- Supports box constraints and analytic gradients
- Added nloptr to Imports
- Fixed CRAN NOTE: commented out stray std::cout in vendored LBFGS++ header (LineSearchMoreThuente.h)
psychonetrics 0.14.9
- Tuned LBFGS++ convergence parameters for better performance on ill-conditioned models:
- Switched primary convergence criterion from gradient norm to relative function change (delta = 1e-8), matching nlminb behavior
- Increased L-BFGS history size from 6 to 10 for better Hessian approximation on larger models
- Relaxed gradient tolerance (epsilon = 1e-5) to avoid unnecessary iterations on ill-conditioned FIML problems
- ~4x faster on complex dlvm1/panel models (e.g., 8.6s -> 2.0s on 84-parameter panelgvar)
- Removed unused variable declarations to eliminate all compiler warnings from psychonetrics C++ source
psychonetrics 0.14.8
- Added LBFGS++ (LBFGSpp) as a new pure C++ optimizer, available via setoptimizer(x, "LBFGS++"):
- Vendored header-only LBFGS++ library (MIT license) into inst/include/
- Combined function+gradient evaluation exploits the prepareModel cache for efficiency
- ~1.6x faster than the existing roptim-based cpp_L-BFGS-B optimizer on benchmark models
- Added RcppEigen to LinkingTo for Eigen header support
psychonetrics 0.14.7
- Fixed R CMD check --as-cran issues:
- Guarded semPlot example with requireNamespace() since it is a Suggested package
- Replaced defunct dplyr::summarize_each_() with summarize(across()) in lvm example
- Added build artifacts and non-package files to .Rbuildignore
psychonetrics 0.14.6
- Fixed Ising model Fisher information for the beta (inverse temperature) parameter:
- Added missing beta factor in beta-threshold and beta-network cross-derivative blocks of the expected Hessian
- Fixed expectedmodel() to replace sample sufficient statistics with model-implied expectations for Ising models
psychonetrics 0.14.5
- Optimized dlvm1 Jacobian construction (d_phi_theta_dlvm1_group_cpp):
- Added dense Kronecker helpers (kronecker_I_X_dense, kronecker_X_I_dense) to avoid slow sparse element-by-element insertion when the result is used as a dense matrix
- Precompute the constant "between" Kronecker block once instead of recomputing it for every lag
- Pre-extract all grouplist values before the loop to eliminate repeated string-based hash lookups
- Fixed copy-paste bug: removed duplicate Jac.submat write for sigma_zeta_between in the lag loop
psychonetrics 0.14.4
- Fixed dplyr 1.1.0 deprecation warning about one-column matrices in filter() calls
psychonetrics 0.14.3
- Cache-aware prepareModel_cpp: skip redundant prepare when fit and gradient are called at the same parameter vector
- ~24% speedup in full model fitting (e.g., 9-variable CFA: ~155ms -> ~118ms)
psychonetrics 0.14.2
- Expanded OptimWorkspace to cache all constant S4 model/sample data (extramatrices, types, sample stats, etc.)
- All prepare_*_cpp and implied_*_cpp_core functions now read from cached workspace instead of S4 slots
- prepareModel_cpp post-processing now reads from workspace instead of S4 slots
psychonetrics 0.14.1
- Performance optimization: replaced O(nMat * nPar) parameter-to-matrix mapping with O(nFreePar) direct index mapping
- Eliminated S4 model cloning during optimization iterations by bypassing updateModel_cpp in all prepare functions
- Refactored all 8 implied_*_cpp functions into _core variants that accept pre-formed matrices
- All 8 prepare_*_cpp functions now use formModelMatrices_cpp_direct + implied_*_cpp_core pipeline
- Added OptimWorkspace: static cache for MatrixMapping, M matrix, and parameter indices across optimization iterations
- ~25% speedup in full model fitting (e.g., 9-variable CFA: ~200ms -> ~155ms)
psychonetrics 0.13.2 (2025-10-22)
- Fixed a bug in 'intersectionmodel' not properly clearing removed parameters
- Improved starting values for identity model in partialprune
- Fixed a bug in partialprune resulting in an uninformative error
- Switch from deprecated arma::is_finite() to std::isfinite()
psychonetrics 0.13.1 (2025-06-02)
- Bootstrapping now occurs before standardizing the data (thanks to Yiwei Yang and Amanda Montoya)
- Improved support for N=1 models in dlvm1 (e.g., for true ML estimation)
- Fixed a bug with missing values in the beta starting values matrix for dlvm1 models
- Changed partialprune() default for 'return' to "partialprune"; set to "best" to mimic old behavior
- Added 'final_prune' argument to partialprune() and changed the default to "saturated"; set to "partialprune" to mimic old behavior
psychonetrics 0.13 (2024-06-20)
- Added bootstrap support:
- Every model now supports 'bootstrap' (to enable bootstrap), 'boot_sub' (for subsampling) and 'boot_resample' (to resample)
- Enabling these will bootstrap the data *once* and create a psychonetrics object for the bootstrap sample
- The function 'aggregate_bootstraps' can be used to aggregate bootstraps:
- use print(...) for some general info
- use parameters(...) for the bootstrap results on the parameters
- use fit(...) for the bootstrap results on the fit indices
- use CIplot(...) for the bootstrap results on the confidence intervals (enable split0=TRUE if model selection is used)
psychonetrics 0.12 (2024-05-21)
- Updates for the dlvm1 model family:
- Changed the starting values for dlvm1 models for better convergence
- Added the 'start' argument to the dlvm1 model family. Use start = "version1" to use pre 0.12 start values
- Added the 'baseline' argument to dlvm1 model family, which now defaults to "stationary_random_intercept". Note: this will change incremental fit indices! To recover old behavior, set baseline = "independence"
- The log(det(kappa)) value used in fit function is now computed in FIML in the same way as computed in ML framework, this should lead to more stability, but might also lead to some different chi-square values
- The startreduce argument in prune() now defaults to 1
- Using "empty" for matrix specification is now deprecated; use "diag" or "zero" instead.
- getmatrix will no longer evaluate a model if it is not computed (now returns start values)
- the 'startreduce' argument in 'prune' now defaults to 1
- Added the 'fixstart' function that can aide in fixing startvalues
- The 'getmatrix' function now supports thresholded PDC matrices
- Diagnostic functions 'checkJacobian' and 'checkFischer' are now exported
- Added the transmod function, which can be used to transform one model type (e.g., using a Cholesky decomposition) to another model type (e.g., a GGM).
- Improved support for models with only one (latent) variable
- Added the 'logbook' function which can be used to more easily retrieve the psychonetrics logbook
- The 'partialprune' function has been updated and now allows for pruning specific matrices as well as returning different models
- Fixed some instances where the 'equal' argument wasn't working
- 'partialprune' now supports multiple matrices
- 'unionmodel' and 'intersectionmodel' now support the 'matrices' argument
- Changed lambda starting value choice to the following: Run an EFA, set the starting values to the loadigs of the factor that has the strongest loadings in the indicators, and set correlations between factors that use the same EFA factor for starting values to 0.8
- added warning for non computed model in parameters()
psychonetrics 0.11.5 (2023-10-03)
- Fixes to support Matrix 1.7-0
- The groupequal, groupfree, fixpar and freepar functions can now handle multiple rows/columns for symmetric matrices
- Fixed a bug in the identification function for ml_lvm models
- Implemented min_sum argument for estimating Ising models on data with a minimum sum score due to selection bias. This method is further discussed in Boot, de Ron, Haslbeck & Epskamp (in preperation). Correcting for selection bias after sum score selection in the Ising model.
- The 'warn_improper' argument in runmodel now defaults to FALSE, omitting the warning that a non-positive defnite matrix was encountered in parameter estimation.
- SEs are now set to NA if the Fischer information matrix could not be inverted normally
- Added an argument 'approximate_SEs' to runmodel to allow for SEs to be approximated using a pseudoinverse
psychonetrics 0.11 (2023-06-05)
- Optimised lambda starting value choice
- Changed warning for when a peusdoinverse was used in model estimation.
- Added 'threshold' argument to the matrix(...) function for significance thresholding
- Fixed a bug in psychonetrics:::emergencystart
psychonetrics 0.10.3
- Added a 'bootstrap' argument to var1() family to facilitate bootstrapping the data
- Fixed a bug where the saturated log-likelihood under FIML estimation could be incorrect
psychonetrics 0.10.2
- Built in a check to find NA sample covs
- added diag argument to getmatrix to obtain diagonal
- diagonal elements are now also thresholded when using getmatrix(..., threshold = ...)
- Added 'threshold' argument to getmatrix()
- Adjusting p-values is now done by default only for the tested p-values. This means that the number of tests is assumed to be the number of parameters of interest (matrices that are to be pruned), not the total number of parameters. Previous behavior can be reproduced with mode = "all" in prune(..).
- panelvar and panelgvar now support arguments within_latent and between_latent
psychonetrics 0.10.1
- Fixed a bug in 'prune' which did not allow intercept/mean vectors to be pruned
psychonetrics 0.10 (2021-10-25)
- ml_tsdlvm1 now orders data by idvar if beepvar is not supplied.
- Added 'CIplot' function
- Standardizing in ts_dlvm1 is now more stable.
- dlvm1 now uses observed variable names as latent variable names when appropriate.
- Fixed a bug with the beepvar argument in psychonetrics
psychonetrics 0.9 (2021-02-23)
- Changed 'rel.tol=1e-10' in the nlminb optimizer to be in line with lavaan
- Added 'warn_improper' argument to runmodel(..)
- The argument 'return_improper' now defaults to TRUE
- Optimizer no longer uses a spectral shift when inverting a semi-positive definite matrix
- Detection of non positive definite matrices is now done faster and should lead to less problems
- Added the 'bounded' argument to runmodel() to define if bounded estimation should be used (defaults to TRUE)
- Diagonals of symmetrical matrices can no longer be estimated to be negative (avoiding Heywood cases, although doing this with a Cholesky decomposition is nicer!)
psychonetrics 0.8.1 (2021-01-28)
- The nlminb estimator now uses more iterations.
psychonetrics 0.8 (2020-10-26)
- The log determinant is now computer better to be able to include fit measures at higher model complexities
- The 'return_improper' argument in runmodel( ) now returns improper estimates without trying different starting values
- Fixed a bug in meta_varcov when using individual estimates of the sampling variation
- Updated optimizer default control parameters
psychonetrics 0.7.6
- Model estimations that used improper estimation (pseudoinverse of variance-covariance matrix) now return with an error unless return_improper = TRUE in runmodel()
- Equality-free MIs are now also computed when all edges are included
psychonetrics 0.7.5
- Fixed a bug with nu_zeta being constrained in multigroup LGC models.
- factorscores now supports multi group models
psychonetrics 0.7.3
- Added the 'covariates_as' argument to latentgrowth() to model covariates with regressions (now default) or covariances
psychonetrics 0.7.2 (2020-06-24)
- Fixed a bug with removing diagonal elements of sigma_epsilon with single indicators
- Fixed another bug with models with only one free indicator
- Added the partialprune function for partially pruning multi-group models
psychonetrics 0.7.1 (2020-04-15)
psychonetrics 0.7 (2020-04-01)
- Major changes
- Major restructuring of underlying core code. Most vital functions have been translated to C++, leading to a large speedup!
- Added C++ based optimizers cpp_L-BFGS-B, cpp_CG, cpp_SANN, and cpp_Nelder. These are faster but slightly less stable than nlminb (now the default optimizer).
- Verbose now defaults to FALSE everywhere. This can be set with the setverbose function for a model
- Added meta-analysis model for varcov family (meta_varcov)
- The ml_tsdlvm1 function now allows for multi-kevek tsdlvm1 models (dlvm1 models) to be specified using syntax familiar to those using mlVAR and graphicalVAR
- Minor changes
- Numerous small bugfixes and improvements (e.g., better starting values)
- Added function 'fullFIML' to use true FIML computing the likelihood per row
- Changed 'WLS.V' to 'WLS.W'
- Several warning messages have been updated
psychonetrics 0.6 (2020-02-21)
- The 'dlvm1' model family now also returns the implied latent variance-covarriance matrix as 'sigma_eta_within'
- The latent lag-1 matrix is also returned as 'sigma_eta_within_lag1'
- The dlvm1 model family now no longer requires a 'lambda' matrix to be specified (will default to a panelvar model)
- Most model families now support the 'standardized' argument, which can be set to 'z' for z-scores (helps convergence) or 'quantile' for a semiparametric transformation
- Added the ml_lvm family for two-level random intercept latent variable models
- Added the simplestructure function to easily make a lambda matrix
- addalpha in modelsearch now works as expected
- addalpha and prunealpha now default to 0.01 in 'modelsearch'
- recursive now defaults to FALSE in 'prune'
psychonetrics 0.5.1 (2020-01-26)
- Fixed a bug with responses being missing when summary statistics are used in Ising()
- Fixed a bug with covtype sometimes being set to UB when corinput = TRUE
- Added DOI to description field
psychonetrics 0.5.0 (2020-01-25)
- The Ising model is now supported (only ML estimation) through the Ising() model function
- Added the covtype option to several models, controlling if maximum likelihood or unbiased estimates are used for the input covariances
- Added the function 'covML' for maximum likelihood covariance estimates
psychonetrics 0.4.1 (2019-12-18)
- Small fix for CRAN checks
psychonetrics 0.4 (2019-12-17)
- type = "cor" is now supported in varcov, with rho representing the correlations and SD the diagonal standard deviations matrix.
- The 'corr' function is now implemented as shorthand for varcov(..., type = "cor")
- The scale of the Fisher information matrix has been adjusted to portray unit information to be similar to Lavaan
- The getVCOV function has been added to obtain the estimated asymptotic var-cov matrix of the parameters.
- The meanstructure can now be ignored using meanstructure = TRUE in the following model families:
- varcov
- A correlation matrix can now be used as input (detected or set with corinput = TRUE) for the following families:
- varcov (type = "ggm" and type = "cor")
- The WLS estimator will now not investigate means when meanstructure is ignored, and variances when a correlaton matrix is used as input.
- The WLS weights matrix must be of the appropriate dimensions!
- The WLS.V matrix will no longer be adjusted for missing means.
- Added 'startEPC' argument to stepup and freepar
- Added the 'modelsearch' function for extensive stepwise model search
- Fixed several bugs and improved starting values in several models
psychonetrics 0.3.3 (2019-07-06)
- prune() now removes diagonal values of temporal effects
- psychonetrics now requires R 3.6
- Some C++ fixes for Solaris
psychonetrics 0.3.2 (2019-06-28)
- The parameters function now invisibly returns the parameter estimate data frame
- The MIs function now invisibly returns a data frame with MI estimates
- fit now invisibly returns a data frame with fit measure estimates
psychonetrics 0.3.1
- Several help-files are now updated with executable examples
psychonetrics 0.3.0
- First version to be submitted to CRAN