A visual tour of the nda package

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 (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.

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.

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  0
sparse_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.075

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.

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.471

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.

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.000

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.

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.

Full, partial, and semipartial distance associations on a common visual scale.

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.

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.00

Feature selection

KMO-based selection

kmo_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.

Loading-based selection

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.

The two main model constructors

NDR: communities become latent scores

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.4654978

NDRLM: a latent input model from the published example

The 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.83850

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

nda_network <- plot(
  nda_fit, interactive = FALSE, show_weights = TRUE,
  shape = "automatic"
)
nda_network
Observed Swiss indicators. Colour indicates community; there are no latent nodes in this view.

Observed Swiss indicators. Colour indicates community; there are no latent nodes in this view.

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.

nda_biplot <- biplot(
  nda_fit, view_3D = FALSE, interactive = FALSE
)
nda_biplot
Swiss observations and indicator directions on the fitted NDR score axes.

Swiss observations and indicator directions on the fitted NDR score axes.

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.

ndrlm_network <- plot(
  ndrlm_fit, sig = 1, interactive = FALSE,
  show_edge_labels = TRUE, shape = "automatic"
)
ndrlm_network
Ten observed car indicators, two latent input predictors, and the observed mpg response.

Ten 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.

References

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).

Kosztyán, Zsolt T. 2026a. “Network-Based Structural Equation Models for Supervised Machine Learning Problems.” Unpublished manuscript.
Kosztyán, Zsolt T. 2026b. “R and MATLAB Packages for Network-Based Dimensionality Reduction Analysis.” Unpublished manuscript.
Kosztyán, Zsolt Tibor. 2023. “Hálózatalapú Modell- És Adatredukciós Módszer.” Statisztikai Szemle 101 (4): 289–324. https://doi.org/10.20311/stat2023.04.hu0289.
Kosztyán, Zsolt T., Attila I. Katona, Marcell T. Kurbucz, and Zoltán Lantos. 2024. “Generalized Network-Based Dimensionality Analysis.” Expert Systems with Applications 238: 121779. https://doi.org/10.1016/j.eswa.2023.121779.
Kosztyán, Zsolt T., Marcell T. Kurbucz, and Attila I. Katona. 2022. “Network-Based Dimensionality Reduction of High-Dimensional, Low-Sample-Size Datasets.” Knowledge-Based Systems 251: 109180. https://doi.org/10.1016/j.knosys.2022.109180.