---
title: "A visual tour of the nda package"
author: "Zsolt T. Kosztyan, Marcell T. Kurbucz, Attila I. Katona, and Zahid Khan"
bibliography: references.bib
link-citations: true
output:
rmarkdown::html_vignette:
toc: true
number_sections: true
css: vignette.css
vignette: >
%\VignetteIndexEntry{A visual tour of the nda package}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.width = 8,
fig.height = 5,
warning = FALSE,
message = FALSE
)
library(nda)
set.seed(42)
```
# Purpose and workflow
The **nda** package prepares structured data, measures full and conditional
associations, selects indicators, constructs network-based latent variables,
and links those latent variables to supervised response models. The methods
follow the original and generalized NDA frameworks [@Kosztyan2022;
@Kosztyan2024] and their supervised extension [@Kosztyan2026NBSEM].
Every visible code block below is executable and is followed by the result it
is intended to produce. The examples remain deliberately small so that the
vignette can be built as part of a routine package check.
1. Preparedata_gen() and normalize()
2. AssociatedCov(), dCor(), pdCor(), spdCor()
3. Selectfs.KMO() and fs.dimred()
4. Modelndr() and ndrlm()
# Function map
| Task | Functions and methods | Main result |
|---|---|---|
| Data preparation | `data_gen()`, `normalize()` | dense or sparse data with controlled scaling |
| Association | `dCov()`, `dCor()`, `pdCor()`, `spdCor()` | full, partial, or semipartial dependence matrix |
| Feature selection | `fs.KMO()`, `fs.dimred()` | retained indicators and named exclusions |
| Unsupervised reduction | `ndr()` plus `print()`, `summary()`, `predict()`, `plot()`, `biplot()` | communities, latent scores, loadings, and graphs |
| Supervised modelling | `ndrlm()` plus `print()`, `summary()`, `predict()`, `fitted()`, `residuals()`, `plot()` | latent-variable regression and path networks |
# Structured data generation
`data_gen()` creates reproducible block-structured examples. The first call
returns a regular data frame; the second demonstrates the sparse output rather
than merely documenting it.
```{r data-generation}
dense_data <- data_gen(
n = 40, m = 8, nfactors = 2,
lambda = 1, seed = 42
)
dim(dense_data)
round(head(dense_data, 3), 3)
```
```{r sparse-generation}
sparse_data <- data_gen(
n = 60, m = 30, nfactors = 3,
lambda = 1, sparse = TRUE, density = 0.20,
seed = 42
)
class(sparse_data)
dim(sparse_data)
Matrix::nnzero(sparse_data) / length(sparse_data)
```
The sparse representation is useful when the planted block structure is large
but most entries are zero. It changes storage, not the conceptual meaning of
rows, indicators, or blocks.
# Normalization
`normalize()` can rescale all values jointly, each row, or each column.
Constant slices are mapped to the midpoint of the requested range.
```{r normalization}
small_matrix <- as.matrix(dense_data[1:5, 1:4])
round(normalize(small_matrix), 3)
round(normalize(small_matrix, type = "row", range = c(-1, 1)), 3)
round(normalize(small_matrix, type = "col"), 3)
```
# Full and conditional distance association
`dCov()` and `dCor()` measure unconditional dependence. `pdCor()` removes the
other variables from both sides; `spdCor()` removes them from one side and can
therefore be asymmetric.
```{r distance-association}
association_data <- mtcars[, c("mpg", "disp", "hp", "wt")]
dcov_matrix <- dCov(association_data)
dcor_matrix <- dCor(association_data)
pdcor_matrix <- pdCor(association_data)
spdcor_matrix <- spdCor(association_data)
round(dcor_matrix, 3)
round(pdcor_matrix, 3)
round(spdcor_matrix, 3)
```
The bubble maps use a common scale, so differences in symbol size are directly
comparable. Square area represents absolute association and colour carries the
sign. This makes the reduction from full to conditional association more
visible than a smooth raster image.
```{r distance-correlation-figure, fig.cap="Full, partial, and semipartial distance associations on a common visual scale."}
association_bubbles <- function(z, main) {
z <- as.matrix(z)
nr <- nrow(z); nc <- ncol(z)
plot(c(0.5, nc + 0.5), c(0.5, nr + 0.5), type = "n",
axes = FALSE, xlab = "", ylab = "", main = main, asp = 1)
grid <- expand.grid(x = seq_len(nc), y = seq_len(nr))
values <- as.vector(t(z[nr:1, , drop = FALSE]))
symbols(grid$x, grid$y,
squares = 0.65 * sqrt(pmin(1, abs(values))), inches = FALSE,
bg = ifelse(values < 0, "#C62828", "#009E73"),
fg = "white", add = TRUE)
axis(1, at = seq_len(nc), labels = colnames(z), las = 2)
axis(2, at = seq_len(nr), labels = rev(rownames(z)), las = 1)
text(grid$x, grid$y, labels = formatC(values, digits = 2, format = "f"),
cex = 0.65, col = "black")
box()
}
op <- par(mfrow = c(1, 3), mar = c(6, 5, 3, 1))
association_bubbles(dcor_matrix, "Full dCor")
association_bubbles(pdcor_matrix, "Partial dCor")
association_bubbles(spdcor_matrix, "Semipartial dCor")
par(op)
```
Permutation inference can also be shown without making the vignette heavy. A
small number of permutations is sufficient for illustrating the returned
object; a substantive study should use a larger value.
```{r distance-test}
dcor_test <- dCor(
association_data[, c("mpg", "wt", "hp")],
test = TRUE, R = 49, adjust = "BH"
)
round(dcor_test$r, 3)
round(dcor_test$p, 3)
```
# Feature selection
## KMO-based selection
```{r kmo-selection}
kmo_selected <- fs.KMO(swiss, min_MSA = 0.50)
colnames(kmo_selected)
attr(kmo_selected, "removed")
```
`fs.KMO()` removes the weakest indicator only when this is needed to reach the
requested sampling adequacy.
## Loading-based selection
```{r loading-selection}
pca <- prcomp(swiss, scale. = TRUE, rank. = 2)
pca_selected <- fs.dimred(pca, swiss)
colnames(pca_selected$retained_DF)
names(pca_selected$dropped_low)
names(pca_selected$dropped_com)
```
Weak representation and cross-loading are reported separately. The retained
data remain available under both `retained_DF` and the legacy `remain_DF` name.
# The two main model constructors
## NDR: communities become latent scores
```{r ndr-overview}
nda_fit <- ndr(
swiss,
cor_method = "spearman",
cor_type = "full",
centrality = "pagerank",
seed = 42
)
nda_fit
summary(nda_fit)
head(predict(nda_fit, swiss))
```
## NDRLM: a latent input model from the published example
The supervised example follows the low-sample-size `mtcars` analysis in
@Kosztyan2026NBSEM. Ten observed input indicators form two input-side latent
variables, which are then used to model fuel efficiency.
```{r ndrlm-overview}
X_car <- mtcars[, setdiff(names(mtcars), "mpg")]
Y_car <- mtcars["mpg"]
input_membership <- c(
cyl = 1, disp = 2, hp = 1, drat = 2, wt = 2,
qsec = 1, vs = 1, am = 2, gear = 2, carb = 1
)
ndrlm_fit <- ndrlm(
Y_car, X_car,
latents = "in",
optimize = FALSE,
membership.X = input_membership,
regression = "lm",
seed = 42
)
summary(ndrlm_fit)
round(ndrlm_fit$NDAin$loadings, 3)
head(predict(ndrlm_fit, X_car))
```
# Visible model results
All figures are static and portable. In an NDR graph every observed indicator
uses a rectangle; community membership is encoded by colour only. Positive
associations are green, negative associations are red, and absolute magnitude
controls edge width. In an NDRLM graph rectangles denote observed variables
and circles are reserved for latent variables. The NDR network below contains
observed indicators only, drawn as rectangles. Its communities are groups of
indicators, not additional nodes named C1 or C2. In the biplot, NDA1 and NDA2
are score axes. In the supervised network, NDAin1 and NDAin2 are two distinct
latent predictor nodes. These are three views of two different fitted models;
their community numbers are local to each model and do not identify shared
variables across models.
## Indicator association network
```{r model-methods, fig.cap="Observed Swiss indicators. Colour indicates community; there are no latent nodes in this view."}
nda_network <- plot(
nda_fit, interactive = FALSE, show_weights = TRUE,
shape = "automatic"
)
nda_network
```
## Observation scores and indicator loadings
The points are Swiss observations. The arrows represent indicators. Neither
points nor arrows are community nodes. Score-axis names refer only to this
NDR fit.
```{r model-biplot, fig.cap="Swiss observations and indicator directions on the fitted NDR score axes."}
nda_biplot <- biplot(
nda_fit, view_3D = FALSE, interactive = FALSE
)
nda_biplot
```
## Latent predictor path model
Each observed car indicator appears once as a rectangle; each of the two
latent predictors appears once as a circle. Fuel efficiency (mpg) is an
observed response. Edge labels show loadings or regression coefficients;
these quantities have different units and should be interpreted separately.
```{r model-paths, fig.cap="Ten observed car indicators, two latent input predictors, and the observed mpg response."}
ndrlm_network <- plot(
ndrlm_fit, sig = 1, interactive = FALSE,
show_edge_labels = TRUE, shape = "automatic"
)
ndrlm_network
```
For `nda` objects, `predict()` returns latent scores. For `ndrlm` objects,
`predict()` returns response predictions, while `fitted()` and `residuals()`
preserve response names. The dedicated workflow vignettes expand the two model
families and execute their advanced options.
# References
The references connect the original NDA method, its generalized and Hungarian
presentations, the package implementation, and the supervised extension
[@Kosztyan2022; @Kosztyan2023; @Kosztyan2024; @Kosztyan2026Packages;
@Kosztyan2026NBSEM].
::: {#refs}
:::