--- title: "Generalized NDR: associations, communities, and latent scores" 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{Generalized NDR: associations, communities, and latent scores} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 8, fig.height = 5.5, warning = FALSE, message = FALSE ) library(nda) set.seed(42) ``` # What `ndr()` estimates Generalized network-based dimensionality reduction has three linked stages: 1. estimate or receive an association matrix; 2. detect communities in the squared-association network; 3. construct one standardized, centrality-weighted score per retained community. Squaring lets community detection use relationship strength. The unsquared association is retained for interpretation: positive edges are green, negative edges are red, and absolute magnitude controls width. Community membership is encoded by node colour only; indicators use one common shape by default. This visual grammar remains feasible when a model contains many communities.
AssociationVariables or observations become nodes.
CommunityStrongly connected nodes form modules.
ScoreEach retained module defines one latent score.
# Full, partial, and semipartial networks The association method and conditioning type are independent choices. `cor_method` supports Pearson, Spearman, Kendall, distance, Gaussian-rank, and biweight associations. `cor_type` supports full, partial, and semipartial forms. ```{r association-comparison} fit_full <- ndr( swiss, cor_method = "pearson", cor_type = "full", centrality = "pagerank", seed = 42 ) fit_partial <- ndr( swiss, cor_method = "pearson", cor_type = "partial", centrality = "pagerank", seed = 42 ) fit_semipartial <- ndr( swiss, cor_method = "pearson", cor_type = "semipartial", centrality = "authority", seed = 42 ) comparison <- data.frame( network = c("full", "partial", "semipartial"), factors = c(fit_full$factors, fit_partial$factors, fit_semipartial$factors), retained = c(sum(fit_full$membership > 0), sum(fit_partial$membership > 0), sum(fit_semipartial$membership > 0)), directed = c(fit_full$stats$directed, fit_partial$stats$directed, fit_semipartial$stats$directed) ) comparison ``` Partial association asks whether two indicators remain related after the others are controlled on both sides. Semipartial association controls one side and may be directed; hub and authority centralities can therefore answer different questions. # Inspecting communities and latent scores The full Pearson fit provides a compact example of the fitted object. ```{r inspect-fit} fit_full summary(fit_full) fit_full$membership round(fit_full$loadings, 3) round(fit_full$communality, 3) fit_full$stats$community_sizes ``` `membership` identifies the score to which an indicator contributes. `loadings` connect indicators to the resulting scores; `communality` describes how well each indicator is represented. A zero membership means exclusion. For a confirmatory illustration, a fixed two-community structure makes the network and biplot stable across community-detection backends. ```{r fixed-membership} visual_fit <- ndr( swiss, cor_method = "pearson", membership = c(1, 1, 2, 2, 2, 0), centrality = "eigenvector", seed = 42 ) data.frame( indicator = colnames(visual_fit$R), community = unname(visual_fit$membership) ) ``` # Predicting new observations ```{r prediction} new_scores <- predict(visual_fit, swiss[1:5, , drop = FALSE]) round(new_scores, 3) identical(predict(visual_fit), visual_fit$scores) ``` Prediction reuses the training means, scales, indicator weights, centrality weights, latent centring, latent scaling, and rotation. Consequently, scores for new observations remain on the fitted model's coordinate system. # Supplying an arbitrary matrix and clustering observations With `covar = TRUE`, `ndr()` accepts a square matrix directly. This makes the method usable beyond correlation matrices. The example below follows the clustering use described in @Kosztyan2023: Euclidean distances between the standardized Swiss observations are converted to similarities. ```{r observation-clustering} scaled_swiss <- scale(swiss) distance_matrix <- as.matrix(dist(scaled_swiss)) similarity_matrix <- 1 - normalize(distance_matrix) diag(similarity_matrix) <- 1 cluster_fit <- ndr( similarity_matrix, covar = TRUE, min_comm = 2, centrality = "eigenvector", seed = 42 ) cluster_fit$factors table(cluster_fit$membership) head(cluster_fit$membership, 10) is.null(cluster_fit$scores) ``` Here the nodes are observations rather than indicators. The returned membership is therefore an observation clustering. Since an arbitrary matrix contains no original row-level transformation, latent scores for new rows are not available in this mode. ```{r clustering-network, fig.cap="Observation clusters obtained from a normalized Euclidean similarity matrix."} cluster_network <- plot( cluster_fit, cuts = 0.55, interactive = FALSE, show_weights = FALSE, shape = "automatic", vertex.label.size = 7, layout = "fr" ) cluster_network ``` # Static network and biplot ```{r ndr-static-network, fig.cap="NDR association network: colour denotes community, edge colour denotes sign, and width denotes absolute association."} static_network <- plot( visual_fit, cuts = 0.3, interactive = FALSE, show_weights = TRUE, shape = "automatic" ) static_network ``` All observed indicators use the same shape. The two communities differ only by node colour. Solid green and red edges distinguish positive and negative associations; their thickness is scaled between the requested minimum and maximum widths. ```{r ndr-biplot, fig.cap="Two-factor NDR biplot for the fixed Swiss-data communities."} score_biplot <- biplot( visual_fit, view_3D = FALSE, interactive = FALSE ) score_biplot ``` Nearby observation points have similar score profiles. Loading arrows indicate the direction of increasing indicator values. The graph explains association structure; the biplot explains observations and indicators in latent-score space. Neither should be read as a causal model. # Inference, filtering, and weighting The optional style controls can be demonstrated on the same fitted network. All observed indicators remain rectangles, and community uses colour only. Shapes are supplied in association-matrix column order. ```{r custom-network, fig.cap="Explicit common node shape, custom signed colours, and bounded edge widths."} custom_network <- plot( visual_fit, interactive = FALSE, shape = rep("rectangle", ncol(visual_fit$R)), positive.color = "#007A53", negative.color = "#B71C1C", positive.lty = 1, negative.lty = 1, edge.width.range = c(1, 5), show_weights = TRUE ) custom_network ``` For a four-node graph, an individual shape vector could instead be `c("circle", "circle", "square", "square")`; shapes are not assigned by community. Optional `positive.lty` and `negative.lty` control line types. Both default to 1 (solid). Widths are scaled within the displayed graph; separate graphs require a common scale for direct width comparisons. Association tests and bootstrap intervals can be executed on a small example without dominating build time. The low replicate count below illustrates the returned structure; final inference should use a larger `n_boot`. ```{r inference} inference_fit <- ndr( swiss, cor_method = "pearson", test = TRUE, p_adjust = "BH", alpha = 0.10, bootstrap = TRUE, n_boot = 30, conf = 0.90, seed = 42 ) round(inference_fit$correlation_p, 3) head(inference_fit$stats$bootstrap$lower) head(inference_fit$stats$bootstrap$upper) ``` The principal filtering controls are `min_R`, `min_evalue`, `min_communality`, `com_communalities`, and `min_comm`. The `weight` argument supplies one finite score weight per indicator. These filters act at different stages: network formation, centrality screening, representation, loading separation, and minimum module size. # Sparse storage and reproducible settings ```{r sparse-fit} sparse_fit <- ndr( swiss, min_R = 0.15, sparsecalc = TRUE, seed = 42 ) inherits(sparse_fit$R, "sparseMatrix") sparse_fit$factors sparse_fit$stats$community_sizes ``` Sparse storage is helpful after thresholding a large network. Parallel workers mainly benefit pairwise distance association and bootstrap calculations; small Pearson examples usually run faster sequentially. A fixed `seed` makes the stochastic parts reproducible. # References The cited literature covers the original NDA method, the generalized association-matrix formulation, the clustering interpretation, the software interface, and the supervised extension [@Kosztyan2022; @Kosztyan2023; @Kosztyan2024; @Kosztyan2026Packages; @Kosztyan2026NBSEM]. ::: {#refs} :::