---
title: "Node-Importance Analysis: Multiple Centralities and IVI"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Node-Importance Analysis: Multiple Centralities and IVI}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

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

## Why look beyond degree?

Degree is the simplest measure of node importance, and it's what most
network analyses lead with -- including the per-node `Degree` and
`Strength` columns that every `build_graph_from_*()` constructor in
`ggNetView` already attaches. But degree only counts a node's immediate
neighbours; it doesn't tell you whether those neighbours sit on
shortest paths between distant communities (betweenness), whether the
node is "central" in the global topology (closeness, eigenvector), or
whether it functions as a bridge to other modules (Zi-Pi). For a
defensible node-importance analysis you usually want several
complementary perspectives, plus an integrative score that combines
them.

`ggNetView` exposes node-importance functionality at three levels:

| Function                          | What it gives you                           |
|-----------------------------------|---------------------------------------------|
| `Degree`, `Strength` columns      | Local connectivity (set automatically)      |
| `get_node_centrality()`           | Eight per-node centralities (this vignette) |
| `ggnetview_zipi()`                | Module-aware Zi-Pi role classification      |
| `get_node_ivi()`                  | Salavaty 2020 Integrated Value of Influence |
| `get_network_topology()`          | Network-level summaries of the same metrics |

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

## Build a network

We use the package's `otu_rare_relative` example data and pick the 50
most abundant OTUs to keep the vignette responsive.

```{r build}
data(otu_rare_relative)
data(tax_tab)

mat <- as.matrix(otu_rare_relative)
mat <- mat[order(rowSums(mat), decreasing = TRUE)[seq_len(50)], ]

obj <- build_graph_from_mat(
  mat             = mat,
  method          = "cor",
  cor.method      = "spearman",
  proc            = "BH",
  r.threshold     = 0.5,
  p.threshold     = 0.05,
  module.method   = "Fast_greedy",
  node_annotation = tax_tab[tax_tab$OTUID %in% rownames(mat), ],
  seed            = 1
)
obj
```

The result is a `tbl_graph` whose node table already carries `Degree`,
`Strength`, and `Modularity`.

## Step 1: Compute per-node centralities

```{r centrality}
obj_aug <- get_node_centrality(obj)

obj_aug %>%
  tidygraph::activate(nodes) %>%
  tidygraph::as_tibble() %>%
  dplyr::select(name, Degree, Strength, Betweenness, Closeness,
                Eigenvector, PageRank, Hub_score, Coreness, Harmonic) %>%
  utils::head(8)
```

`get_node_centrality()` adds eight new columns (Betweenness, Closeness,
Eigenvector, PageRank, Hub_score, Authority_score, Coreness, Harmonic)
to the existing node schema. Pass `measures = c("Betweenness",
"PageRank")` (or any subset) to compute only what you need.

> **Weighted vs unweighted.** Centrality measures interpret edge
> weights differently. By default `get_node_centrality()` uses the
> unweighted definition; pass `weighted = TRUE` to feed `1 / |weight|`
> as the distance vector to `igraph` (so that strongly correlated pairs
> count as short paths). For module-detection-friendly inputs the two
> usually rank the same handful of nodes at the top.

## Step 2: Different centralities highlight different nodes

A common observation in network biology is that no single centrality
captures all "important" nodes -- a node can be a high-degree hub
without being on many shortest paths, or vice versa. We can quantify
that disagreement:

```{r ranking-comparison}
node_df <- obj_aug %>%
  tidygraph::activate(nodes) %>%
  tidygraph::as_tibble()

top_n <- 10
top_lists <- list(
  Degree      = node_df$name[order(-node_df$Degree)][seq_len(top_n)],
  Betweenness = node_df$name[order(-node_df$Betweenness)][seq_len(top_n)],
  PageRank    = node_df$name[order(-node_df$PageRank)][seq_len(top_n)],
  Eigenvector = node_df$name[order(-node_df$Eigenvector)][seq_len(top_n)]
)

# Pairwise overlap between top-10 lists.
pairs <- utils::combn(names(top_lists), 2, simplify = FALSE)
do.call(rbind, lapply(pairs, function(pair) {
  data.frame(
    a = pair[1L],
    b = pair[2L],
    overlap_top10 = length(intersect(top_lists[[pair[1L]]],
                                     top_lists[[pair[2L]]]))
  )
}))
```

If two centralities had perfect agreement their pairwise overlap would
be 10; if they disagreed completely it would be 0. Real microbiome /
gene networks usually fall somewhere in between, and that gap is
exactly what a single-metric importance ranking misses.

## Step 3: Integrated Value of Influence (IVI)

`get_node_ivi()` collapses local, semi-local, and global centralities
into one score using the formula of Salavaty et al. (2020). It's a
thin wrapper over `influential::ivi()` -- the canonical
implementation by the IVI paper's first author -- so the values match
the published algorithm exactly.

The function does one thing: it adds a single new column called `IVI`
to the input graph's node table and returns the augmented `tbl_graph`.
It does **not** call `ggNetView()` or produce any plot -- what to do
with the IVI column is entirely up to you.

```{r ivi, eval = has_influential}
obj_aug <- get_node_ivi(obj_aug)

# The graph is unchanged except for one new column on the node table:
obj_aug %>%
  tidygraph::activate(nodes) %>%
  tidygraph::as_tibble() %>%
  dplyr::select(name, Modularity, Degree, Betweenness, PageRank, IVI) %>%
  utils::head(5)
```

### The most common deliverable is just a ranked table

For most reports and papers, the deliverable is a small table of the
most-influential nodes. One pipe:

```{r ivi-rank, eval = has_influential}
obj_aug %>%
  tidygraph::activate(nodes) %>%
  tidygraph::as_tibble() %>%
  dplyr::arrange(dplyr::desc(IVI)) %>%
  dplyr::select(name, Modularity, Degree, IVI) %>%
  utils::head(10)
```

That's the whole node-importance answer in five lines. No plot
required.

### Picking a defensible threshold for "significantly influential"

Default `get_node_ivi()` uses `influential::ivi(scale = "range")`,
which normalises IVI to `[1, 100]` -- great for inspecting the full
spread of node influences within one network. Two other scaling modes
support different downstream questions:

* `scale = "z-scale"` standardises IVI as a z-score. Use this when
  comparing influence across multiple networks (e.g. gut vs soil
  microbiome), or when you want a numeric threshold backed by a
  one-sided 0.05 cutoff -- the IVI paper suggests `z > 1.645`.
* `scale = "none"` returns the raw IVI scores untouched.

```{r ivi-zscale, eval = has_influential}
obj_z <- get_node_ivi(obj_aug, scale = "z-scale")
obj_z %>%
  tidygraph::activate(nodes) %>%
  tidygraph::as_tibble() %>%
  dplyr::filter(IVI > 1.645) %>%
  dplyr::arrange(dplyr::desc(IVI)) %>%
  dplyr::select(name, Modularity, Degree, IVI)
```

Larger `IVI` always means more influential, so ranking by
`dplyr::arrange(dplyr::desc(IVI))` works identically across all three
scaling choices.

## Step 4 (optional): Map IVI onto a network plot

If you also want a network plot with IVI as a visual aesthetic, you
can pass the `IVI`-augmented graph to `ggNetView()`. Two notes:

1. `ggNetView()` is a discrete-fill plotter -- its `fill.by` argument
   maps to `ggplot2::scale_fill_manual()`, so it expects a factor /
   character column, not raw numeric.
2. Easiest fix: bin `IVI` into a small ordered factor first (quartiles
   are usually informative), then pass that bin column as `fill.by`.

```{r plot-ivi, eval = has_influential, fig.width = 7, fig.height = 6, fig.cap = "Network coloured by IVI quartile; warmer = more influential."}
obj_plot <- obj_aug %>%
  tidygraph::activate(nodes) %>%
  tidygraph::mutate(
    IVI_bin = cut(
      IVI,
      breaks = stats::quantile(IVI, probs = seq(0, 1, 0.25), na.rm = TRUE),
      labels = c("Q1 (low)", "Q2", "Q3", "Q4 (high)"),
      include.lowest = TRUE,
      ordered_result = TRUE
    )
  )

ggNetView(
  obj_plot,
  layout    = "fr",
  seed      = 1,
  pointsize = c(2, 8),
  fill.by   = "IVI_bin",
  label     = FALSE
)
```

The same recipe works for any other continuous centrality column
(`Betweenness`, `PageRank`, `Eigenvector`, ...): wrap it in `cut()`
to produce an ordered factor, then pass that factor as `fill.by`.

## Step 5: Combine with Zi-Pi role classification

For a complete node-importance picture in a modular network, layer
IVI on top of Guimera & Amaral's Zi-Pi roles:

```{r zipi, eval = has_influential, fig.cap = "Zi-Pi quadrant plot; node roles classified by within- vs across-module connectivity."}
nodes_bulk <- get_graph_nodes(obj_aug)
adj_mat    <- get_graph_adjacency(obj_aug)

zp <- ggnetview_zipi(
  nodes_bulk      = nodes_bulk,
  z_bulk_mat      = adj_mat,
  modularity_col  = "Modularity",
  degree_col      = "Degree"
)

zp$plot
```

The four quadrants of the Zi-Pi plot -- `Module hubs`, `Connectors`,
`Network hubs`, and `Peripherals` -- partition the network by
**topological role**, while IVI ranks every node on a single
**influence axis**. The two views answer different questions and tend
to complement each other: a top-IVI node that lands in the
`Network hubs` quadrant is a doubly defensible candidate for
biological follow-up.

## Choosing a measure

* **Single best general-purpose score**: `IVI`. Built specifically to
  reduce the bias of any one centrality.
* **For pathfinding / signal-flow questions**: `Betweenness` (number
  of shortest paths) or `PageRank` (weighted random-walk score).
* **For "central in the eigen-structure" questions**: `Eigenvector`
  centrality.
* **For module-aware roles**: pair Zi-Pi (`ggnetview_zipi()`) with IVI.
* **For network-level comparisons** (e.g. comparing two cohorts):
  `get_network_topology()` gives the same families of metrics as
  scalar means / sums, useful as cohort-vs-cohort summaries.

## References

* Salavaty, A., Ramialison, M., & Currie, P. D. (2020). Integrated
  Value of Influence: An Integrative Method for the Identification of
  the Most Influential Nodes within Networks. *Patterns*, 1(5),
  100052.
* Guimera, R., & Amaral, L. A. N. (2005). Functional cartography of
  complex metabolic networks. *Nature*, 433(7028), 895-900.
