Actor Achiever Group Course Time Action
1 1 High 1 A 2025-01-01 08:27:07 cohesion
2 1 High 1 A 2025-01-01 08:35:20 consensus
3 1 High 1 A 2025-01-01 08:42:18 discuss
4 1 High 1 A 2025-01-01 08:50:00 synthesis
5 1 High 1 A 2025-01-01 08:52:25 adapt
6 1 High 1 A 2025-01-01 08:57:31 consensus

Network Estimation and Visualization with Nestimate + cograph
2026-04-09
Source:vignettes/articles/cograph-tutorial-nestimate.qmd
1 Introduction
Nestimate is a unified network estimation package for R. It provides a single entry point — build_network() — that accepts sequence data, wide-format behavioral data, or numeric matrices and estimates networks using any of 10 built-in methods. The result is a netobject that inherits cograph_network, so every object produced by Nestimate can be plotted with splot() directly.
The core design principle is that estimation and visualization are separate concerns: Nestimate handles all computation (network construction, bootstrapping, permutation testing, clustering, multi-cluster analysis), while cograph handles all rendering. This separation means the analyst writes estimation code once and gets publication-ready figures without adapter code or manual class conversion.
Beyond single-network estimation, Nestimate provides a complete statistical toolkit:
-
Bootstrap stability analysis (
bootstrap_network) — identifies which edges survive resampling and which are sampling artifacts. -
Permutation-based group comparison (
permutation) — tests whether edges differ significantly between conditions. -
Multi-cluster multi-level analysis (
cluster_summary,build_mcml) — aggregates node-level networks into cluster-level summaries for hierarchical visualization. -
Centrality stability (
centrality_stability) — assesses whether centrality rankings are robust to case dropping. - Group-level operations — bootstrapping, permutation testing, and plotting work on grouped networks with no additional code.
This tutorial walks through the full pipeline with the group_regulation_long dataset shipped with Nestimate.
2 Data
group_regulation_long is a long-format dataset of collaborative regulation behaviors coded across student groups. Each row is one coded action in a session, with columns identifying the actor, the group, achievement level, course, timestamp, and the regulation behavior.
The nine regulation behaviors are: adapt, cohesion, consensus, coregulate, discuss, emotion, monitor, plan, and synthesis. These form the nodes of the networks we estimate below.
3 Estimating Networks
3.1 A single network
build_network() estimates a network from raw data. The method argument selects the estimation algorithm. Here we use "relative", which computes row-normalized transition probabilities — the standard approach in transition network analysis (TNA). Unlike constructing a TNA model manually (preparing wide-format data, computing the transition matrix, normalizing), build_network() handles all data preparation internally.
net <- build_network(
group_regulation_long,
action = "Action", actor = "Actor",
method = "relative"
)
netTransition Network (relative probabilities) [directed]
Weights: [0.001, 0.498] | mean: 0.116
Weight matrix:
adapt cohesion consensus coregulate discuss emotion monitor plan
adapt 0.000 0.273 0.477 0.022 0.059 0.120 0.033 0.016
cohesion 0.003 0.027 0.498 0.119 0.060 0.116 0.033 0.141
consensus 0.005 0.015 0.082 0.188 0.188 0.073 0.047 0.396
coregulate 0.016 0.036 0.135 0.023 0.274 0.172 0.086 0.239
discuss 0.071 0.048 0.321 0.084 0.195 0.106 0.022 0.012
emotion 0.002 0.325 0.320 0.034 0.102 0.077 0.036 0.100
monitor 0.011 0.056 0.159 0.058 0.375 0.091 0.018 0.216
plan 0.001 0.025 0.290 0.017 0.068 0.147 0.076 0.374
synthesis 0.235 0.034 0.466 0.044 0.063 0.071 0.012 0.075
synthesis
adapt 0.000
cohesion 0.004
consensus 0.008
coregulate 0.019
discuss 0.141
emotion 0.003
monitor 0.016
plan 0.002
synthesis 0.000
Initial probabilities:
consensus 0.214 ████████████████████████████████████████
plan 0.204 ██████████████████████████████████████
discuss 0.175 █████████████████████████████████
emotion 0.151 ████████████████████████████
monitor 0.144 ███████████████████████████
cohesion 0.060 ███████████
synthesis 0.019 ████
coregulate 0.019 ████
adapt 0.011 ██
The returned net is a netobject (which inherits cograph_network), so splot() renders it directly:
splot(net)3.2 Available estimators
Nestimate ships with 10 built-in estimators. Each is selected via the method argument to build_network():
name description
1 attention Decay-weighted attention transitions
2 co_occurrence Co-occurrence within sequences
3 cor Pairwise correlation network
4 frequency Raw transition frequency counts
5 glasso EBICglasso regularized partial correlations
6 ising Ising model (L1-penalized logistic regression)
7 mgm Mixed Graphical Model (nodewise lasso, EBIC, LW threshold)
8 pcor Unregularized partial correlations
9 relative Row-normalized transition probabilities
10 wtna Window-based TNA transitions (one-hot)
11 wtna_cooccurrence Window-based TNA co-occurrence (one-hot)
directed
1 TRUE
2 FALSE
3 FALSE
4 TRUE
5 FALSE
6 FALSE
7 FALSE
8 FALSE
9 TRUE
10 TRUE
11 FALSE
Different estimators answer different research questions. Directed methods (relative, frequency, attention) model sequential transitions — which behavior follows which. Undirected methods (glasso, pcor, cor, co_occurrence, ising) model co-occurrence or partial correlation structures — which behaviors tend to appear together. The choice of method should be driven by the theoretical question, not by convenience.
frequency_net <- build_network(group_regulation_long, action = "Action",
actor = "Actor", method = "frequency")
attention_net <- build_network(group_regulation_long, action = "Action",
actor = "Actor", method = "attention")
co_occurrence_net <- build_network(group_regulation_long, action = "Action",
actor = "Actor", method = "co_occurrence")Each of these is a netobject that works with splot():
splot(frequency_net)
splot(co_occurrence_net, layout = "spring")4 Bootstrap Stability
To ensure that the patterns we observe reflect genuine structure rather than sampling noise, we need bootstrap resampling. bootstrap_network() resamples the data, refits the model at each iteration, and identifies which edges are stable. The result is a net_bootstrap object.
set.seed(265)
boot <- bootstrap_network(net, iter = 1000)
boot Edge Mean 95% CI p
-----------------------------------------------
cohesion → consensus 0.498 [0.474, 0.521] ***
adapt → consensus 0.478 [0.430, 0.523] ***
synthesis → consensus 0.466 [0.427, 0.507] ***
consensus → plan 0.396 [0.383, 0.407] ***
monitor → discuss 0.375 [0.351, 0.401] ***
... and 46 more significant edges
Bootstrap Network [Transition Network (relative) | directed]
Iterations : 1000 | Nodes : 9
Edges : 47 significant / 71 total
CI : 95% | Inference: stability | CR [0.75, 1.25]
4.1 Styled mode (default)
Significant edges (p < 0.05) appear solid dark blue; non-significant edges appear dashed grey. This immediately separates the stable network structure from noise.
splot(boot)4.2 Significant edges only
splot(boot, display = "significant")4.3 Full network (no styling)
splot(boot, display = "full")4.4 Significance stars on edge labels
splot(boot, show_stars = TRUE)4.5 Confidence interval labels
splot(boot, show_ci = TRUE)
splot()display modes fornet_bootstrap
displayDescription "styled"(default)Significant = solid blue, non-significant = dashed grey "significant"Only significant edges "full"All edges, uniform styling Additional parameters:
show_stars,show_ci,inherit_style.
4.6 Forest plots
Forest plots provide a complementary view of bootstrap results, showing the point estimate and confidence interval for every edge. Three layouts are available:
plot_bootstrap_forest(boot, show_ci = TRUE)
plot_bootstrap_forest(boot, show_ci = TRUE, layout = "circular")
plot_bootstrap_forest(boot, show_ci = TRUE, layout = "grouped", scale = 0.55)5 Centrality Stability
Bootstrap alone tells us which edges are stable. To assess whether centrality rankings are robust, we need the case-dropping procedure from Epskamp et al. (2018): progressively remove increasing fractions of cases, recompute centrality, and correlate with the original ranking. The CS-coefficient is the maximum proportion of cases that can be dropped while maintaining a correlation above 0.7 in at least 95% of bootstrap samples.
set.seed(265)
cs <- centrality_stability(net, iter = 500)
csCentrality Stability (500 iterations, threshold = 0.7)
Drop proportions: 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9
CS-coefficients:
InStrength 0.90
OutStrength 0.90
Betweenness 0.90
#splot(cs)A CS-coefficient above 0.5 is generally considered acceptable; above 0.7 is good (Epskamp et al., 2018). The horizontal dashed line marks the 0.7 threshold.
6 Group Networks
6.1 Building group networks
Pass group = to build_network() and it estimates a separate network per group level, returning a netobject_group. splot() dispatches to plot_netobject_group() and renders all groups in an automatic grid.
grp_net <- build_network(
group_regulation_long, method = "relative",
action = "Action", actor = "Actor", time = "Time",
group = "Achiever"
)
names(grp_net)[1] "High" "Low"
splot(grp_net)common_scale = TRUE (default) ensures both panels use the same maximum weight, so edge widths are directly comparable. Turn it off when you want each panel to use its own scale:
splot(grp_net, common_scale = FALSE, title_prefix = "Achievers: ")6.2 Group bootstrap
Bootstrapping works on grouped networks. bootstrap_network() applied to a netobject_group returns a net_bootstrap_group — one bootstrap result per group, rendered in a multi-panel grid.
set.seed(265)
gboot <- bootstrap_network(grp_net, iter = 500)
#splot(gboot)7 Permutation Testing
7.1 Pairwise comparison
permutation() tests whether each edge differs significantly between two networks by comparing the observed difference to a permutation null distribution. Green edges indicate the first group is stronger; red edges indicate the second group is stronger.
set.seed(265)
perm <- permutation(grp_net$High, grp_net$Low, iter = 1000)
permPermutation Test:Transition Network (relative probabilities) [directed]
Iterations: 1000 | Alpha: 0.05
Nodes: 9 | Edges tested: 78 | Significant: 42
splot(perm)7.2 Show non-significant edges
splot(perm, show_nonsig = TRUE)7.3 With significance stars and effect sizes
splot(perm, show_stars = TRUE, show_effect = TRUE)
splot()parameters fornet_permutation
Parameter Default Description show_nonsigFALSEShow non-significant edges edge_positive_color"#009900"Color when group 1 > group 2 edge_negative_color"#C62828"Color when group 2 > group 1 show_starsTRUESignificance stars (*, **, ***) show_effectFALSEEffect size in parentheses
7.4 Group-level permutation
When applied to a netobject_group, permutation() runs all pairwise comparisons and returns a net_permutation_group. splot() renders each comparison in a grid.
set.seed(265)
gperm <- permutation(grp_net, iter = 1000)
#splot(gperm)8 Multi-Cluster Multi-Level Analysis (MCML)
A common analytical question is: given a network of individual states (nodes), can we group them into meaningful clusters and study how these clusters interact? The MCML (Multi-Cluster Multi-Level) pipeline does exactly this. It aggregates a node-level network into a cluster-level summary, producing both a macro-level view (cluster-to-cluster transitions) and per-cluster detail networks (within-cluster structure).
There are several ways to build an MCML analysis, depending on whether you start from a pre-built network or from raw data, and whether you define clusters manually or discover them algorithmically.
8.1 Approach 1: Theory-driven clusters from a network
When domain knowledge dictates the grouping, define clusters as a named list of node labels and pass them to cluster_summary(). For collaborative regulation, we can distinguish regulatory behaviors (monitoring, planning, coregulating, adapting) from social-communicative behaviors (cohesion, consensus, discussion, emotion, synthesis):
clusters <- list(
Regulatory = c("monitor", "plan", "coregulate", "adapt"),
Social = c("cohesion", "consensus", "discuss", "emotion", "synthesis")
)
mcml_theory <- cluster_summary(net, clusters, type = "tna")
mcml_theoryCluster Summary
---------------
Type: tna
Method: sum
Clusters: 2
Nodes: 9
Cluster sizes: 4, 5
Macro (cluster-level) weights (2x2):
Inits: 0.318, 0.682
Regulatory Social
Regulatory 0.302 0.698
Social 0.332 0.668
Per-cluster weights:
Regulatory (4 nodes)
Social (5 nodes)
splot() dispatches the mcml object to plot_mcml(), rendering a two-layer visualization: the bottom layer shows within-cluster detail networks, the top layer shows the macro (cluster-to-cluster) summary.
splot(mcml_theory)8.2 Approach 2: Theory-driven clusters from a network object
build_mcml() provides the same result through a slightly different interface. When given a netobject, it uses the pre-computed weight matrix:
mcml_built <- build_mcml(net, clusters, type = "tna")
splot(mcml_built)8.3 Approach 3: Data-driven clusters
When no strong theoretical grouping exists, cluster_network() discovers clusters algorithmically. It clusters the raw sequence data (using PAM by default) and builds a separate network per cluster, returning a netobject_group:
#grp_clustered <- cluster_network(group_regulation_long, k = 3)
#names(grp_clustered)
#splot(grp_clustered)8.4 Converting MCML to individual network objects
as_tna() converts an MCML result into a netobject_group — a named list containing the macro network and each per-cluster network as individual netobject objects. This is useful when you want to analyze each cluster’s network independently (e.g., compute centrality, run bootstrap) or pass them to other functions.
splot(tna_models)8.5 Summarize to a cluster-level network
summarize_network() from cograph takes a different approach: instead of a two-layer MCML visualization, it collapses the node-level network into a single cluster-level cograph_network where each cluster becomes one node. This is useful when you only need the macro view.
summary_net <- summarize_network(net, clusters)
summary_netCograph network: 2 nodes, 4 edges ( directed )
Source: matrix
Nodes (2): Regulatory, Social
Edges: 2 / 2 (density: 100.0%)
Weights: [1.660, 2.794] | mean: 2.227
Strongest edges:
Regulatory -> Social 2.794
Social -> Regulatory 1.660
Self-loops: 2 | range: [1.206, 3.340]
Layout: none
splot(summary_net, node_size = 15, edge_labels = TRUE)MCML approaches summary
Approach Function Input Output Use when Theory-driven cluster_summary(net, clusters)netobject + named list mcmlYou know the grouping Theory-driven build_mcml(net, clusters)netobject + named list mcmlSame, alternative API Data-driven cluster_network(data, k)raw data + k netobject_groupExploratory clustering Convert as_tna(mcml)mcml object netobject_groupPer-cluster analysis Collapse summarize_network(net, clusters)netobject + clusters cograph_networkMacro-only view
9 The Complete Dispatch Chain
Every Nestimate object routes through splot() automatically. The following table summarizes all supported object classes:
| Nestimate class | Producer |
splot() renders |
|---|---|---|
netobject |
build_network() |
Standard network plot |
net_bootstrap |
bootstrap_network() |
Stability-styled network |
net_permutation |
permutation(x, y) |
Coloured difference network |
boot_glasso |
boot_glasso() |
Regularized network with stability |
netobject_group |
build_network(group=) |
Multi-panel grid |
net_bootstrap_group |
bootstrap_network(group) |
Multi-panel bootstrap |
net_permutation_group |
permutation(group) |
Multi-panel comparisons |
mcml |
cluster_summary() / build_mcml()
|
Two-layer MCML plot |
net_stability |
centrality_stability() |
Stability drop-off curve |
Each dispatcher respects all standard splot() arguments (layout, node_fill, edge_color, title, minimum, threshold, etc.), so the same customization API works everywhere.
10 References
Epskamp, S., Borsboom, D., & Fried, E. I. (2018). Estimating psychological networks and their accuracy: A tutorial paper. Behavior Research Methods, 50(1), 195–212.
R version 4.6.0 (2026-04-24)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 24.04.4 LTS
Matrix products: default
BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so; LAPACK version 3.12.0
locale:
[1] LC_CTYPE=C.UTF-8 LC_NUMERIC=C LC_TIME=C.UTF-8
[4] LC_COLLATE=C.UTF-8 LC_MONETARY=C.UTF-8 LC_MESSAGES=C.UTF-8
[7] LC_PAPER=C.UTF-8 LC_NAME=C LC_ADDRESS=C
[10] LC_TELEPHONE=C LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C
time zone: UTC
tzcode source: system (glibc)
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] cograph_2.1.1 Nestimate_0.4.3
loaded via a namespace (and not attached):
[1] gtable_0.3.6 jsonlite_2.0.0 dplyr_1.2.1
[4] compiler_4.6.0 tidyselect_1.2.1 parallel_4.6.0
[7] scales_1.4.0 yaml_2.3.12 fastmap_1.2.0
[10] ggplot2_4.0.3 R6_2.6.1 labeling_0.4.3
[13] generics_0.1.4 igraph_2.3.0 knitr_1.51
[16] tibble_3.3.1 pillar_1.11.1 RColorBrewer_1.1-3
[19] rlang_1.2.0 xfun_0.57 glasso_1.11
[22] S7_0.2.2 cli_3.6.6 withr_3.0.2
[25] magrittr_2.0.5 digest_0.6.39 grid_4.6.0
[28] tna_1.2.3 lifecycle_1.0.5 vctrs_0.7.3
[31] evaluate_1.0.5 glue_1.8.1 data.table_1.18.2.1
[34] farver_2.1.2 codetools_0.2-20 rmarkdown_2.31
[37] tools_4.6.0 pkgconfig_2.0.3 htmltools_0.5.9