net_boot(),
difference_test(), net_stability(),
net_casedrop_reliability(),
net_split_reliability(), and their plot methods) now
demonstrate the workflow with the closed-form
method = "pcor" estimator and note that the
"glasso" default with full iteration counts is what a real
analysis should use. net_compare() examples use fewer
permutations on smaller data.method = "pcor".devtools::test()); the frozen 1e-12 golden estimator
baselines still run on CRAN.ising_fit(),
mgm_fit(), moderated MGM with native = TRUE)
now returns exact zeros for inactive lasso coordinates. Coordinate
descent could leave a BLAS-rounding crumb (~1e-17) on a coordinate whose
gradient sits exactly at the penalty boundary; whether the crumb was
0.0 or 4e-17 differed between BLAS
implementations, and the AND rule then counted it as an edge on some
platforms but not others. The EBIC degrees of freedom already ignored
such coefficients (|beta| <= 1e-10); the returned
support now agrees. One golden-fixture edge changes: the base-kernel
mixed-data mgm network loses a spurious weak edge, matching the glmnet
reference path, which always reported that edge as absent.cor_auto() now rejects variable pairs with fewer than
two joint observations instead of optimizing an empty polychoric
likelihood. Marginal-correlation p-values use pair-specific effective
sample sizes; full-order partial-correlation inference rejects
heterogeneous pairwise sample sizes.estimator_args carries estimator options whose names
collide with diagnostic options. The legacy
net_boot(engine=) argument is translated to the public
native switch.p = 1.certified = NA. GGM objects retain the pre-threshold
residual in $fit_kkt.nlambda and lambda_min_ratio; moderated MGM
honors both LW and HW. MGM consistently stores
$levels, and prediction accepts original two-level factor
columns.Unblocks Nestimate’s delegation of its cross-sectional psychometric
estimators to psychnets (see
AGENT-NOTE-NESTIMATE-DELEGATION.md, gaps A and B). Default
numeric output of every estimator is unchanged; all new
behaviour is opt-in. Two functions are renamed — see
below.
casedrop_reliability() is now
net_casedrop_reliability().network_reliability() is now
net_split_reliability().The old names are removed, not deprecated. Both were
also exported by Nestimate with a different first argument (a
fitted network there, raw data here), so library() load
order silently decided which one a user got and the failure surfaced as
an argument-matching error inside their own code. Nestimate is the older
package and now depends on psychnets, so psychnets is the side that
renames. An erroring alias would not have fixed anything — psychnets
would still own the name and still shadow Nestimate’s verb.
Neither the arguments, the return value, nor the numbers changed;
only the name. Update calls with a search and replace. The new names
also bring both verbs into the net_* style every other
framework verb already uses.
net_edge_betweenness() is not renamed:
both packages give it the same contract, so it is genuinely one verb
rather than a collision.
ebic_glasso()
can now serve a resampling callerNew lambda_path = argument: scan a caller-supplied
penalty grid, in the order given, instead of recomputing one from the
input correlation matrix. Resampling callers compute the path once from
the original matrix and reuse it for every resample, so selected
penalties stay comparable across draws rather than drifting with each
sample’s largest absolute correlation.
New penalize_diagonal = argument (default
FALSE, unchanged behaviour). glasso_kkt()
gained a matching argument: with a penalized diagonal the optimality
condition becomes W_ii = S_ii + rho, so grading such a fit
with the default would report a spurious violation of exactly
rho. Verified against the glasso Fortran
package to ~1e-11.
New refit = argument. TRUE (default)
keeps today’s behaviour of re-solving the selected penalty to machine
precision. FALSE returns the path fit exactly as scanned –
a bit-compatibility switch for consumers holding frozen baselines
against an unrefitted path scan, not a performance knob.
"unregularized" keeps the selected support and refits the
unpenalized graph-restricted MLE on it, certified by
ggm_support_kkt() instead of glasso_kkt().
$support is stored on the object in that case
only.
mgm_fit()
supports multi-level categorical nodesmgm_fit(native = FALSE) now models a
factor/character column, or a numeric column
with more than two levels, as a single k-level categorical node.
Previously these errored, and the suggested workaround (one-hot
encoding) was not equivalent: it turns one k-level node into k separate
nodes, a different model. Matches mgm::mgm() to ~1e-15 with
exact structure on 3- and 4-level factors.
Node type detection now follows mgm::mgm()’s own
rule: a factor/character column is
categorical, and so is a numeric column with 10 or fewer distinct
integer values. A numeric column promoted by that integer rule is
warned about by name (condition class
psychnet_type_promotion), because it switches the node from
gaussian to categorical – a different model, not a different encoding.
Likert and count items hit this rule routinely. No previously-working
call changes its result: every input newly classified as categorical
raised an error before, and constant / all-NA columns are still dropped
as before. A character column with more than 10 distinct
values is refused as an identifier (use factor() to
insist), and a column declared "c" via types =
with more than 10 distinct values is refused as a mislabelled continuous
variable rather than routed to a multinomial.
na_method applies identically on both engines: the
glmnet path is fed the same imputed (or listwise-reduced) rows as the
base path, and a missing multi-level code is imputed with the modal
level rather than a mean.
The certificate of a multi-level node is the multinomial
(softmax) stationarity residual. Grading each class as an independent
logistic regression reported ~0.2 for a fit glmnet had converged; the
true residual is ~1e-5. Related: on the glmnet paths
(mgm_fit / ising_fit,
native = FALSE) a node whose EBIC-selected model is
empty now certifies as optimal (residual ~0) instead of
reporting its whole gradient as a violation – the empty solution is
exact at glmnet’s own lambda_max. Only $kkt
changes; weights are untouched.
The resampling verbs (net_boot(),
net_stability(), net_casedrop_reliability(),
net_split_reliability()) now hand each draw to the
estimator exactly as the full data would be: a
data.frame keeps its factor columns and its incomplete rows, a matrix is
coerced without dropping rows. So resampling
mgm_fit(native = FALSE) keeps a factor node (previously it
was silently dropped), and na_method = "pairwise" – every
estimator’s default – resamples the rows a direct call would use instead
of a listwise-reduced subset (previously the verbs listwise-deleted up
front, so the bootstrapped network was not the network
psychnet() returns on the same data). Node labels are read
off the observed fit. For method = "mgm" node types are
decided once on the full data and pinned via types= for
every draw, so a borderline column cannot flip between gaussian and
categorical from draw to draw, and the promotion warning is raised once
per verb call. Complete numeric data is byte-identical to
before.
mgm_fit(): constant / all-NA columns are dropped
before anything is indexed against the columns, and
moderators (a positional index) and a labels
vector given for all original columns now follow that drop; a moderator
that is itself constant is refused by name.
na_method = "listwise" is applied first, so a node left
with one level after deletion is dropped like any other constant column
on both engines. An explicit factor with more than 10
levels is accepted (that is the documented escape hatch); only
character columns and types = "c" declarations
on non-factor columns are subject to the 10-level guard.
A 2-level column is recoded through its factor levels, so any
binary coding ({1,2}, {"no","yes"}) now works
and gives the same fit as {0,1}.
The base kernel (native = TRUE) is a binomial solver
and still refuses a node with more than two levels, now naming the
column and pointing at native = FALSE.
net_predict() refuses an mgm network containing a
multi-level categorical node: a k-level predictor occupies k-1 design
columns, so there is no one-coefficient-per-variable matrix to predict
from. It errors rather than computing from a truncated model.
Node order is now a documented guarantee, not observed behaviour:
$weights, $nodes, and $precision
follow the input column order and are never sorted. Stated in
?ebic_glasso, ?cor_network,
?pcor_network, ?mgm_fit and pinned by a test
using non-alphabetical names.
A numerical-stability policy is now documented:
estimator numerics are stable within a minor version; any change to a
selected support, or to a weight beyond 1e-10, ships in a minor bump and
is listed here. Enforced by tests/testthat/test-golden.R
against frozen fixtures at 1e-12, so downstream consumers can pin
psychnets (>= x.y) against the promise.
net_crosswalk() gained rows for
penalize.diagonal, refit, and
lambda_path, which it previously listed as
reference-only.
First CRAN release.
mgm_fit(..., moderators = ) (the moderated mixed
graphical model, read with condition()) now runs on
psychnets’ own base-R penalized-IRLS kernel and is KKT-certified. It
previously required the compiled glmnet package and
silently ignored native =. Every estimator in the package
is now pure base R by default; glmnet and
glasso remain optional reference engines selected with
native = FALSE.
The base moderated kernel covers gaussian and binary nodes (the
documented mgm_fit() scope) and errors explicitly on a
categorical node with more than two levels, which needs
native = FALSE.
The two resampling-heavy long-form guides, “Network reliability” and “Visualizing networks”, are now website articles rather than installed vignettes, so they keep their full iteration counts while the package check stays inside CRAN’s time budget. Read them at https://pak.dynasite.org/psychnets. The eight remaining vignettes are unchanged and still ship with the package.