The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Introduction to BrainNetTest

Maximiliano Martino, Daniel Fraiman

2026-09-23

Overview

BrainNetTest implements the non-parametric L1-distance ANOVA test of Fraiman and Fraiman (2018) for comparing populations of brain networks represented as graphs. The package provides:

This vignette introduces the basic API on simple synthetic data. The companion vignette, Generating and Analyzing Brain Networks with Community Structures, shows the full pipeline on community-structured populations.

Installation

# Release version
install.packages("BrainNetTest")

# Development version from GitHub
# install.packages("remotes")
remotes::install_github("mmaximiliano/BrainNetTest")

A minimal example

library(BrainNetTest)
set.seed(1)

Generating random graphs

generate_random_graph() returns a symmetric binary adjacency matrix with no self-loops:

G <- generate_random_graph(n_nodes = 10, edge_prob = 0.2)
isSymmetric(G)
#> [1] TRUE
all(diag(G) == 0)
#> [1] TRUE

Central graph and Manhattan distance

Given a population (list of adjacency matrices), the central graph is the entry-wise mean; compute_distance() returns the L1 (Manhattan) distance between two graphs:

population <- replicate(
  5, generate_random_graph(n_nodes = 10, edge_prob = 0.2),
  simplify = FALSE)

central <- compute_central_graph(population)
compute_distance(population[[1]], central)
#> [1] 18

The group test statistic T

compute_test_statistic() aggregates within- and between-group Manhattan distances into the statistic T. Larger values indicate stronger evidence that the groups differ:

control <- replicate(
  15, generate_random_graph(n_nodes = 10, edge_prob = 0.20),
  simplify = FALSE)
patient <- replicate(
  15, generate_random_graph(n_nodes = 10, edge_prob = 0.40),
  simplify = FALSE)

populations <- list(Control = control, Patient = patient)
compute_test_statistic(populations, a = 1)
#> [1] -18.54162

The global test

compute_test_statistic() returns only the observed value. global_test() assesses it: it permutes the group labels across the pooled sample, keeping the group sizes, recomputes T for each permutation, and reports the proportion of permutations with a statistic smaller than the observed one as the p-value. The normalisation constant a is fixed at 1; the p-value does not depend on it.

gt <- global_test(populations, n_permutations = 500, seed = 42)
gt
#> 
#> Global test for populations of brain networks
#> 
#> Populations:  2 (Control, Patient), 15 / 15 graphs
#> Nodes:        10 (45 possible edges)
#> Statistic:    T = -18.54162
#> p value:      < 0.002 (500 permutations)

The permutation null distribution is kept in gt$null_distribution, so it can be inspected or plotted. With B permutations the p-value cannot resolve below 1 / B, which is how an estimate of zero is reported.

Critical edges

Once a difference is established, identify_critical_links() looks for the edges responsible. It runs the same global test first and then iteratively removes edges, ranked by their marginal p-values, until the populations are no longer distinguishable:

result <- identify_critical_links(
  populations,
  alpha          = 0.05,
  method         = "fisher",
  n_permutations = 200,
  seed           = 42)

result
#> 
#> Critical edges between populations of brain networks
#> 
#> Populations:     2 (Control, Patient), 15 / 15 graphs
#> Candidate edges: 45
#> Global test:     p < 0.005 (200 permutations, alpha = 0.05)
#> Critical edges:  7 of 45 (15.6%), ranked by Fisher's exact test
#> 
#> Most significant 6 of them:
#>   node1 node2    p_value
#> 1     2     4 0.01419290
#> 2     4    10 0.01419290
#> 3     2     5 0.02093953
#> 4     2    10 0.02093953
#> 5     8    10 0.02532769
#> 6     1     3 0.03518241
#> ... 1 more; see the critical_edges component

The result is a critical_links object. Printing it, as above, gives a compact report; summary() adds the settings the analysis ran with and the node-level ranking:

summary(result)
#> 
#> Critical edges between populations of brain networks
#> 
#> Call:
#> identify_critical_links(populations = populations, alpha = 0.05, 
#>     method = "fisher", n_permutations = 200, seed = 42)
#> 
#> Populations:     2 (Control, Patient), 15 / 15 graphs
#> Candidate edges: 45
#> Global test:     p < 0.005
#> Critical edges:  7 (15.6% of candidates)
#> 
#> Settings:
#>   marginal test        Fisher's exact test
#>   multiplicity         none
#>   significance level   0.05
#>   edges removed / step 1
#>   permutations         200
#>   normalisation a      1
#>   seed                 42
#> 
#> Most significant edges:
#>   node1 node2    p_value
#> 1     2     4 0.01419290
#> 2     4    10 0.01419290
#> 3     2     5 0.02093953
#> 4     2    10 0.02093953
#> 5     8    10 0.02532769
#> 6     1     3 0.03518241
#> 7     4     6 0.05017491
#> 
#> Nodes ranked by critical degree:
#>   node critical_degree
#> 1    2               3
#> 2    4               3
#> 3   10               3
#> 4    1               1
#> 5    3               1
#> 6    5               1
#> 7    6               1
#> 8    8               1

The individual components remain available, result$critical_edges being the ranked table of critical edges. The get_critical_nodes() helper summarises the result at the node level on its own:

get_critical_nodes(result)
#>   node critical_degree
#> 1    2               3
#> 2    4               3
#> 3   10               3
#> 4    1               1
#> 5    3               1
#> 6    5               1
#> 7    6               1
#> 8    8               1

References

Fraiman, D. and Fraiman, R. (2018) An ANOVA approach for statistical comparisons of brain networks. Scientific Reports, 8, 4746. https://doi.org/10.1038/s41598-018-23152-5

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.