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 network object built by
build_network. Its weight matrixx$weightsis aggregated; a barecograph_networkis coerced to a netobject first.- tna
A tna object from the tna package. Extracts
x$weights.- mcml
An
mcmlobject is returned unchanged.
- clusters
Cluster/group assignments for nodes. Accepts multiple formats:
- NULL
(default) Not usable here:
clustersis required andNULLraises an error. Auto-detection of a cluster column in anetobject's node table happens inbuild_mcml, which then calls this function with the detected assignment.- 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/NAedges 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. IfFALSE, 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 toFALSEto skip this computation for better performance when only between-cluster summary is needed.
Value
An mcml object (S3 class): a list with
- macro
An
mcml_layerholding the cluster-level network:$weights, the k x k matrix whose entry (i, j) is the aggregation (permethod) of all edges from nodes in cluster i to nodes in cluster j, with the diagonal holding the within-cluster edges – pure arithmetic, no row normalization;$inits, the length-k column sums of that matrix normalized to sum to 1;$labels, the cluster names; and$data,NULLon this path.- clusters
Named list with one
mcml_layerper cluster. Its$weightsis the n_i x n_i submatrix of the nodes in that cluster and its$initsthe normalized column sums of that submatrix.NULLwhencompute_within = FALSE.- cluster_members
Named list mapping cluster names to their member node labels, e.g.
list(A = c("n1", "n2"), B = c("n3", "n4", "n5")).- edges
NULLon this path – a matrix carries no node-level transitions. The sequence and edge-list paths ofbuild_mcmlfill in a tidy edge table here.- meta
List with
type(always"aggregate"here),method,directed,n_nodes,n_clusters,cluster_sizes(named integer vector) andsource("matrix").
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 the node-level network
net <- build_network(data, method = "relative")
# 2. Aggregate its edges to cluster level (arithmetic aggregation)
cs <- cluster_summary(net, clusters = group_assignments, method = "sum")
# 3. Read the result
print(cs) # macro weights
summary(cs) # one row per cluster
# 4. Promote the layers to netobjects for downstream verbs
nets <- as_tna(cs)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.
See also
build_mcml to build an mcml from raw transitions instead
of a weight matrix,
as_tna to promote the layers to netobjects,
macro_network for the cluster-level network with one
cluster expanded back into its member states
Examples
# -----------------------------------------------------
# Basic usage with matrix and cluster vector
# -----------------------------------------------------
set.seed(1)
mat <- matrix(runif(100), 10, 10)
rownames(mat) <- colnames(mat) <- LETTERS[1:10]
cs <- cluster_summary(mat, c(1, 1, 1, 2, 2, 2, 3, 3, 3, 3))
cs # cluster-level (macro) weights
#> MCML Network
#> ============
#> Type: aggregate | Method: sum
#> Nodes: 10 | Clusters: 3
#>
#> Clusters:
#> 1 (3): A, B, C
#> 2 (3): D, E, F
#> 3 (4): G, H, I, J
#>
#> Macro (cluster-level) weights:
#> 1 2 3
#> 1 4.0786 5.6031 5.6788
#> 2 4.4388 3.9691 6.6812
#> 3 6.7692 5.8665 8.6995
summary(cs) # one row per cluster
#> cluster size within_total between_out between_in
#> 1 1 3 2.984822 11.28182 11.20801
#> 2 2 3 3.153709 11.12004 11.46957
#> 3 3 4 6.980503 12.63571 12.35999
# -----------------------------------------------------
# Named list clusters (more readable)
# -----------------------------------------------------
clusters <- list(
Alpha = c("A", "B", "C"),
Beta = c("D", "E", "F"),
Gamma = c("G", "H", "I", "J")
)
cluster_summary(mat, clusters)
#> MCML Network
#> ============
#> Type: aggregate | Method: sum
#> Nodes: 10 | Clusters: 3
#>
#> Clusters:
#> Alpha (3): A, B, C
#> Beta (3): D, E, F
#> Gamma (4): G, H, I, J
#>
#> Macro (cluster-level) weights:
#> Alpha Beta Gamma
#> Alpha 4.0786 5.6031 5.6788
#> Beta 4.4388 3.9691 6.6812
#> Gamma 6.7692 5.8665 8.6995
# -----------------------------------------------------
# A netobject as input: its weight matrix is aggregated
# -----------------------------------------------------
seqs <- data.frame(
T1 = c("A", "C", "B", "D"), T2 = c("B", "D", "A", "C"),
T3 = c("C", "A", "D", "B")
)
net <- build_network(seqs, method = "relative")
cluster_summary(net, list(G1 = c("A", "B"), G2 = c("C", "D")))
#> MCML Network
#> ============
#> Type: aggregate | Method: sum
#> Nodes: 4 | Clusters: 2
#>
#> Clusters:
#> G1 (2): A, B
#> G2 (2): C, D
#>
#> Macro (cluster-level) weights:
#> G1 G2
#> G1 1 1
#> G2 1 1
# -----------------------------------------------------
# Different aggregation methods
# -----------------------------------------------------
summary(cluster_summary(mat, clusters, method = "sum")) # total flow
#> cluster size within_total between_out between_in
#> 1 Alpha 3 2.984822 11.28182 11.20801
#> 2 Beta 3 3.153709 11.12004 11.46957
#> 3 Gamma 4 6.980503 12.63571 12.35999
summary(cluster_summary(mat, clusters, method = "mean")) # average
#> cluster size within_total between_out between_in
#> 1 Alpha 3 2.984822 1.095792 1.057301
#> 2 Beta 3 3.153709 1.049971 1.111438
#> 3 Gamma 4 6.980503 1.052976 1.029999
summary(cluster_summary(mat, clusters, method = "max")) # strongest
#> cluster size within_total between_out between_in
#> 1 Alpha 3 2.984822 1.774085 1.900114
#> 2 Beta 3 3.153709 1.800406 1.655449
#> 3 Gamma 4 6.980503 1.786146 1.805074
# -----------------------------------------------------
# Skip within-cluster computation for speed
# -----------------------------------------------------
cluster_summary(mat, clusters, compute_within = FALSE)
#> MCML Network
#> ============
#> Type: aggregate | Method: sum
#> Nodes: 10 | Clusters: 3
#>
#> Clusters:
#> Alpha (3): A, B, C
#> Beta (3): D, E, F
#> Gamma (4): G, H, I, J
#>
#> Macro (cluster-level) weights:
#> Alpha Beta Gamma
#> Alpha 4.0786 5.6031 5.6788
#> Beta 4.4388 3.9691 6.6812
#> Gamma 6.7692 5.8665 8.6995
# -----------------------------------------------------
# Promote the layers to netobjects
# (as_tna() stores the aggregated weights as they are)
# -----------------------------------------------------
as_tna(cluster_summary(mat, clusters, method = "sum"))
#> Group Networks (4 groups)
#>
#> Group Nodes Edges Weights
#> macro 3 9 [3.969, 8.699]
#> Alpha 3 9 [0.177, 0.935]
#> Beta 3 9 [0.071, 0.827]
#> Gamma 4 16 [0.084, 0.961]