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.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"))
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)
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.
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.
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/.
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.
MIT © Pavlos Delias