nmf-sem-with-nmfkc.Rmd vignette code now
reference the canonical nmf.ffb.* aliases
(nmf.ffb(), nmf.ffb.cv(),
nmf.ffb.DOT()) instead of the legacy nmf.sem.*
names. Both names continue to work; the change only affects what users
see on the GitHub Pages homepage and in the vignette source.nmf.ffb* family added as the canonical alias for
nmf.sem* (Satoh 2025, arXiv:2512.18250 adopts “NMF-FFB” —
Non-negative Matrix Factorization with Feed-Forward + Feedback — as the
model’s canonical name). nmf.sem* continues to work and
shares the same return classes (c("nmf.ffb", "nmf.sem") and
c("nmf.ffb.inference", "nmf.sem.inference", ...)), so
existing scripts are unaffected.nmf.sem.inference() / nmf.ffb.inference():
replaced the legacy 1-step Newton wild bootstrap with a full
X-fixed pair bootstrap. Resamples columns of (Y1, Y2), refits
(C1, C2) with X held at the original fit, and reports per-element
support_rate = mean(|c_b| > threshold) together with
percentile CIs. Significance markers (* / ** /
*** at sup > 0.95 / 0.99 / 0.999) follow the lavaan
convention. Both Theta_1 (feedback) and Theta_2 (exogenous) are
inference targets (previous version covered only Theta_2).nmf.sem() / nmf.ffb(): now runs
nmfkc(Y1, A = Y2) internally by default
when X.init is a string method, forwarding
X.init, X.L2.ortho, epsilon,
maxit, seed. The feedforward fit is used both
as the X warm-start and as the baseline for SC.map.
nmfkc.baseline = FALSE opts out.nmf.sem.inference(): fixed dimension bug in the
Leontief identity matrix (I_mat <- diag(Q) should have
been diag(P1)); previously every replicate was silently
marked invalid when P1 != Q.nmfkc.net(): now auto-masks NA entries of
Y (parity with the other four NMF variants); previously
errored at the min(Y) < 0 check when Y
contained NA.nmfkc(): Fixed C matrix asymmetry in tri-symmetric NMF
(Y.symmetric = "tri"). The C update was using stale B and
XB computed from the old X; now B and XB are recomputed after X is
updated. Also fixed column reordering to permute both rows and columns
of C. Previously the relative asymmetry could reach ~46%; now it is at
machine precision (~1e-14).Y.weights semantics unified to lm()-style
weighted least squares across nmfkc(),
nmfae(), nmfkc.net(),
nmfkc.signed(), nmfae.signed(): loss is now
sum(W * (Y - Yhat)^2) (linear in W, matching
lm()’s weights argument). Binary masks (W ∈
{0, 1}; the standard ECV / NA-mask case) are unaffected since W =
W^2."maximum iterations (N) reached..." warning when
maxit is exhausted without meeting the relative- tolerance
criterion (previously silent in nmfae,
nmfae.signed, nmfkc.net,
nmfkc.signed, nmfre, and
nmf.sem).maxit = 5000 as the default
(was 5000 / 20000 / 50000 inconsistently). Together with the maxit
warning above, users see explicit feedback when 5000 is insufficient and
can opt into a larger cap..init_X_method() for X
initialization via "nndsvd" / "kmeans" /
"kmeansar" / "runif" / numeric matrix. All NMF
families now use the same dispatch logic; previous ad-hoc inline
implementations are removed.nmf.sem() returns SC.map (input-output
structural fidelity: correlation between the equilibrium operator and
the feedforward baseline mapping; Satoh 2025 §4.SC.map) automatically
when nmfkc.baseline is supplied or computed
internally.summary.nmf.sem(): rewritten to display the
full-bootstrap inference output — separate Theta_1 / Theta_2 blocks with
Estimate | CI_low | CI_high | support | Pr(>0) | sig,
plus a bootstrap meta-info header.coef.nmf.sem(): now returns a long-format data frame
with rows for every entry of both C1 and C2
(Type | Basis | Covariate | Estimate); previously
returned only the C2 matrix when no inference had been run. Schema
matches the inference-augmented output for uniformity.plot.nmf.sem(): default trace is now
objfunc.full (loss + penalties — the actual
monotonically-decreasing quantity that the multiplicative updates
minimize) instead of objfunc (reconstruction only). New
argument which = "full" | "reconstruction" | "both".nmf.sem.DOT(): significance stars now appear on Theta_1
(feedback Y1 → F) edges in addition to Theta_2 (exogenous Y2 → F); X (F
→ Y1) edges remain unstarred since the basis is not the inference
target.plot.nmfae.ecv(): Heatmap cell text color is now always
black for better readability on light-colored cells.nmfkc(): X.init = "runif" now supports
nstart > 1 for multi-start initialization. Multiple
random starting points are evaluated with 10 standard NMF iterations,
and the best (lowest Frobenius error) is selected.nmfae(), nmfre(): r.squared
is now computed as cor(Y, fitted)^2 (squared correlation
between observed and fitted values), consistent with
nmfkc(). Previously nmfae() used
1 - SS_res/SS_tot and nmfre() used the same
regression-style R-squared, which can behave unexpectedly for
intercept-free non-negative models.nmfkc.kernel.beta.nearest.med(): added a
candidates argument controlling the bandwidth grid.
Options: "7points" (new default,
t = {-1,-2/3,-1/3,0,1/3,2/3,1}), "4points"
(t = {-1/2, 0, 1/2, 1}), or a user-supplied numeric vector
of values. Previously the grid silently differed between the no-landmark
(Uk = NULL; 4 points) and landmark (7 points)
branches.nmfkc.signed(): NMF-KC with signed
covariate/coefficient. Model with , (signed), real-valued. Uses Ding et
al. (2010) sign-splitting + Direct MU; may also contain negative entries
(semi-NMF regression). Supports Y.weights for element-wise
masking.nmfkc.signed.cv(), nmfkc.signed.ecv():
column-wise and element-wise k-fold CV for rank selection on signed
data.nmfae.signed(): Three-layer autoencoder with . preserve
soft clustering on both decoder and encoder sides while the bottleneck
can carry negative weights (e.g., anti-correlated properties). Hybrid
warm-start (from nmfae()) + Direct MU with
multi-restart.nmfae.signed.ecv(): element-wise CV for (decoder-rank,
encoder-rank) selection.nmfae.signed.inference(): sandwich SE + wild bootstrap
for (no non-negativity projection on since it is signed).predict.*.signed(),
plot.*.signed(), summary.*.signed(), and
nmfae.signed.rename() helper.nmfkc.net(): Single unified entry point for symmetric
NMF of network data, with type = "tri" | "bi" | "signed".
All three variants use the Frobenius-full bilateral gradient (supersedes
the one-sided approximation in nmfkc(Y.symmetric = ...)).
type = "signed" supports signed via Ding et al. (2010)
sign-splitting, preserving for soft clustering while allowing
inter-cluster repulsion. The returned object’s fields are uniform across
types: and are for tri/bi, and populated matrices for signed. is always
populated (identity for bi, non-negative for tri, signed for
signed).nmfkc.net.ecv(): Element-wise cross-validation with
upper-triangle folds (mirrored to the lower triangle to prevent symmetry
leakage). Unified entry point for
type = "tri" | "bi" | "signed" (calls
nmfkc.net() with the matching type for each
fold).nmfkc.net.DOT(): Graphviz DOT visualization for
symmetric NMF networks. Displays basis-to-node membership edges and
inter-basis interaction edges (C matrix) with significance stars. Now
has signed parameter (auto-detected from class) to render
negative C entries as dashed edges.nmfkc.net.inference(): Statistical inference for
symmetric NMF. Wrapper around nmfkc.inference() with
A = t(X). Returns off-diagonal C coefficients with sandwich
SE and wild bootstrap.nmfkc(Y, Y.symmetric = "bi"|"tri"):
Deprecated in favor of
nmfkc.net(Y, type = "bi"|"tri"). The old implementation
uses a one-sided gradient approximation that empirically converges for
but is theoretically incorrect and does not extend to signed . The
deprecated branch still works in v0.6.8 (with a deprecation warning) and
will be removed in a future release.nmf.sem.DOT(): weight_scale_y2f →
weight_scale_c2, weight_scale_fy1 →
weight_scale_x1 (matrix-name-based naming, consistent with
nmfae.DOT() and nmfkc.DOT()).nmf.sem.DOT(): sig.level moved to after
threshold for consistency with other .DOT
functions.@title /
@description updated to use NMF-FFB as the
canonical model name (with “(formerly NMF-SEM)” attached on first
mention for discoverability of the legacy term). File names
(R/nmf.sem.R,
vignettes/nmf-sem-with- nmfkc.Rmd,
man/nmf.sem.Rd), function names (nmf.sem*),
and S3 classes ("nmf.sem") are unchanged so URLs and
existing scripts continue to work.fitted.nmfae() and residuals.nmfae()
S3 methods; previously fitted() on an nmfae
object silently returned NULL because the wrong field name
($XB instead of $Y1hat) was used.Basis / Covariate columns (was
Factor/Exogenous in
nmf.sem.inference(),
Decoder/Encoder in
nmfae.inference()).wild.B = 500,
wild.seed = 123 across all inference functions..DOT functions renamed to
result for consistency.nfolds, seed,
shuffle) moved to ... in
nmfkc.ecv(), nmfae.ecv(),
nmfae.cv(), nmf.sem.cv(); div
also accepted for backward compatibility.nmfkc.criterion(): Extracted criterion computation from
nmfkc() as a standalone exported function. Supports
detail = "full" / "fast" /
"minimal" to control computation cost.nmfre.inference(): Separated statistical inference from
nmfre() optimization. Returns coefficient table with SE,
z-values, and p-values via wild bootstrap.nmf.sem.inference(): Statistical inference for the C2
parameter matrix in NMF-SEM. Uses sandwich SE and wild bootstrap.coef(), fitted(),
residuals() for all model classes (nmfkc,
nmfae, nmfre, nmf.sem).plot() for nmfre and
nmf.sem (convergence diagnostics).summary.nmf.sem(): Stability diagnostics, fit
statistics, and C2 coefficient table.nmfkc(), nmfkc.rank():
save.time / save.memory →
detailnmfae(): Q → rank,
R → rank.encodernmfre(): Q → rank,
dfU.cap.rate → df.ratenmfre.dfU.scan(), nmfkc.ar.degree.cv():
Q → ranknmfkc.residual.plot(): Y_XB_palette →
fitted.palette, E_palette →
residual.palettenmfkc.kernel.beta.nearest.med():
block_size → block.size,
sample_size → sample.sizehide.isolated option added to all .DOT
functions (default TRUE).nmf.sem.DOT(): Added sig.level parameter;
C2 edges decorated with significance stars.nmfkc(): Added X.restriction = "none"
option and X.init = "kmeansar" initialization.@section Lifecycle: Experimental added to
nmfae().mc.cores parallel option from
nmfae.ecv() for CRAN compliance.T shadowing TRUE in
information criterion computation.nmfkc.ecv() to use KL divergence for evaluation
when method="KL".save.time=TRUE) to
nmfkc.ecv() inner calls.nmfkc.rank() elbow normalization
when R-squared values are identical.rank → Q)
in nmfkc.rank() call to nmfkc.ecv().nmf.sem.split() when P=2.n.exogenous in
nmf.sem.split().summary.nmfkc() and
print.summary.nmfkc().@return for plot.nmfkc() and
predict.nmfkc().@return items (method,
n.missing, n.total, rank,
mae) to nmfkc().T/F with
TRUE/FALSE.1:length() with seq_along().nmf.sem.cv() defaults with
nmf.sem().Harmonized all DOT-generating functions
(nmf.sem.DOT, nmfkc.DOT,
nmfkc.ar.DOT) for consistent structure, naming conventions,
and visualization logic.
Standardized node and edge formatting rules, including unified cluster behavior, color schemes, and edge-scaling conventions.
Implemented threshold-aware coefficient labeling so that displayed numerical precision aligns with the visualization threshold, preventing misleadingly detailed labels.
Removed unused or redundant DOT fragments and improved compatibility across Graphviz engines.
Enhanced layout readability through consistent indentation, node
grouping, and suppression of isolated nodes in specific visualization
modes (e.g., type = "YA" in
nmfkc.DOT).
Refactored and expanded internal DOT helper functions
(.nmfkc_dot_format_coef,
.nmfkc_dot_digits_from_threshold,
.nmfkc_dot_cluster_nodes, etc.) for better maintainability
and uniform behavior.
New Function: Implemented
nmfkc.ecv() for Element-wise Cross-Validation (Wold’s
CV).
rank to evaluate multiple
ranks simultaneously.Missing Value & Weight Support:
nmfkc() and nmfkc.cv() now fully support
missing values (NA) and observation weights via the hidden
argument Y.weights (passed through ...).Y contains NAs, they are automatically
detected and masked (assigned a weight of 0) during optimization.Rank Selection Diagnostics
(nmfkc.rank):
save.time defaults to FALSE, enabling the
robust Element-wise CV calculation by default.Argument Standardization:
rank across all
functions (nmfkc, nmfkc.cv,
nmfkc.ecv, nmfkc.rank).Q is still supported for backward
compatibility but internally mapped to rank.Summary Improvements:
summary() and print() methods to
report:
Other Improvements:
nmfkc.ar() to ensure the
input Y has no missing values (as they cannot be propagated
to the covariate matrix A in VAR models).nmfkc.residual.plot() layout margins for better
visibility of titles.Regularization Update:
The regularization scheme has been revised from L2
(ridge) to L1 (lasso-type) penalties.
gamma now controls the L1 penalty on the
coefficient matrix ( B = C A ), promoting sparsity in
sample-wise coefficients.lambda has been added to control the
L1 penalty on the parameter matrix ( C ), encouraging
sparsity in the shared template structure....) to
nmfkc() and related functions.Function Signature Simplification:** Many less-frequently used
arguments in nmfkc() (e.g., gamma,
X.restriction, X.init) and in
nmfkc.cv() (e.g., div, seed) have
been moved into the ellipsis (...) for a cleaner function
signature.
Performance Improvement: The internal function
.silhouette.simple was vectorized and optimized to reduce
computational cost, particularly for the calculation of
a(i) and b(i).
Removed the fast.calc option from the
nmfkc() function.
Added the X.init argument to the
nmfkc() function, allowing selection between
'kmeans' and 'nndsvd' initialization
methods.
The penalty term has been changed from tr(CC') to
tr(BB') = tr(CAA'C').
Implemented the internal .z and xnorm
functions.
Added the fast.calc option to the nmfkc()
function.
Optimized internal calculations for improved performance.
Updated citation("nmfkc") and added AIC/BIC to the
output.
Implemented the nmfkc.ar.stationarity()
function.
Modified the z() function.
Used crossprod() for faster matrix
multiplication.
Implemented the nmfkc.ar.DOT() function.
Added logic to sort the columns of X to form a unit
matrix in special cases.
Implemented nmfkc.kernel.beta.cv() and
nmfkc.ar.degree.cv() functions.
Set the default column names of X to
Basis1, Basis2, etc.
Added X.prob and X.cluster to the
return object.
Skipped CPCC and silhouette calculations when
save.time = TRUE.
Added a prototype for the nmfkc.ar()
function.
Added the criterion argument to the
nmfkc() function to support multiple criteria.
Updated the nmfkc.rank() function.
Added the criterion argument to the
nmfkc.rank() function.
Implemented the save.time argument.
Implemented the nmfkc.rank() function.
Implemented the nstart option from the
kmeans() function.
Added an experimental implementation of the
nmfkc.rank() function.
Removed zero-variance columns and rows with a warning.
Added source and references to the documentation.
Renamed several components for clarity:
nmfkcreg to nmfkccreate.kernel to nmfkc.kernelnmfkcreg.cv to nmfkc.cvP to B.probcluster to B.clusterunit to X.columntrace to print.tracedims to print.dimsAdded the r.squared argument to the
nmfkcreg.cv() function.
In nmfkcreg():
dims argument to check matrix sizes.unit argument to normalize the basis matrix
columns.Modified the create.kernel() function to support
prediction.
Updated examples on GitHub.
Removed the YHAT return value; use XB
instead.
Added the cluster return value for hard
clustering.