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 (Kosztyán et al. 2022, 2024) and their supervised extension (Kosztyán 2026a).
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.
data_gen() and
normalize()
dCov(), dCor(),
pdCor(), spdCor()
fs.KMO() and
fs.dimred()
ndr() and ndrlm()
| 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 |
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.
dense_data <- data_gen(
n = 40, m = 8, nfactors = 2,
lambda = 1, seed = 42
)
dim(dense_data)
#> [1] 40 8
round(head(dense_data, 3), 3)
#> V1 V2 V3 V4 V5 V6 V7 V8
#> case1 0.663 0.655 0.895 0.694 0 0 0 0
#> case2 0.764 0.809 0.729 0.950 0 0 0 0
#> case3 0.758 0.741 0.832 0.735 0 0 0 0sparse_data <- data_gen(
n = 60, m = 30, nfactors = 3,
lambda = 1, sparse = TRUE, density = 0.20,
seed = 42
)
class(sparse_data)
#> [1] "dgCMatrix"
#> attr(,"package")
#> [1] "Matrix"
dim(sparse_data)
#> [1] 60 30
Matrix::nnzero(sparse_data) / length(sparse_data)
#> [1] 0.075The 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.
normalize() can rescale all values jointly, each row, or
each column. Constant slices are mapped to the midpoint of the requested
range.
small_matrix <- as.matrix(dense_data[1:5, 1:4])
round(normalize(small_matrix), 3)
#> V1 V2 V3 V4
#> case1 0.074 0.048 0.804 0.172
#> case2 0.391 0.533 0.281 0.980
#> case3 0.373 0.317 0.605 0.301
#> case4 0.051 0.840 0.599 0.044
#> case5 0.000 1.000 0.585 0.486
round(normalize(small_matrix, type = "row", range = c(-1, 1)), 3)
#> V1 V2 V3 V4
#> case1 -0.932 -1.000 1.000 -0.672
#> case2 -0.685 -0.277 -1.000 1.000
#> case3 -0.525 -0.893 1.000 -1.000
#> case4 -0.984 1.000 0.396 -1.000
#> case5 -1.000 1.000 0.169 -0.029
round(normalize(small_matrix, type = "col"), 3)
#> V1 V2 V3 V4
#> case1 0.188 0.000 1.000 0.136
#> case2 1.000 0.510 0.000 1.000
#> case3 0.955 0.283 0.619 0.274
#> case4 0.129 0.832 0.609 0.000
#> case5 0.000 1.000 0.581 0.471dCov() and dCor() measure unconditional
dependence. pdCor() removes the other variables from both
sides; spdCor() removes them from one side and can
therefore be asymmetric.
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)
#> mpg disp hp wt
#> mpg 1.000 0.852 0.836 0.871
#> disp 0.852 1.000 0.855 0.866
#> hp 0.836 0.855 1.000 0.736
#> wt 0.871 0.866 0.736 1.000
round(pdcor_matrix, 3)
#> mpg disp hp wt
#> mpg 1.000 0.033 0.471 0.562
#> disp 0.033 1.000 0.552 0.538
#> hp 0.471 0.552 1.000 -0.277
#> wt 0.562 0.538 -0.277 1.000
round(spdcor_matrix, 3)
#> mpg disp hp wt
#> mpg 1.000 0.013 0.212 0.270
#> disp 0.013 1.000 0.254 0.244
#> hp 0.244 0.303 1.000 -0.132
#> wt 0.281 0.264 -0.119 1.000The 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.
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")Full, partial, and semipartial distance associations on a common visual scale.
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.
dcor_test <- dCor(
association_data[, c("mpg", "wt", "hp")],
test = TRUE, R = 49, adjust = "BH"
)
round(dcor_test$r, 3)
#> mpg wt hp
#> mpg 1.000 0.871 0.836
#> wt 0.871 1.000 0.736
#> hp 0.836 0.736 1.000
round(dcor_test$p, 3)
#> mpg wt hp
#> mpg 0.00 0.02 0.02
#> wt 0.02 0.00 0.02
#> hp 0.02 0.02 0.00kmo_selected <- fs.KMO(swiss, min_MSA = 0.50)
colnames(kmo_selected)
#> [1] "Fertility" "Agriculture" "Examination" "Education"
#> [5] "Catholic" "Infant.Mortality"
attr(kmo_selected, "removed")
#> character(0)fs.KMO() removes the weakest indicator only when this is
needed to reach the requested sampling adequacy.
pca <- prcomp(swiss, scale. = TRUE, rank. = 2)
pca_selected <- fs.dimred(pca, swiss)
colnames(pca_selected$retained_DF)
#> [1] "Examination" "Infant.Mortality"
names(pca_selected$dropped_low)
#> [1] "Education" "Catholic"
names(pca_selected$dropped_com)
#> [1] "Fertility" "Agriculture"Weak representation and cross-loading are reported separately. The
retained data remain available under both retained_DF and
the legacy remain_DF name.
nda_fit <- ndr(
swiss,
cor_method = "spearman",
cor_type = "full",
centrality = "pagerank",
seed = 42
)
nda_fit
#> Generalized network-based dimensionality reduction
#> Call:
#> ndr(r = swiss, cor_method = "spearman", cor_type = "full", seed = 42,
#> centrality = "pagerank")
#>
#> Factors: 2 | Observations: 47 | Modularity: 0.05067427
#> Association: full spearman | Centrality: pagerank | Null model: none
#> Community sizes: 3, 3
#>
#> Loadings:
#> NDA1 NDA2
#> Fertility 0.9082360 -0.7318924
#> Agriculture 0.3240064 -0.2927682
#> Examination -0.6197197 0.7818630
#> Education -0.4924112 0.8300116
#> Catholic 0.6646997 -0.2524128
#> Infant.Mortality 0.6596018 -0.2010569
#>
#> Communalities:
#> Fertility Agriculture Examination Education
#> 0.8248926 0.1049802 0.6113098 0.6889193
#> Catholic Infant.Mortality
#> 0.4418256 0.4350745
summary(nda_fit)
#> Generalized network-based dimensionality reduction
#> Call:
#> ndr(r = swiss, cor_method = "spearman", cor_type = "full", seed = 42,
#> centrality = "pagerank")
#>
#> Factors: 2 | Observations: 47 | Modularity: 0.05067427
#> Association: full spearman | Centrality: pagerank | Null model: none
#> Community sizes: 3, 3
#>
#> Loadings:
#> NDA1 NDA2
#> Fertility 0.9082360 -0.7318924
#> Agriculture 0.3240064 -0.2927682
#> Examination -0.6197197 0.7818630
#> Education -0.4924112 0.8300116
#> Catholic 0.6646997 -0.2524128
#> Infant.Mortality 0.6596018 -0.2010569
#>
#> Communalities:
#> Fertility Agriculture Examination Education
#> 0.8248926 0.1049802 0.6113098 0.6889193
#> Catholic Infant.Mortality
#> 0.4418256 0.4350745
head(predict(nda_fit, swiss))
#> NDA1 NDA2
#> Courtelary 0.5272742 -1.0827287
#> Delemont 1.2414237 -1.2759154
#> Franches-Mnt 1.5320172 -1.8524263
#> Moutier 0.7586614 -1.1579280
#> Neuveville 0.1372140 0.1496291
#> Porrentruy 1.4642171 -1.4654978The supervised example follows the low-sample-size
mtcars analysis in Kosztyán (2026a). Ten observed input
indicators form two input-side latent variables, which are then used to
model fuel efficiency.
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)
#> Network-based dimensionality reduction and regression
#> Call:
#> ndrlm(Y = Y_car, X = X_car, latents = "in", optimize = FALSE,
#> seed = 42, regression = "lm", membership.X = input_membership)
#>
#> Regression: lm | Latent mode: in | Optimized: no
#> Target: adj.r.square | Objective: 0.4508713
#> Independent variables: NDAin1, NDAin2
#> Dependent variables: mpg
#>
#> response metric
#> mpg 0.4508713
#>
#> Fit for mpg
#>
#> Call:
#> stats::lm(formula = lm_formula, data = lm_train)
#>
#> Residuals:
#> Min 1Q Median 3Q Max
#> -6.2287304 -2.7541419 -0.9093334 2.5742679 10.4369019
#>
#> Coefficients:
#> Estimate Std. Error t value Pr(>|t|)
#> (Intercept) 20.0906250 0.7895134 25.44684 < 2.22e-16 ***
#> NDAin1 -4.2225851 0.8065667 -5.23526 1.3191e-05 ***
#> NDAin2 0.2710497 0.8065667 0.33605 0.73925
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Residual standard error: 4.466162 on 29 degrees of freedom
#> Multiple R-squared: 0.486299, Adjusted R-squared: 0.4508713
#> F-statistic: 13.72653 on 2 and 29 DF, p-value: 6.387067e-05
round(ndrlm_fit$NDAin$loadings, 3)
#> NDA1 NDA2
#> cyl 0.620 0.034
#> disp 0.618 0.075
#> hp 0.735 0.378
#> drat -0.468 0.490
#> wt 0.719 0.008
#> qsec -0.144 -0.526
#> vs -0.233 -0.278
#> am -0.499 0.612
#> gear -0.250 0.680
#> carb 0.627 0.590
head(predict(ndrlm_fit, X_car))
#> mpg
#> Mazda RX4 24.76310
#> Mazda RX4 Wag 24.02762
#> Datsun 710 24.07228
#> Hornet 4 Drive 18.79777
#> Hornet Sportabout 20.59865
#> Valiant 17.83850All 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.
nda_network <- plot(
nda_fit, interactive = FALSE, show_weights = TRUE,
shape = "automatic"
)
nda_networkObserved Swiss indicators. Colour indicates community; there are no latent nodes in this view.
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.
Swiss observations and indicator directions on the fitted NDR score axes.
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.
ndrlm_network <- plot(
ndrlm_fit, sig = 1, interactive = FALSE,
show_edge_labels = TRUE, shape = "automatic"
)
ndrlm_networkTen observed car indicators, two latent input predictors, and the observed mpg response.
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.
The references connect the original NDA method, its generalized and Hungarian presentations, the package implementation, and the supervised extension (Kosztyán et al. 2022, 2024; Kosztyán 2023; Kosztyán 2026b, 2026a).