---
title: "Visualizing networks with cograph"
author: "[Mohammed Saqr](https://saqr.me)"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Visualizing networks with cograph}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
has_cograph <- requireNamespace("cograph", quietly = TRUE)
knitr::opts_chunk$set(collapse = TRUE, comment = "#>",
                      fig.width = 7, fig.height = 6, dpi = 100)
library(psychnets)
```

## Drawing a fitted network

A fitted `psychnets` network is a weighted graph of nodes and signed edges. The
picture of that graph is where its structure becomes readable: which nodes
connect, how strongly, and with what sign. Every estimator in `psychnets`
returns an object of class `c("psychnet", "cograph_network")`, so a fitted
network moves into the `cograph` drawing engine with no conversion. The same
model is then rendered with different layouts, node aesthetics, edge styling,
thresholds, themes, and export settings, all from one object.

This vignette shows the plotting workflow from the default graph to a
figure ready for a paper. `cograph::splot()` is the drawing verb
throughout. The chunks that call it are guarded, so they run only when `cograph`
is installed.

```{r check, echo = FALSE, results = "asis", eval = !has_cograph}
cat("> `cograph` is not installed, so the cograph plotting chunks below are skipped.")
```

## The data and the fit

The bundled `SRL_GPT` data hold 300 learners scored on five self-regulated
-learning constructs (CSU, IV, SE, SR, TA). `ebic_glasso()` estimates a
Gaussian graphical model whose edges are partial correlations selected by the
extended Bayesian information criterion.

```{r fit, eval = has_cograph}
fit <- ebic_glasso(SRL_GPT)
fit
```

## The default graph

`plot()` on a `psychnet` object delegates to `cograph::splot()`, so a
bare `plot(fit)` draws the network. Each node is a construct and each edge is a
partial correlation between two constructs given the other three. The colour of
an edge encodes the sign of that partial correlation and the width encodes its
magnitude, so a wide edge of one colour is a strong positive association and a
wide edge of the other colour is a strong negative one.

```{r default, eval = has_cograph}
plot(fit)
```

Calling `cograph::splot()` directly draws the same graph and opens its full
argument surface for the customization below.

```{r splot-default, eval = has_cograph}
cograph::splot(fit)
```

## Layouts

The `layout` argument sets the algorithm that places the nodes. A circular layout
fixes the nodes on a ring, which keeps positions comparable between figures; a
spring layout places connected nodes near one another, which makes clusters
visible. The `seed` argument fixes the random start of a stochastic layout so the
figure is reproducible.

```{r layouts, eval = has_cograph, fig.width = 9, fig.height = 4.5}
op <- par(mfrow = c(1, 2), mar = c(1, 1, 3, 1))
cograph::splot(fit, layout = "circle", title = "circle")
cograph::splot(fit, layout = "spring", seed = 11, title = "spring")
par(op)
```

## Nodes and labels

The node arguments set fill, border, label size, and shape. The
`scale_nodes_by` argument sizes each node by a centrality measure and
`node_size_range` sets the smallest and largest radius, so node area reads as
structural importance. Sizing the nodes by strength makes the most connected
construct the largest on the page.

```{r nodes, eval = has_cograph}
cograph::splot(
  fit,
  layout = "circle",
  scale_nodes_by = "strength",
  node_size_range = c(4, 12),
  node_fill = c("#4C78A8", "#F58518", "#54A24B", "#B279A2", "#E45756"),
  node_border_color = "white",
  node_border_width = 2,
  label_size = 0.9,
  title = "Node size scaled by strength"
)
```

## Edges

For a signed psychometric network, the edge encoding carries the substantive
result. The `edge_positive_color` and `edge_negative_color` arguments set the two
sign colours and `edge_width_range` sets the mapping from magnitude to width. The
`threshold` argument hides edges below an absolute weight, which thins a dense
graph down to its strongest associations, and `edge_labels` prints the weight on
each retained edge.

```{r edges, eval = has_cograph}
cograph::splot(
  fit,
  layout = "circle",
  threshold = 0.05,
  edge_width_range = c(0.5, 5),
  edge_positive_color = "#2A9D8F",
  edge_negative_color = "#E76F51",
  edge_labels = TRUE,
  edge_label_size = 0.7,
  edge_label_bg = "white",
  title = "Thresholded edge weights"
)
```

`cograph::plot_edge_weights()` draws the distribution of the edge
weights on its own, which reads the spread of associations and the location of
the strong edges without the graph layout.

```{r edge-weights, eval = has_cograph, fig.width = 7, fig.height = 4.5}
cograph::plot_edge_weights(fit)
```

## Bootstrap diagnostics

The bootstrap diagnostics are computed in `psychnets` and drawn by their own
base-R plot methods, covered fully in the companion vignette.
`net_boot()` resamples the data and refits the network on each resample, and
`plot()` on its result defaults to the edge-weight confidence intervals.

```{r bootstrap, eval = has_cograph, fig.width = 8, fig.height = 5}
set.seed(1)
bs <- net_boot(SRL_GPT, method = "glasso", n_boot = 250, cores = 1)
plot(bs)
```

The same bootstrap object holds the retained draws for the pairwise
edge-difference test. Under `type = "edge_diff"`, `plot()` draws the
significance-box matrix of which edges differ from one another.

```{r edge-diff-box, eval = has_cograph, fig.width = 6.5, fig.height = 6.5}
plot(bs, type = "edge_diff")
```

`difference_test()` returns the pairwise differences as a tidy table,
and `plot()` under `style = "forest"` draws each difference as a point with its
confidence interval when the effect sizes matter more than the box display.

```{r edge-diff-forest, eval = has_cograph, fig.width = 7, fig.height = 5}
plot(difference_test(bs, type = "edge"), style = "forest")
```

## Themes

The `theme` argument applies a coordinated set of colour and styling defaults
without changing the fitted model, so the same network is redrawn in a house
style or a colourblind-safe palette from one call.

```{r themes, eval = has_cograph, fig.width = 9, fig.height = 4.5}
op <- par(mfrow = c(1, 2), mar = c(1, 1, 3, 1))
cograph::splot(fit, theme = "minimal", title = "minimal")
cograph::splot(fit, theme = "colorblind", title = "colorblind")
par(op)
```

## Group networks

Passing `group =` to `psychnet()` fits one network per level of a grouping
column and returns the collection. `cograph::splot()` draws the
collection as a grid that shares one layout, so a node sits in the same position
in every panel and the panels are read side by side.

```{r group-data, include = FALSE}
grouped_srl <- rbind(
  data.frame(SRL_GPT, source = "GPT", check.names = FALSE),
  data.frame(SRL_Claude, source = "Claude", check.names = FALSE)
)
```

```{r groups, eval = has_cograph, fig.width = 8, fig.height = 4.5}
group_fit <- psychnet(grouped_srl, group = "source", method = "glasso")
cograph::splot(group_fit, layout = "circle", psych_styling = TRUE)
```

## Export

The `filename`, `width`, `height`, and `res` arguments write the figure to a file
at a chosen size and resolution when it is ready to save.

```{r export, eval = FALSE}
cograph::splot(fit, layout = "circle", filename = "network.png",
               width = 8, height = 8, res = 300)
```

## The splot arguments at a glance

| Task | Arguments |
|---|---|
| Layout | `layout`, `seed`, `layout_scale`, `layout_margin` |
| Nodes | `node_size`, `scale_nodes_by`, `node_size_range`, `node_fill`, `node_shape` |
| Labels | `labels`, `label_size`, `label_color`, `label_position` |
| Edges | `edge_width_range`, `edge_positive_color`, `edge_negative_color`, `edge_alpha` |
| Filtering | `threshold`, `minimum`, `maximum` |
| Edge labels | `edge_labels`, `edge_label_size`, `edge_label_bg`, `edge_label_style` |
| Themes | `theme`, `background`, `title` |
| Export | `filename`, `width`, `height`, `res` |
