spectralDEMATEL

Spectral diagnostics for DEMATEL influence matrices.

Two DEMATEL models with the same number of factors and the same rating scale can require opposite interventions. Which one you are holding is a property of the matrix's spectrum, not of its subject matter — so you cannot look up your intervention logic, you have to diagnose it. This package is the diagnosis.

Install

install.packages("spectralDEMATEL",
                 repos = c("https://pdelias.r-universe.dev",
                           "https://cloud.r-project.org"))

In webR or a shinylive application, point at the WebAssembly builds instead:

install.packages("spectralDEMATEL",
                 repos = c("https://pdelias.r-universe.dev",
                           "https://repo.r-wasm.org"))

Use

library(spectralDEMATEL)

A <- worked_matrices$fefo_stock_control   # 13 factors, from a published study
d <- spectral_diagnostics(A)

d$mu_max              # 0.828  coupling, in (0, 1)
d$multiplier          # 5.81   total effects are 5.8x the direct ones
d$indirect_dominant   # TRUE   indirect effects exceed direct ones
d$dominance           # 0.028  one propagation mode, no competing second
d$hierarchy_sd        # 0.038  low, so influence enters diffusely
d$ev_condition        # 1.01   sensitivity estimates are trustworthy here

Where to intervene:

s <- sensitivity_matrix(A)
which(s$total == max(s$total, na.rm = TRUE), arr.ind = TRUE)

s$total is the derivative of the dominant eigenvalue in every off-diagonal entry, so it ranks relationships rather than factors. It is signed: negative entries are the levers for calming a system rather than driving it. Read it alongside d$ev_condition, which bounds how far a first-order estimate can be trusted — never one without the other.

Is the matrix even in scope? Every condition comes back as data — a verdict, a plain-language reason, and the factors at fault:

subset(d$checks, verdict != "pass", c(check, verdict, value))
#>                 check verdict value
#> 6       zero_diagonal    warn    13

ck <- assumption_checks(worked_matrices$resilience_capabilities)
ck$factors[ck$check == "strong_connectivity"][[1]]
#> [1]  4  7  8 14

Those four are stranded in this published matrix: row 14 is blank so factor 14 dispatches nothing, column 7 is blank so factor 7 receives nothing, and factors 8 and 4 are reachable only through 7. Naming them is the point — the check tells you which rows to look at, not just that something is wrong.

Nothing here stops, warns or prints. An inadmissible matrix comes back with the same shape, every number NA, and a table saying why — so a batch job over a hundred matrices never changes shape and never fails.

Is the structure real, or does it follow from the entry distribution alone?

ens <- surrogate_ensemble(A, B = 200, seed = 42)
mean(ens$mu_max >= d$mu_max)

Reading the numbers in the right direction

Two definitions here invert easily, and both have been inverted before in reviewed code, while producing entirely plausible output.

Hierarchy has three readings and they do not run the same way.

High means Size-free
hierarchy_sd concentrated no
hierarchy_gini concentrated yes
hierarchy_pr diffuse — the opposite yes

Anything that displays one of these must say which reading it used and which way it runs. hierarchy_sd is the reading behind every published corpus result.

Mode dominance takes the largest-modulus subdominant eigenvalue, not the largest real part. For a non-negative total-relation matrix the negative end of the spectrum usually carries the larger modulus, so the real-part version understates the ratio nearly everywhere — while passing every plausibility check.

Coupling, the dominant eigenvalue and the multiplier are one quantity on three scales: mu_max in (0, 1), lambda_max = mu/(1 - mu), and multiplier = 1/(1 - mu_max) = 1 + lambda_max.

What it does not do

It computes and returns data. It does not draw, read files, print on a success path, or know Shiny exists. Parsing a spreadsheet, choosing a colour and rendering a verdict belong to whatever is calling it.

That boundary is what lets the same code serve a script, a batch job over a hundred matrices, and a browser-side application compiled to WebAssembly, without a second implementation — and a second implementation is exactly how these definitions drift apart.

Dependencies: base R and stats. Nothing else, deliberately.

Provenance

The five working functions are a port of R/spectral_dematel.R from the research code behind the source paper, which is the single source of truth for every metric definition. The port is verbatim; everything added here is documentation, tests and packaging.

The test suite compares each definition against an independent route to the same number: values from the paper's precomputed corpus workbook, closed-form spectra for matrices whose eigenvalues can be worked out on paper, the package's own two input branches checked against each other, and finite differences for the closed-form derivative. Agreement between a function and a literal it produced proves only that nothing changed.

See DEVELOPMENT.md before changing anything in R/.

Citation

Delias, P. (2026). spectralDEMATEL: Spectral Diagnostics for DEMATEL Influence
Matrices. Zenodo. https://doi.org/10.5281/zenodo.21849277
@software{delias_spectraldematel,
  author    = {Delias, Pavlos},
  title     = {spectralDEMATEL: Spectral Diagnostics for {DEMATEL} Influence Matrices},
  publisher = {Zenodo},
  year      = {2026},
  doi       = {10.5281/zenodo.21849277},
  url       = {https://doi.org/10.5281/zenodo.21849277}
}

That is the concept DOI: it always resolves to the newest release, which is what you want when citing the software as such.

Where the exact version matters — reproducing a number from a paper, say — cite the version DOI instead. Every release has its own, shown on its Zenodo record. For 0.5.0 it is 10.5281/zenodo.21849278.

License

MIT © Pavlos Delias