Skip to content

nmfkc

0.9.8Package record

Non-Negative Matrix Factorization with Kernel Covariates

0packages depend
2.4Kdownloads / year
28.4%test coverage
13/13flavors OK

Overview

About

Performs Non-negative Matrix Factorization (NMF) with Kernel Covariates. Given an observation matrix and kernel covariates, it optimizes both a basis matrix and a parameter matrix. Notably, if the kernel matrix is an identity matrix, the method simplifies to standard NMF. Also provides NMF with Random Effects (NMF-RE) via nmfre(), which estimates a mixed-effects model combining covariate-driven scores with unit-specific random effects together with wild bootstrap inference, and NMF-based Structural Equation Modeling (NMF-SEM) via nmf.sem(), which fits a two-block input-output model for blind source separation and path analysis. References: Satoh (2025) doi:10.48550/arXiv.2403.05359; Satoh (2026) doi:10.1007/s42081-026-00349-x; Satoh (2025) doi:10.48550/arXiv.2512.18250; Satoh (2026) doi:10.48550/arXiv.2603.01468; Satoh and Tokuda (2026) doi:10.48550/arXiv.2607.27474; Satoh (2026) doi:10.1007/s42081-025-00314-0.

Changes in 0.9.8

The deprecated nmfae* names are removed

The fifteen nmfae* / nmfae.signed* forwarders are gone: nmfae(),

nmfae.inference(), nmfae.ecv(), nmfae.cv(), nmfae.rank(),

nmfae.DOT(), nmfae.heatmap(), nmfae.kernel.beta.cv(), nmfae.rename()

and their six nmfae.signed* counterparts. Use the nmf.rrr* /

nmf.rrr.signed* names, which they forwarded to. Deprecated in 0.8.8, carried

through two CRAN releases with a .Deprecated() note.

**The nmfae S3 classes stay.** They are where this family's methods are

defined, nmf.rrr() still returns c("nmf.rrr", "nmfae", "nmf"), and

summary() still returns "summary.nmfae". Nothing about dispatch changes, so

code that inspects the class of a fit, and objects saved by an earlier version,

keep working. Renaming that layer to nmf.rrr is a separate change, for a

Show all changes in 0.9.8Hide the rest of the changes in 0.9.8

release that is not three days from a submission.

The deprecated nmf.sem* names are removed

nmf.sem(), nmf.sem.inference(), nmf.sem.cv(), nmf.sem.split() and

nmf.sem.DOT() are gone, together with the six S3 methods registered on the

nmf.sem classes. Use nmf.ffb() and its family; the names map one to one and

nothing else changes, because the removed functions were pure forwarders

(nmf.sem <- function(...) { .Deprecated("nmf.ffb"); nmf.ffb(...) }).

They were deprecated in 0.8.8 (2026-07-14) and have emitted a .Deprecated()

note through two CRAN releases, 0.8.8 and 0.9.6. No package on CRAN depends on

nmfkc, so nothing else is affected.

Fitted objects no longer carry the legacy class: nmf.ffb() now returns

c("nmf.ffb", "nmf") rather than c("nmf.ffb", "nmf.sem", "nmf"), inference

results drop "nmf.sem.inference", nmf.ffb.DOT() drops "nmf.sem.DOT", and

summary() returns "summary.nmf.ffb" alone. **Code that tests for those class

strings, and objects saved by an earlier version, will no longer dispatch to

the nmf.sem methods** -- which is the point of removing them, but it is worth

knowing before loading an old .rds.

NMF-FFB: the help pages no longer describe options that were removed

C1.restriction accepted "block" and "cross" while the exclusion

restriction was being settled; both were dropped in 0.9.8 in favour of

"union", but the help pages and the vignette still described them and still

listed them as admissible values. They now describe only what the code accepts,

"union" and "none" (or a user matrix).

The wording of the restriction itself is also corrected. It was "an outcome may

not feed back into a factor on which it loads", which reads as if any non-zero

loading blocked the entry; the rule is "a factor on which it has more than a

negligible loading" -- the dominant factor of the outcome, plus every factor

whose loading reaches C1.restriction.threshold. Neither half alone is the

rule, and the help now says why. The vignette adds the reference for reading a

BIC difference as evidence (Kass and Raftery 1995, p. 777).

The NMF-GMM family is marked experimental again

nmf.gmm(), nmf.gmm.inference(), nmf.gmm.select(), nmf.gmm.twostage()

and the S3 methods on their classes say on their help pages that they are

experimental and still under development: argument names, defaults and the

contents of the returned objects are not yet stable. The vignette says so too.

The notice was removed in 0.9.7 on the reasoning that publishing on CRAN is a

commitment to a fixed interface. That reasoning was premature — the family is

still being developed alongside the paper — so the notice is back, and now

covers nmf.gmm.twostage() and the S3 methods, which it had missed. Nothing

else in the package carries the caveat.

Every restriction on X that is not a gauge fix is removed

X.rowSums.min (both fitters) and X.restriction = "rowSums"

(nmfkc.signed()) are gone. Both acted on the **rows** of X, which changes

X %*% C %*% A, so neither was a gauge fix: the scale they removed could not

be handed to C, they sat outside the multiplicative form, and the objective

was no longer monotone.

  • The row-sum floor pushed the pinned rows below the floor and the projection lifted them back, so the objective alternated between two values until maxit (observed on Covertype at rank 6, 200,000 iterations).
  • "rowSums" has the same defect in a milder form: on a 10x30 example the objective rose at 45 steps, every second step from the 49th on. The oscillation is small enough that the fit still meets epsilon and stops, but it descends by luck rather than by construction.

Both were added after the last CRAN release, so nothing on CRAN is affected.

"rowSums" is refused with a message rather than silently re-mapped to

"colSums", which would change results without saying so. The remaining

restrictions -- "colSums" (default), "colSqSums", "totalSum", "none",

"fixed" -- are all gauge fixes and leave the updates monotone.

Both fitters now return epsilon.iter (relative change at the last step) and

objfunc.increases (steps at which the objective rose), and print() /

summary() show them, so a fit that is oscillating rather than descending is

visible. nmfkc.signed()'s converged is now the stopping rule's own test

rather than iter < maxit, so a run that stopped early on a non-finite

objective is no longer reported as converged.

The two epsilon.iter are not the same quantity and should not be compared

across the two fitters: nmfkc() divides by max(|f_i|, 1) and nmfkc.signed()

by |f_{i-1}|. Each matches the stopping rule of its own fitter, and they agree

while the objective exceeds 1; below 1 nmfkc()'s test is effectively an

absolute one. Both are documented on the respective help pages.

NMF-FFB: names brought into line with the rest of the package

nmf.ffb() grew its own vocabulary while the likelihood estimator was being

built, and of the 55 fields on a fit only nine shared a name with an nmfkc()

fit -- two of those meaning something different. The names are now the

package's. **This is a breaking change**, confined to the likelihood branch and

its inference, all of which was added after the last CRAN release.

| was | is | why |

|:--|:--|:--|

| fit$Q | fit$rank | rank is the field on every other fitter |

| fit$MAE | fit$mae | likewise (the path / candidates **columns** stay MAE, beside BIC) |

| fit$objfunc (NULL) | fit$objfunc = the minimized negative log-likelihood | it was the one house field the fiml path left empty |

| fit$objfunc.full | fit$objfunc.penalized | it is the penalized objective, unrelated to the full fit |

| mask = (argument) | C1.restriction = | mirrors X.restriction; the argument and the field of the same name held different things |

| fit$mask (matrix) | fit$C1.free | it marks the entries left **free**, and the argument mask was a rule string |

| fit$mask.rule | fit$C1.restriction | the field now means what the argument means |

| cross.threshold | C1.restriction.threshold | it parameterizes the restriction, so it shares its prefix |

| phi =, fit$phi | Phi.restriction | phi and Phi differed only in case |

| lambda1 | C1.L1.path | the L1 penalty on C1, as a path; fit$path$C1.L1 is one value of it |

| lambda1.selected | C1.L1.selected | |

| ci.level | boot.level | the confidence level of the bootstrap, as wild.level elsewhere |

| bootstrap.B, .threshold, .ci.level, .n.valid, .n.invalid, .type | boot.B, boot.threshold, boot.level, boot.n.valid, boot.n.invalid, boot.method | boot.method is the house name |

| C1.array, C2.array | C1.boot.draws, C2.boot.draws | as C.boot.draws in nmfkc.inference() |

| rho.boot | rho.boot.draws | |

| mask.change.rate | C1.restriction.change.rate | |

| calibration = "full" | calibration = "procedure" | "full" already named the unrestricted feedback fit |

| plot(fit, which = "full") | which = "penalized" | same reason |

| coefficients$p_value | (removed; use prob.unsupported) | it held 1 - support_rate, which is not a p-value |

| nmf.ffb.diagnostics()$top$rank | $top$order | rank now means the number of factors |

Three defects surfaced while doing this, all of them silent until now.

  • nmf.ffb(C1.L1 = , C2.L1 = ) had **no effect at all** under the default method = "fiml": the arguments were accepted and never forwarded. They belong to the multiplicative updates; the likelihood path penalizes C1 along C1.L1.path and leaves C2 unpenalized. Passing them to a fiml fit now warns. Same in nmf.ffb.inference().
  • fit$maxit held the **stage-2** cap while the maxit argument set the **stage-1** one, and print() paired stage 2's iteration count with stage 1's epsilon (87 / 3000 epsilon = 1e-06 -- three numbers from two different optimizers). iter, maxit, epsilon now describe stage 1, as the arguments of those names do; stage 2 is fiml.iter, fiml.maxit, factr, fiml.converged; and converged is TRUE only if both stages converged. print() shows both.
  • plot() on a method = "fiml" fit failed inside sprintf(). A fiml fit has no iteration trace to draw (its optimizer is L-BFGS-B), so it now says so and names what to use instead.

Options withdrawn in 0.9.8 (starts, nsplit, calibration as an argument of

the fit) reached ... and were dropped without a word; they now warn. A

**renamed** argument is worse than a withdrawn one -- mask = "none" would be

dropped and the fit would silently use the default restriction, the opposite of

the request -- so the four renamed arguments stop with the new name instead.

NMF-FFB is the canonical name everywhere, NMF-SEM the alias

The nmf.sem* functions have been deprecated aliases of nmf.ffb* for some

time, but the rest of the package had not followed:

  • the tutorial was vignettes/nmf-sem-with-nmfkc.Rmd and the engine file R/nmf.sem.R; they are now nmf-ffb-with-nmfkc.Rmd and R/nmf.ffb.R;
  • **the six S3 methods were registered on nmf.sem, not on nmf.ffb** -- summary(), plot(), coef(), fitted(), residuals() and print.summary() -- so the deprecated name was the one dispatch resolved against and ?summary.nmf.sem was the page a user landed on. They are now defined on nmf.ffb, and the nmf.sem methods are one-line aliases collected at the end of R/nmf.sem-deprecated.R, so that removing nmf.sem later means removing one file. An object saved by a version that wrote only c("nmf.sem", "nmf"), and a summary object of class "summary.nmf.sem", still dispatch;
  • nmf.ffb.DOT() tagged its result c("nmf.sem.DOT", "nmfkc.DOT"); the leading class is now "nmf.ffb.DOT".

The fitted object still carries c("nmf.ffb", "nmf.sem", "nmf"), and the

deprecated functions still work and still say so. The tutorial now also shows

the exclusion restriction (fit$C1.restriction, fit$C1.free), the BIC path

that plot() draws, and the two-stage convergence line.

NMF-FFB: one function per step of the procedure

The feedback model is now driven by four functions, one for each step, instead

of two that each did several things:

```r

ecv <- nmf.ffb.ecv(Y1, Y2, rank = 1:5) # 1. choose Q by element-wise CV

fit <- nmf.ffb(Y1, Y2, rank = Q) # 2. estimate; BIC selects the support

tst <- nmf.ffb.test(fit, Y1, Y2) # 3. test the feed-forward null

dgn <- nmf.ffb.diagnostics(fit) # 4. cycles, spectral radius, best supports

inf <- nmf.ffb.inference(fit, Y1, Y2) # 5. intervals for the retained entries

```

  • **New** nmf.ffb.test(): the calibrated test of the feed-forward null. It runs the null bootstrap only, and returns LR.p.boot, the null quantiles, the null false-selection rate prob.select.null and, for the default calibration, C1.restriction.change.rate. Replicates run in parallel with cores = as elsewhere in the package.
  • **New** nmf.ffb.ecv(): choosing *Q* under the name that says what it does. nmf.ffb.cv(method = "fiml") has delegated to element-wise CV since 0.9.8; the old name still works and is kept for the multiplicative-update path.
  • **Breaking** nmf.ffb.inference() no longer runs the null bootstrap and no longer returns LR.boot, LR.p.boot, LR.null.quantile, prob.select.null, LR.boot.*, C1.restriction.change.rate, split.table or bootstrap.calibration; its calibration and nsplit arguments are gone. Use nmf.ffb.test(). An intervals object that also carried a *p*-value for the presence of feedback invited the reader to treat an interval that excludes zero as evidence for the entry, which it is not. This affects only the likelihood branch (method = "fiml"), which was added after the last release.
  • nmf.ffb.diagnostics() now also reports the three best distinct supports with their differences in BIC, the entries common to all of them, their envelope, and whether they form a chain under inclusion. A difference in BIC below about 2 is not evidence for one support over another (Kass and Raftery 1995), so the presence of feedback can be settled while its composition is not.

Options removed

Measurement, not taste, decided each of these; keeping them invited the reader

to compare procedures as if they were equally valid.

  • nmf.ffb(starts = ) is gone. Every penalized fit is warm-started from the unpenalized full-feedback fit. The three alternative starting points measured on six data sets never uniquely attained the minimum BIC.
  • calibration (now an argument of nmf.ffb.test()) keeps "procedure" (the default: stage 1 and the exclusion restriction re-estimated in every null replicate) and "conditional" (valid only when the basis and the restriction come from outside the data being tested, and selected automatically in that case). The two sample-splitting levels are gone: "split" fixes the basis of the estimation half and is anti-conservative, and "split-full" is valid but strictly dominated by "procedure" -- same size, lower power, and it needs a large *N*.
  • C1.restriction keeps "union" (an outcome may not feed back into a factor on which it loads) and "none", plus a user-supplied matrix. The partial rules "block" and "cross" were kept for comparison and are neither the rule of the paper nor useful on their own.

The feedback test: what the bootstrap conditions on, and two fixes

The parametric bootstrap that calibrates the feedback LR statistics used to

hold the estimated basis X and the exclusion restriction fixed at their

observed values while regenerating Y1*. Both are functions of Y1, so the

null distribution omitted the adaptivity of that selection and the p-values

were anti-conservative: on the two positive examples of the NMF-FFB paper the

fixed-basis bootstrap gives p < 0.001, re-running stage 1 on every replicate

gives p = 0.05 and 0.35, and the exclusion restriction turns out to move in

40% and 95% of the null replicates. nmf.ffb.test() therefore has a

calibration argument with two levels:

  • "procedure" (default): stage 1 and the restriction are re-estimated on every replicate (one nmfkc() fit per replicate), so the p-value is the operating characteristic of the whole exploratory procedure. Returns C1.restriction.change.rate, the share of replicates whose restriction moved, and LR.boot.df.
  • "conditional": stage 2 only, basis and restriction fixed (the pre-0.9.8 behaviour). Valid only if they came from data independent of Y1; labelled as conditional in print(), and selected automatically when the fit used a basis or a restriction supplied by the caller.

Two fixes in the same code. LR.p.boot is now (1 + #)/(1 + B_ok) instead of

the raw proportion, which was exactly 0 whenever no replicate reached the

observed statistic (the normal case for a strongly significant fit, and not a

valid p-value); the floor 1/(1 + B_ok) is printed as < floor. And the

L-BFGS-B convergence codes of the null replicates are no longer discarded:

LR.boot.n.nonconv reports how many missed the tolerance and a warning is

raised above 10%, because on a flat null likelihood (small N, full Phi) the

share can reach 40% and must be visible to the user.

nmf.ffb(): exclusion restriction "union" (new default)

`C

The NEWS section is longer than shown here.

From NEWS.md in the release

Install

Health

CRAN checks
13OK
Slowest check: 4.7 min · r-oldrel-macos-x86_64
Status changes
  • OK13 OK2026-08-05
  • NOTE12 OK, 1 NOTE2026-08-01
  • OK3 OK2026-04-16
Advisories

none known

Documentation

In release 0.9.8
READMEYes · 875 wordsVignettesYes · 1 · dynamicNEWSYes · 100% structured · mentions 0.9.8Citation fileYes · cites a publicationWebsiteYes · pkgdown
Help pages
147 (15 internal)
Examples that run
59%46 run in a plain check · 32 only when donttest examples are enabled (R CMD check --as-cran)
Exported functions with examples
85%
Documented parameters
93%
Return-value docs
100%
Help pages citing references
29%

Downloads

2.4K
CRAN downloads in the past year
Rank #10,723 · ~7/day · ~200/mo
Daily download trend is not available in this view yet.
37830 days
97890 days
2.4K1 year
Compare downloads with other packages →
Also on77 r2u7 autocran

Repository

7Stars
1Forks
0Open issues
0Open PRs
1Releases
1,058Commits
1Commit identities
1,058 commits · Last activity 2026-10-19 · 0% stars, 30d · Default branch at 1.0.1, ahead of CRAN 0.9.8 (checked 2026-10-21)
Responsiveness
not measured

Stars over time

2025-02-03 · 32026-09-21 · 7
PracticesBeta

6 development-tooling and community-health practices detected across 4 families in the upstream repository

Checks run against github.com/ksatohds/nmfkc on 2026-10-21.

Continuous integration (1)
GitHub Actions
Docs source (2)
pkgdown

GitHub Pages site last deployed 2026-10-19

pkgdown site last built 2026-10-19

CRAN release process (2)
cran-comments.mdCRAN-SUBMISSION
Lint, format, editor (1)
RStudio project
How this is detected·Detection ruleset v4 (2026-10-20)
AI coding toolsBeta

This repository shows signs of AI coding tools.

  • Claude Codefirst seen 2026-02-27
    Named in .Rbuildignore: .claude, CLAUDE.md
    Named in .gitignore: .claude
    Commits: at least 307 crediting it in the message
Models named in its commits
  • Claude Sonnet 4.656 commits · Mar to Apr 2026
  • Claude Opus 4.644 commits · Feb to Mar 2026

Some of these counts are a minimum, because the search stopped before the oldest commits.

Counts are commits, not lines of code. Commits by a tool's accounts count once across all of its accounts. Commits crediting it are the largest count among its credit lines, a minimum.How this is detected·Detection ruleset v4 (updated 2026-09-27)

Dependencies

Declared dependencies
10 external dependencies (excludes base and recommended)

Installing nmfkc adds no packages beyond those that come with R.

Depends (0)
none
Imports (4)
statsgraphicsutilsgrDevices
LinkingTo (0)
none
Enhances (0)
none
Reverse dependencies
0direct
0indirect

Nothing depends on this yet.

People & History

People (1)
Author, Maintainer since 0.6.7 (2026)
Releases (6)

Pick two releases to compare their code metrics.

  • 0.9.8Latest
    record
    2026-09-22 · current release · diff ↗ ·
    removed 20 exports
    nmf.sem, nmf.sem.DOT, nmf.sem.cv, nmf.sem.inference, nmf.sem.split, nmfae, nmfae.DOT, nmfae.cv, nmfae.ecv, nmfae.heatmap, nmfae.inference, nmfae.kernel.beta.cv, nmfae.rank, nmfae.rename, nmfae.signed, nmfae.signed.ecv, nmfae.signed.heatmap, nmfae.signed.inference, nmfae.signed.rank, nmfae.signed.rename
    added 12 exports
    nmf.ffb.diagnostics, nmf.ffb.ecv, nmf.ffb.test, nmf.gmm, nmf.gmm.inference, nmf.gmm.select, nmf.gmm.twostage, nmfkc.kernel.gram, nmfkc.rff.beta.cv, nmfkc.rff.positive, nmfkc.rff.positive.gram, nmfkc.signed.rff.gram
  • 0.9.6
    record
    2026-07-13 · diff ↗ ·
    added 2 exports
    nmfkc.ar.latent, nmfkc.ar.latent.inference
  • 0.8.8
    record
    2026-07-13 · diff ↗ ·
    removed 1 export
    nmfre.dfU.scan
    added 16 exports
    nmf.rrr, nmf.rrr.DOT, nmf.rrr.cv, nmf.rrr.ecv, nmf.rrr.heatmap, nmf.rrr.inference, nmf.rrr.kernel.beta.cv, nmf.rrr.rank, nmf.rrr.rename, nmf.rrr.signed, nmf.rrr.signed.ecv, nmf.rrr.signed.heatmap, nmf.rrr.signed.inference, nmf.rrr.signed.rank, nmf.rrr.signed.rename, nmfre.ecv
  • 0.8.2
    record
    2026-06-14 · diff ↗ ·
    added 10 exports
    nmf.cluster.criteria, nmf.cluster.flow, nmfae.rank, nmfae.signed.rank, nmfkc.ard, nmfkc.bicv, nmfkc.consensus, nmfkc.net.rank, nmfkc.signed.rank, nmfkc.signed.rff
  • 0.7.3
    record
    2026-05-13 · diff ↗ ·
    added 17 exports
    nmf.ffb, nmf.ffb.DOT, nmf.ffb.cv, nmf.ffb.inference, nmf.ffb.split, nmfae.signed, nmfae.signed.ecv, nmfae.signed.heatmap, nmfae.signed.inference, nmfae.signed.rename, nmfkc.net, nmfkc.net.DOT, nmfkc.net.ecv, nmfkc.net.inference, nmfkc.signed, nmfkc.signed.cv, nmfkc.signed.ecv
  • R
    R 4.6.0 released · 2026-04-24
  • 0.6.7
    record
    2026-04-15
  • R
    R 4.5.0 released · 2025-04-11

Package metadata

First published
2026-04-15
Total releases
6 / 1 yrs
License
MIT + file LICENSE OSI
Download size
545 KB

Cite

Cite this package

Run in R for the authors' entry:

citation("nmfkc")

The package provides a citation file, so citation() returns the authors' own entry.

It points to 10.1007/s42081-026-00349-x, 10.1007/s42081-025-00314-0, 10.5023/jappstat.52.59.

Cite the R Observatory

For a number measured here: a download total, a coverage figure, an archival date.

APA

Balamuta, J. J. (2026). R Observatory: Metrics for nmfkc version 0.9.8 [Data set]. HJJB, LLC. Data release v2026-10-25. https://doi.org/10.5281/zenodo.21843040

From data release v2026-10-25, which the citation names so these numbers can be found later. More on citing and the projects behind them.

Report a problem with this page →

Privacy choices

These apply to this browser and are stored on this device only. Nothing about your choice is sent to us.

Read the privacy policy