cluster_summary: Cluster Summary Statistics

View source: R/mcml.R

cluster_summaryR Documentation

Cluster Summary Statistics

Description

Aggregates node-level network weights to cluster-level summaries. Computes both between-cluster transitions (how clusters connect to each other) and within-cluster transitions (how nodes connect within each cluster).

Usage

cluster_summary(
  x,
  clusters = NULL,
  method = c("sum", "mean", "median", "max", "min", "density", "geomean"),
  directed = TRUE,
  compute_within = TRUE
)

Arguments

x

Network input. Accepts multiple formats:

matrix

Numeric adjacency/weight matrix. Row and column names are used as node labels. Values represent edge weights (e.g., transition counts, co-occurrence frequencies, or probabilities).

netobject

A cograph network object. The function extracts the weight matrix from x$weights or converts via to_matrix(). Clusters can be auto-detected from node attributes.

tna

A tna object from the tna package. Extracts x$weights.

cluster_summary

If already a cluster_summary, returns unchanged.

clusters

Cluster/group assignments for nodes. Accepts multiple formats:

NULL

(default) Auto-detect from netobject. Looks for columns named 'clusters', 'cluster', 'groups', or 'group' in x$nodes. Throws an error if no cluster column is found. This option only works when x is a netobject.

vector

Cluster membership for each node, in the same order as the matrix rows/columns. Can be numeric (1, 2, 3) or character ("A", "B"). Cluster names will be derived from unique values. Example: c(1, 1, 2, 2, 3, 3) assigns first two nodes to cluster 1.

data.frame

A data frame where the first column contains node names and the second column contains group/cluster names. Example: data.frame(node = c("A", "B", "C"), group = c("G1", "G1", "G2"))

named list

Explicit mapping of cluster names to node labels. List names become cluster names, values are character vectors of node labels that must match matrix row/column names. Example: list(Alpha = c("A", "B"), Beta = c("C", "D"))

method

Aggregation method for combining edge weights within/between clusters. Controls how multiple node-to-node edges are summarized:

"sum"

(default) Sum of all edge weights. Best for count data (e.g., transition frequencies). Preserves total flow.

"mean"

Average edge weight. Best when cluster sizes differ and you want to control for size. Note: when input is already a transition matrix (rows sum to 1), "mean" avoids size bias. Example: cluster with 5 nodes won't have 5x the weight of cluster with 1 node.

"median"

Median edge weight. Robust to outliers.

"max"

Maximum edge weight. Captures strongest connection.

"min"

Minimum edge weight. Captures weakest connection.

"density"

Sum of (non-zero) edge weights divided by the number of possible edges between the two clusters (n_i * n_j). Normalizes by cluster size combinations. Because zero/NA edges are stripped before aggregation, this equals "mean" exactly when the cluster-pair block is fully dense (no zero edges), and is strictly smaller than "mean" when zero edges are present (it divides by the larger possible-edge count).

"geomean"

Geometric mean of positive weights. Useful for multiplicative processes.

directed

Logical. If TRUE (default), treat network as directed. A->B and B->A are separate edges. If FALSE, edges are undirected and the matrix is symmetrized before processing.

compute_within

Logical. If TRUE (default), compute within-cluster transition matrices for each cluster. Each cluster gets its own n_i x n_i matrix showing internal node-to-node transitions. Set to FALSE to skip this computation for better performance when only between-cluster summary is needed.

Details

This is the core function for Multi-Cluster Multi-Level (MCML) analysis. Use as_tna() to convert results to tna objects for further analysis with the tna package.

Workflow

Typical MCML analysis workflow:

# 1. Create network
net <- build_network(data, method = "relative")
net$nodes$clusters <- group_assignments

# 2. Compute cluster summary (arithmetic aggregation over edges)
cs <- cluster_summary(net, method = "sum")

# 3. Convert to tna models (normalization happens in as_tna)
tna_models <- as_tna(cs)

# 4. Analyze/visualize
plot(tna_models$macro)
tna::centralities(tna_models$macro)

Between-Cluster Matrix Structure

The macro$weights matrix has clusters as both rows and columns:

  • Off-diagonal (row i, col j): Aggregated weight from cluster i to cluster j

  • Diagonal (row i, col i): Within-cluster total (aggregation of internal edges)

Rows are NOT normalized. Entries are elementwise aggregates produced by method. If the caller wants probabilities, they should normalize downstream (e.g. via as_tna()). Mixing an arithmetic aggregation with row-normalization here (the old type = "tna" combined with method = "min" / "mean" etc.) produces numbers that sum to 1 per row but are not a probability distribution over any process; that silently-wrong combination is why type was removed from the matrix path. The sequence and edgelist paths of build_mcml() keep type, where the aggregation is always counts and the post-processing chooses between well-defined network constructions.

Choosing method

Input data Recommended method Reason
Edge counts "sum" Preserves total flow between clusters
Transition matrix "mean" Avoids cluster size bias
Correlation matrix "mean" Average correlations
Dense weighted "max" / "median" Robust summary

Value

A cluster_summary object (S3 class) containing:

between

List with two elements:

weights

k x k matrix of cluster-to-cluster weights, where k is the number of clusters. Row i, column j contains the elementwise aggregation (per method) of all edges from nodes in cluster i to nodes in cluster j. Diagonal contains within-cluster totals. Pure arithmetic – no row normalization.

inits

Numeric vector of length k. Initial state distribution across clusters, computed from column sums of the original matrix. Represents the proportion of incoming edges to each cluster.

within

Named list with one element per cluster. Each element contains:

weights

n_i x n_i matrix for nodes within that cluster. Shows internal transitions between nodes in the same cluster.

inits

Initial distribution within the cluster.

NULL if compute_within = FALSE.

clusters

Named list mapping cluster names to their member node labels. Example: list(A = c("n1", "n2"), B = c("n3", "n4", "n5"))

meta

List of metadata:

method

The method argument used ("sum", "mean", etc.)

directed

Logical, whether network was treated as directed

n_nodes

Total number of nodes in original network

n_clusters

Number of clusters

cluster_sizes

Named vector of cluster sizes

See Also

as_tna() to convert results to tna objects, plot() for two-layer visualization, plot() for flat cluster visualization

Examples

# -----------------------------------------------------
# Basic usage with matrix and cluster vector
# -----------------------------------------------------
mat <- matrix(runif(100), 10, 10)
rownames(mat) <- colnames(mat) <- LETTERS[1:10]

clusters <- c(1, 1, 1, 2, 2, 2, 3, 3, 3, 3)
cs <- cluster_summary(mat, clusters)

# Access results
cs$macro$weights    # 3x3 cluster transition matrix
cs$macro$inits      # Initial distribution
cs$clusters$`1`$weights # Within-cluster 1 transitions
cs$meta               # Metadata

# -----------------------------------------------------
# Named list clusters (more readable)
# -----------------------------------------------------
clusters <- list(
  Alpha = c("A", "B", "C"),
  Beta = c("D", "E", "F"),
  Gamma = c("G", "H", "I", "J")
)
cs <- cluster_summary(mat, clusters)
cs$macro$weights    # Rows/cols named Alpha, Beta, Gamma
cs$clusters$Alpha       # Within Alpha cluster

# -----------------------------------------------------
# Auto-detect clusters from netobject
# -----------------------------------------------------

seqs <- data.frame(
  V1 = sample(LETTERS[1:10], 30, TRUE), V2 = sample(LETTERS[1:10], 30, TRUE),
  V3 = sample(LETTERS[1:10], 30, TRUE)
)
net <- build_network(seqs, method = "relative")
cs2 <- cluster_summary(net, c(1, 1, 1, 2, 2, 2, 3, 3, 3, 3))


# -----------------------------------------------------
# Different aggregation methods
# -----------------------------------------------------
cs_sum <- cluster_summary(mat, clusters, method = "sum")   # Total flow
cs_mean <- cluster_summary(mat, clusters, method = "mean") # Average
cs_max <- cluster_summary(mat, clusters, method = "max")   # Strongest

# -----------------------------------------------------
# Skip within-cluster computation for speed
# -----------------------------------------------------
cs_fast <- cluster_summary(mat, clusters, compute_within = FALSE)
cs_fast$clusters  # NULL

# -----------------------------------------------------
# Convert to tna objects for tna package
# (as_tna() applies its own row normalisation)
# -----------------------------------------------------
cs <- cluster_summary(mat, clusters, method = "sum")
tna_models <- as_tna(cs)
# tna_models$macro      # tna object
# tna_models$clusters$Alpha # tna object

Nestimate documentation built on July 11, 2026, 1:09 a.m.