---
title: "Consensus Networks Across Multiple Inference Methods"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Consensus Networks Across Multiple Inference Methods}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse  = TRUE,
  comment   = "#>",
  fig.width = 6,
  fig.height = 5,
  dpi       = 72
)
```

## Why consensus networks?

A single inference method has its own assumptions and biases: correlation
ignores compositionality, SparCC was built for compositional microbiome
counts, SpiecEasi enforces sparsity through partial correlations, WGCNA
optimises for scale-free topology. Different methods consequently
disagree on which edges are "real". A consensus network combines two or
more single-method networks into one and keeps the edges that are
robustly supported across methods. Recent work in microbiome research --
such as CMiNet (Aghayeva et al. 2024) and the hybrid Bayesian / machine
learning / network framework of Chowdhury et al. (2024) -- has made the
case that combining methods materially reduces method-specific bias and
sharpens biological interpretation.

`ggNetView::build_graph_from_consensus()` takes a list of per-method
adjacency matrices and aggregates them via one of four strategies, with
three rank-fusion algorithms available under the rank-fusion route:

| `method`            | What it does                                            |
|---------------------|---------------------------------------------------------|
| `intersection`      | Keep edges present in **every** method                  |
| `weighted_average`  | Linear combination after per-method min-max normalisation |
| `majority_vote`     | Keep edges supported by `>= min_methods`                |
| `rank_fusion`       | Aggregate per-method ranks via Borda / RRA / RRF        |

The output is a `tbl_graph` with the same schema as every other
`build_graph_from_*()` constructor, so it plugs directly into
`ggNetView()`.

```{r setup}
library(ggNetView)
```

## Step 1: Build per-method networks on the same data

We use the package's `otu_rare_relative` example data. To keep the
vignette responsive we restrict to the 40 most abundant OTUs.

```{r prepare}
data(otu_rare_relative)
mat <- as.matrix(otu_rare_relative)
mat <- mat[order(rowSums(mat), decreasing = TRUE)[seq_len(40)], ]
dim(mat)
```

We construct three networks on the same matrix using three different
inference methods. The exact downstream pipeline doesn't matter for the
consensus step -- we only need an adjacency matrix from each method.

```{r per-method-networks}
# Method 1: Spearman correlation through psych::corr.test (no
# compositionality assumption, baseline).
g_cor <- build_graph_from_mat(
  mat           = mat,
  method        = "cor",
  cor.method    = "spearman",
  proc          = "BH",
  r.threshold   = 0.4,
  p.threshold   = 0.05,
  module.method = "Fast_greedy",
  seed          = 1
)

# Method 2: WGCNA-style correlation (uses WGCNA::corAndPvalue + the
# package's standard thresholding). Produces a different sparsity
# pattern than plain correlation in practice.
g_wgcna <- build_graph_from_mat(
  mat           = mat,
  method        = "WGCNA",
  cor.method    = "spearman",
  proc          = "BH",
  r.threshold   = 0.4,
  p.threshold   = 0.05,
  module.method = "Fast_greedy",
  seed          = 1
)

# Method 3: Hmisc::rcorr -- another correlation engine with slightly
# different missing-value handling and tie behaviour.
g_hmisc <- build_graph_from_mat(
  mat           = mat,
  method        = "Hmisc",
  cor.method    = "spearman",
  r.threshold   = 0.4,
  p.threshold   = 0.05,
  module.method = "Fast_greedy",
  seed          = 1
)
```

> **Note.** In a real microbiome consensus workflow you would reach for
> SparCC and SpiecEasi to capture compositionality and partial
> correlation structure (both are available in `ggNetView` via
> `method = "SPARCC"` and `method = "SpiecEasi"`). We use lighter
> correlation-family methods here so the vignette builds quickly under
> `R CMD check`. The consensus API is method-agnostic -- the only
> requirement is that each method produces a square adjacency matrix
> with row/column names matching across methods.

Convert each `tbl_graph` back to an adjacency matrix using
`get_graph_adjacency()`:

```{r to-adj}
adj_cor   <- get_graph_adjacency(g_cor)
adj_wgcna <- get_graph_adjacency(g_wgcna)
adj_hmisc <- get_graph_adjacency(g_hmisc)

dim(adj_cor); dim(adj_wgcna); dim(adj_hmisc)
```

`build_graph_from_consensus()` aligns these matrices on a common feature
set automatically -- see `node_handling` -- so the three matrices need
not have identical row/column orders.

## Step 2: Try every consensus strategy

```{r consensus-list}
adj_list <- list(cor = adj_cor, wgcna = adj_wgcna, hmisc = adj_hmisc)

# Top-level strategies
g_intersect <- build_graph_from_consensus(
  adj_list = adj_list,
  method   = "intersection",
  top_modules = 5,
  seed = 1
)

g_wavg <- build_graph_from_consensus(
  adj_list = adj_list,
  method   = "weighted_average",
  threshold = 0.3,
  top_modules = 5,
  seed = 1
)

g_majority <- build_graph_from_consensus(
  adj_list = adj_list,
  method   = "majority_vote",
  binarize = "threshold",
  binarize_threshold = 0.4,
  min_methods = 2,
  top_modules = 5,
  seed = 1
)

# Rank-fusion route, 3 algorithms
g_borda <- build_graph_from_consensus(
  adj_list = adj_list,
  method   = "rank_fusion",
  rank_fusion_algorithm = "borda",
  threshold = 0.5,
  top_modules = 5,
  seed = 1
)

g_rrf <- build_graph_from_consensus(
  adj_list = adj_list,
  method   = "rank_fusion",
  rank_fusion_algorithm = "rrf",
  threshold = 0.5,
  top_modules = 5,
  seed = 1
)

g_rra <- build_graph_from_consensus(
  adj_list = adj_list,
  method   = "rank_fusion",
  rank_fusion_algorithm = "rra",
  threshold = 0.5,
  top_modules = 5,
  seed = 1
)
```

## Step 3: Compare consensus outputs

A quick sanity check: how many nodes and edges does each strategy keep?

```{r compare-table}
summarise <- function(g, label) {
  data.frame(
    method = label,
    n_nodes = igraph::gorder(g),
    n_edges = igraph::gsize(g)
  )
}

do.call(rbind, list(
  summarise(g_intersect, "intersection"),
  summarise(g_wavg,      "weighted_average"),
  summarise(g_majority,  "majority_vote"),
  summarise(g_borda,     "rank_fusion / borda"),
  summarise(g_rrf,       "rank_fusion / rrf"),
  summarise(g_rra,       "rank_fusion / rra")
))
```

The strict strategies (`intersection`, `majority_vote`) tend to produce
sparser, more conservative networks -- only edges that are robustly
supported across methods survive. The rank-fusion family typically
yields denser networks because it ranks every pair and lets the user
pick a final cutoff via `threshold`.

## Step 4: Visualise the consensus network

Any consensus result can be passed to `ggNetView()` exactly like any
other `build_graph_from_*()` output. Below we show the Borda-fused
network with module colouring:

```{r plot-borda, fig.width = 7, fig.height = 6, fig.cap = "Consensus network from rank-fusion / Borda."}
ggNetView(
  g_borda,
  layout    = "fr",
  seed      = 1,
  pointsize = c(2, 7),
  fill.by   = "Modularity",
  label     = FALSE
)
```

```{r plot-intersection, fig.width = 7, fig.height = 6, fig.cap = "Strict-intersection consensus: only edges shared across all three methods."}
ggNetView(
  g_intersect,
  layout    = "fr",
  seed      = 1,
  pointsize = c(2, 7),
  fill.by   = "Modularity",
  label     = FALSE
)
```

## Step 5: Choosing a strategy

As a starting point:

* **Reach for `rank_fusion / rra`** when you want a statistically
  justified consensus score with a notion of "significantly more
  supported than uniform random ranking". This is the closest analogue
  to what CMiNet does for microbiome networks. Falls back to a
  Beta-order-statistic implementation when the optional
  `RobustRankAggreg` package is not installed.
* **Reach for `rank_fusion / rrf`** when robustness to outlier ranks
  matters more than statistical interpretability. RRF is parameter-free
  in practice (the `k = 60` default is the information-retrieval
  convention).
* **Reach for `rank_fusion / borda`** as a baseline -- it has no
  hyperparameters and is the easiest to explain.
* **Reach for `intersection`** when you specifically want the smallest,
  most defensible set of edges.
* **Reach for `weighted_average`** when one of your methods is known to
  be more reliable on this data and you want to bias the consensus
  toward it (set its weight higher in `weights`).

The result of any strategy is a standard `tbl_graph`, so every
downstream analysis in the package -- topological summaries
(`get_network_topology()`), zi-pi role classification
(`ggnetview_zipi()`), per-module subgraphs (`get_subgraph()`), and the
full layout gallery -- works without modification.

## Importing networks built by external tools

`build_graph_from_consensus()` does not assume the input matrices were
built by `ggNetView`. Networks produced by external R packages or
standalone tools (NetCoMi, CoNet, FlashWeave, CMiNet, ...) can be
combined too -- as long as you can express each as a square numeric
adjacency matrix with row and column names. Concretely:

```r
adj_list <- list(
  netcomi   = as.matrix(netcomi_assoc_mat),
  flashweave = as.matrix(flashweave_adj),
  cminet    = as.matrix(cminet_consensus_mat)
)

obj <- build_graph_from_consensus(
  adj_list = adj_list,
  method   = "rank_fusion",
  rank_fusion_algorithm = "rra",
  threshold = 0.4
)
```

This is the same `method`-agnostic interface that
`build_graph_from_adj_mat()`, `build_graph_from_df()`,
`build_graph_from_node_edge()`, `build_graph_from_igraph()`,
`build_graph_from_wgcna()`, and `build_graph_from_stringdb()` provide --
extended to accept multiple inputs at once.

## References

* Kolde, R., Laur, S., Adler, P., & Vilo, J. (2012). Robust rank
  aggregation for gene list integration and meta-analysis.
  *Bioinformatics*, 28(4), 573-580.
* Cormack, G. V., Clarke, C. L. A., & Buettcher, S. (2009). Reciprocal
  rank fusion outperforms Condorcet and individual rank learning
  methods. *Proceedings of the 32nd International ACM SIGIR Conference*,
  758-759.
* Aghayeva, R., et al. (2024). CMiNet: An R package and user-friendly
  Shiny App for constructing consensus microbiome networks.
* Chowdhury, S., et al. (2024). A hybrid framework for disease biomarker
  discovery in microbiome research combining Bayesian networks, machine
  learning, and network-based methods.
