Network Science: From Theory to Biological Applications

BC204 Hands-on Workshop · Spring 2026

Author

Savvas Paragkamian

Published

May 22, 2026

Code
required <- c("igraph", "poweRlaw", "ggplot2", "dplyr", "tidyr", "tibble", "purrr")
missing  <- required[!required %in% installed.packages()[, "Package"]]
if (length(missing)) install.packages(missing, repos = "https://cloud.r-project.org")

library(igraph)
library(poweRlaw)
library(ggplot2)
library(dplyr)
library(tidyr)
library(tibble)
library(purrr)
library(knitr)

theme_net <- function() {
  theme_bw() +
    theme(panel.grid.minor = element_blank(),
          strip.background = element_blank())
}

Time Part Topic
0:00 – 0:20 Part 0 Introduction, Data structures, network types, complexity theory
0:20 – 0:55 Part 1 Graph models (ER, BA, SW)
0:55 – 1:35 Part 2 C. elegans connectome, centralities, power-law
1:35 – 2:20 Break (45 min)
2:20 – 2:50 Part 3 Community detection (Leiden), assortativity
2:50 – 3:30 Part 4 E. coli PPI, robustness, network inference
3:30 – 4:00 Part 5 Networks as computation, GNNs

1 Part 0 · Data Structures and Network Types [~20 min]

Goal: Build the vocabulary and the R objects we will use for the rest of the day. Every network analysis starts here.

Also each one kindly share:

  • Name
  • Expertise background
  • Favourite bioinformatics tool
  • AI toolkit

1.1 Complicated ≠ Complex

These words are used interchangeably but they are not the same, and the distinction determines what kind of model is appropriate.

A complicated system has many parts, but the parts compose trivially. A mechanical watch has hundreds of cogs and springs, each with a specific job. Take it apart, document every piece, and you have a complete description. Behaviour is the sum of parts. Complicated systems need detailed blueprints.

A complex system has parts that interact, with feedbacks and nonlinearities and emergent properties. The interesting behaviour lives in the couplings, not the components. A three-species food web can show oscillations and regime shifts that no single species possesses. Complex systems often admit strikingly simple mathematical descriptions — because the richness is generated by a few interaction rules, not by idiosyncratic detail (Strogatz 2001; Kauffman 1993).

Start with the simplest possible population model. Malthusian growth assumes a population grows proportionally to its current size — no resource limits, no interactions:

\[N_{t+1} = r \cdot N_t \qquad \Longrightarrow \qquad N_t = N_0 \cdot r^t\]

Code
malthus <- function(r, n0 = 1, steps = 20) {
  tibble(t = 0:steps, N = n0 * r^(0:steps), r = as.character(r))
}

map_dfr(c(0.8, 1.0, 1.2), malthus) |>
  ggplot(aes(x = t, y = N, colour = r)) +
  geom_line(linewidth = 1) +
  scale_colour_manual(values = c("0.8" = "steelblue", "1" = "grey50", "1.2" = "coral2"),
                      labels = c("r = 0.8  (decline)", "r = 1.0  (steady)", "r = 1.2  (growth)")) +
  labs(x = "Time step", y = "Population N", colour = NULL,
       title = "Malthusian (exponential) growth") +
  theme_net()

Malthusian dynamics: three growth rates, exponential trajectories. No interactions — the system is complicated only if N₀ is large, but never complex.

Malthusian dynamics is not complex — it is entirely predictable. The behaviour of the whole is the behaviour of one individual, scaled. No emergence is possible.

Adding a single interaction term — density dependence, the idea that crowding limits growth — transforms the model entirely:

\[x_{t+1} = r \cdot x_t (1 - x_t)\]

The \((1 - x_t)\) term makes each individual interact with the rest of the population. One interaction rule. One parameter. This model was proposed by ecologist Pierre Verhulst who introduced the carrying capacity in the growth model.

Code
logistic_attractor <- function(r, n_burn = 500, n_keep = 300, x0 = 0.5) {
  x <- x0
  for (i in seq_len(n_burn))  x <- r * x * (1 - x)
  xs <- numeric(n_keep)
  for (i in seq_len(n_keep)) { x <- r * x * (1 - x); xs[i] <- x }
  xs
}

r_vals <- seq(2.5, 4.0, length.out = 800)

map_dfr(r_vals, function(r) tibble(r = r, x = logistic_attractor(r))) |>
  ggplot(aes(x = r, y = x)) +
  geom_point(size = 0.05, alpha = 0.3, colour = "steelblue") +
  labs(x = "Growth rate r", y = "Long-run population x",
       title = "Logistic map bifurcation diagram") +
  theme_net()

Logistic map bifurcation diagram: stable fixed point → period doubling → chaos. The only change from the Malthusian model is the (1 − x) term.

Beyond \(r \approx 3.57\) the orbit never settles. The system is deterministic — the same \(r\) and \(x_0\) always give the same trajectory — yet unpredictable: two trajectories starting at almost the same \(x_0\) rapidly diverge.

Code
logistic_ts <- function(r, x0, steps = 60) {
  x <- numeric(steps + 1); x[1] <- x0
  for (i in seq_len(steps)) x[i + 1] <- r * x[i] * (1 - x[i])
  tibble(t = 0:steps, x = x)
}

r_chaos <- 3.9
bind_rows(
  logistic_ts(r_chaos, x0 = 0.5000) |> mutate(start = "x₀ = 0.5000"),
  logistic_ts(r_chaos, x0 = 0.5001) |> mutate(start = "x₀ = 0.5001")
) |>
  ggplot(aes(x = t, y = x, colour = start)) +
  geom_line(linewidth = 0.8) +
  scale_colour_manual(values = c("steelblue", "coral2")) +
  labs(x = "Time step", y = "Population x", colour = "Initial condition",
       title  = "Deterministic chaos: two nearly identical initial conditions",
       subtitle = paste0("r = ", r_chaos, "  |  Δx₀ = 0.0001")) +
  theme_net()

Sensitivity to initial conditions at r = 3.9: two trajectories differing by 0.0001 are indistinguishable for ~15 steps, then diverge completely. This is the hallmark of deterministic chaos.

Beyond ~3.57 the orbit never settles — deterministic chaos from a deterministic rule. This is emergence: qualitatively new behaviour that is not present in any single part.

1.2 Reaction-diffusion and spatial pattern formation

The logistic map showed how a single interaction rule generates temporal complexity. Turing’s 1952 paper The Chemical Basis of Morphogenesis (Turing 1952) showed the same principle operating in space: two interacting morphogens with different diffusion rates can spontaneously break a spatially uniform state into a periodic pattern — stripes, spots, labyrinths — from nothing but random noise.

The mechanism is short-range activation, long-range inhibition. The activator promotes itself but also produces an inhibitor. The inhibitor diffuses faster, suppressing the activator at distance. The result is a spatial competition whose outcome depends entirely on the ratio of diffusion rates, not on any pre-existing spatial information.

This is modelled by a pair of coupled PDEs. The Gray-Scott formulation (Pearson 1993) uses two chemical species \(u\) (substrate, consumed) and \(v\) (catalyst, self-replicating):

\[\frac{\partial u}{\partial t} = D_u \nabla^2 u - uv^2 + F(1-u)\]

\[\frac{\partial v}{\partial t} = D_v \nabla^2 v + uv^2 - (F+k)v\]

Code
set.seed(42)
n  <- 80
Du <- 0.16; Dv <- 0.08; F_rate <- 0.035; k <- 0.060

u <- matrix(1, n, n)
v <- matrix(0, n, n)

# Seed a central patch with inhibitor + noise
r <- 8L
cx <- (n %/% 2 - r):(n %/% 2 + r)
u[cx, cx] <- 0.50 + matrix(runif((2*r+1)^2, -0.05, 0.05), 2*r+1)
v[cx, cx] <- 0.25 + matrix(runif((2*r+1)^2, -0.05, 0.05), 2*r+1)

# Discrete Laplacian with periodic boundary conditions
lap <- function(m) {
  rbind(m[-1L, ], m[1L, ]) +   # up
  rbind(m[n,   ], m[-n,  ]) +   # down
  cbind(m[, -1L], m[, 1L]) +   # left
  cbind(m[,  n ], m[, -n ]) -   # right
  4 * m
}

dt <- 1.0
for (i in seq_len(5000L)) {
  uvv <- u * v * v
  u   <- u + dt * (Du * lap(u) - uvv + F_rate * (1 - u))
  v   <- v + dt * (Dv * lap(v) + uvv - (F_rate + k) * v)
}

expand.grid(x = seq_len(n), y = seq_len(n)) |>
  mutate(u = as.vector(u)) |>
  ggplot(aes(x = x, y = y, fill = u)) +
  geom_tile() +
  scale_fill_distiller(palette = "RdYlBu", name = "u") +
  coord_fixed() +
  labs(title  = "Turing pattern — Gray-Scott reaction-diffusion",
       subtitle = paste0("Du=", Du, "  Dv=", Dv,
                         "  F=", F_rate, "  k=", k)) +
  theme_void() +
  theme(legend.position = "right")

Gray-Scott reaction-diffusion on a 80×80 grid after 5000 steps. Spots emerge from a uniform initial state perturbed only by noise — spatial complexity from two interaction rules.

The pattern is not encoded anywhere in the initial conditions or the parameters individually. It is an emergent property of the interaction structure — the coupling between the two equations. Change the ratio \(D_v/D_u\) and the pattern shifts from spots to stripes to labyrinths. Change it enough and the pattern disappears entirely. This is the same logic as the logistic map, now distributed across space.

A cell is both complicated and complex. Billions of molecular machines, each with specific evolved structure (complicated) interacting through feedback loops and signalling cascades that produce homeostasis, differentiation, and cell fate decisions (complex). This is why whole-cell modelling is hard in a way that aircraft engineering and magnetism are not: we cannot get away with just the blueprint, and we cannot get away with just the coarse-grained dynamics.

System Complicated Complex Implication
Mechanical watch Detailed blueprint is the right model
Logistic map / Ising model Simple equation captures everything
Epidemic, food web A few interaction rules suffice
Cell, brain, ecosystem Neither alone works — genuinely hard

Networks are the natural language of complex systems. They make interactions explicit: nodes are the parts, edges are the couplings. The same formalism applies to neurons, proteins, species, and — as we will see in Part 5 — artificial intelligence.

1.3 The cell is not a bag of genes

JBS Haldane described the cell as a “bag of genes”. We know differently now. The first things we encounter inside a cell are not genes but molecular machines — elaborate, evolved structures that do specific physical work: the ribosome translates mRNA, the spliceosome edits it, myosin pulls on actin, the nuclear pore complex controls everything entering or leaving the nucleus (Hartwell et al. 1999; Alon 2003).

Some machines are well-modelled. Ribosome queueing along mRNA has been studied since the 1960s using the TASEP (Totally Asymmetric Simple Exclusion Process) — a particle-exclusion model on a graph — borrowing from statistical physics. Myosin has its own lineage stretching back to Huxley’s 1957 cross-bridge theory.

Many others are strikingly under-modelled despite spectacular cryo-EM structures:

Code
tribble(
  ~Machine,               ~Subunits, ~`Known structure`, ~`Mathematical models (approx.)`, ~`Key function`,
  "Ribosome",             "~80",     "Yes (Å resolution)", "Thousands (TASEP, stochastic)",  "mRNA translation",
  "Myosin II",            "~6",      "Yes",                "Hundreds (cross-bridge theory)",  "Muscle contraction",
  "Spliceosome",          ">100",    "Yes (cryo-EM)",      "~Tens",                           "pre-mRNA splicing",
  "Chaperonin (GroEL)",   "~14",     "Yes",                "~Tens",                           "protein folding",
  "V-ATPase",             "~30",     "Yes",                "~Tens",                           "proton pumping",
  "Nuclear pore complex", "~120",    "Yes (cryo-ET)",      "Very few",                        "nuclear transport"
) |> kable(caption = "Molecular machines: structural knowledge vs mathematical models (after Stumpf)")
Molecular machines: structural knowledge vs mathematical models (after Stumpf)
Machine Subunits Known structure Mathematical models (approx.) Key function
Ribosome ~80 Yes (Å resolution) Thousands (TASEP, stochastic) mRNA translation
Myosin II ~6 Yes Hundreds (cross-bridge theory) Muscle contraction
Spliceosome >100 Yes (cryo-EM) ~Tens pre-mRNA splicing
Chaperonin (GroEL) ~14 Yes ~Tens protein folding
V-ATPase ~30 Yes ~Tens proton pumping
Nuclear pore complex ~120 Yes (cryo-ET) Very few nuclear transport

The challenge is not obtaining structures — cryo-EM has transformed that. The hard step is translating structure → dynamics → predictive mathematical model. Fewer than 1% of published models ever reach shared repositories like BioModels.

Q: The spliceosome has over 100 subunits and near-atomic resolution structures — yet almost no mechanistic models. Is this a complicated problem, a complex one, or both? What would a network model of the spliceosome represent?

1.4 The human is not determined by gene count

When the human genome was sequenced, the expectation was ~100,000 genes — one for each protein, each trait, each behaviour. The actual number was ~20,000–25,000. About the same as a nematode worm.

Code
tribble(
  ~Organism,              ~`Common name`,       ~`Genome size (Mb)`, ~`Protein-coding genes`,
  "Homo sapiens",         "Human",              3200,                 ~20500,
  "Mus musculus",         "Mouse",              2800,                 ~20000,
  "Drosophila melanogaster","Fruit fly",          180,                 ~14000,
  "Caenorhabditis elegans","Nematode worm",       100,                 ~20000,
  "Arabidopsis thaliana", "Thale cress",          135,                 ~27000,
  "Oryza sativa",         "Rice",                430,                 ~38000,
  "Paris japonica",       "Canopy plant",       149000,                ~30000
) |>
  arrange(desc(`Protein-coding genes`)) |>
  kable(caption = "Gene count does not track organismal complexity (G-value paradox)")
Gene count does not track organismal complexity (G-value paradox)
Organism Common name Genome size (Mb) Protein-coding genes
Paris japonica Canopy plant 149000 ~30000
Oryza sativa Rice 430 ~38000
Arabidopsis thaliana Thale cress 135 ~27000
Drosophila melanogaster Fruit fly 180 ~14000
Mus musculus Mouse 2800 ~20000
Caenorhabditis elegans Nematode worm 100 ~20000
Homo sapiens Human 3200 ~20500

Gene count does not track organismal complexity. A rice plant has more protein-coding genes than a human. The complexity of an organism lives in its regulatory interactions, not its parts list.

The resolution is not in the genes but in the interactions. Alternative splicing, post-translational modification, and regulatory networks mean that 20,000 genes can generate far more distinct states than a parts list suggests. A network of \(N\) nodes with binary states has \(2^N\) possible configurations. The cell navigates this state space through its interaction structure — not through gene number.

1.5 Ecosystem function cannot be predicted from species count

The same logic extends to ecology. The number of species in a community — biodiversity — does not determine ecosystem function. A species that appears ecologically redundant under current conditions may be the only one capable of maintaining function under novel conditions (drought, invasion, temperature shift). Remove it and the system can shift abruptly to a different state.

The diversity–stability debate has run since MacArthur (1955) and May (1972). May showed analytically that random complexity destabilises: a randomly wired ecological network with \(S\) species and connectivity \(C\) is stable only if \(SC\sigma^2 < 1\) (where \(\sigma^2\) is interaction-strength variance). More species and more connections, holding strength constant, makes the system less stable. This is the opposite of the intuitive view.

The resolution is that real ecological networks are not random. They have structure: weak interactions dominate, strong interactions are rare and embedded in stabilising motifs, interaction topology shapes stability more than species richness alone (McCann 2000; Bascompte 2009; Pascual and Dunne 2007; Montoya et al. 2006).

1.6 More is Different

In 1972, Philip W. Anderson published a one-page paper in Science with the title More is Different (Anderson 1972). It is one of the founding texts of complexity science. The same principle runs through Kauffman’s work on self-organisation in evolution (Kauffman 1993).

The argument is simple. The reductionist programme — understand the parts, and you understand the whole — works within a level of organisation. Particle physics is not wrong about quarks. Chemistry is not wrong about molecules. But knowing the laws of quarks does not give you the laws of chemistry. Knowing the laws of chemistry does not give you the laws of cell biology. At each level, new properties appear that are not present at the level below and cannot be derived from it by straightforward extrapolation.

Anderson called this broken symmetry: macroscopic systems do not share the symmetries of their microscopic constituents. A crystal has a preferred orientation; the underlying quantum mechanics does not. A flock has a collective direction; individual birds do not. A gene regulatory network has bistability; individual protein interactions do not. Prigogine extended this idea into thermodynamics: far-from-equilibrium systems spontaneously break time symmetry, generating irreversible order — dissipative structures — from fluctuations (Prigogine and Stengers 1984; Antoniou and Prigogine 1993).

The practical consequence: you cannot compress a complex system by reducing it. The emergent properties are not encoded in the parts — they are encoded in the relational structure between parts. This is what networks represent.

1.7 Reductionism, holism, and where network science fits

Science has operated mostly under a reductionist assumption: find the smallest parts, characterise them precisely, and reconstruct the whole. This works exceptionally well for some problems — the periodic table, protein structure, gene function. It fails for others — why a particular ecosystem collapses, how a cell decides to differentiate, whether a drug will have off-target effects in a living network of 20,000 interacting genes.

Holism — the whole has properties not present in the parts — is often dismissed as unscientific because it seems to resist quantification. The contribution of complex systems science, and network science specifically, is to make holism precise and computable:

Approach Core assumption Right tool Fails when
Reductionism Parts determine whole Structural biology, genetics Emergent behaviour dominates
Holism (naive) Whole transcends parts No predictive mechanism
Network science Interactions determine behaviour Graph theory, dynamics Interactions unknown or unmeasurable

Network science is not anti-reductionist. It is a framework for moving between levels: the nodes are the output of reductionist analysis (here is the protein, here is the neuron), and the edges encode the interactions that produce emergent behaviour at the system level (Boccaletti et al. 2006; Newman 2010; Sole et al. 2002; Solé and Valverde 2004). The cell is not a bag of genes. It is a network.

The formalism used in this workshop treats networks as static. Real biological networks are not: interactions are context-dependent, time-varying, and often layered across multiple types simultaneously. These extensions are formalised in temporal networks (Holme and Saramaki 2012) and multiplex networks (Cozzo et al. 2017).

Networks are the natural language of complex systems. They make interactions explicit: nodes are the parts, edges are the couplings. The same formalism applies to neurons, proteins, species, and — as we will see in Part 5 — artificial intelligence.

1.8 What is a graph?

A network is modelled mathematically as a graph \(G(V, E)\): a set of \(N\) nodes \(V\) and a set of edges \(E \subseteq V \times V\) (Crane 2018; Wasserman and Faust 1994).

Four assumptions underpin this abstraction:

  1. All nodes belong to the same variable type (e.g., all proteins, or all neurons).
  2. All edges belong to the same variable type (e.g., all physical interactions).
  3. Each edge connects exactly two nodes — a binary relation.
  4. Both nodes and edges are time-independent (a static snapshot).

These assumptions are often violated in real biology. Knowing when they break tells you when to upgrade to a richer model (temporal, multilayer, hypergraph).

1.9 Data structures for networks

A network can be stored in three equivalent ways:

Structure Best for
Adjacency matrix Dense networks, linear algebra, visualisation as a heatmap
Edge list Sparse networks, tidy data pipelines
Incidence matrix Bipartite networks (two node types)

1.9.1 Adjacency matrix

Rows and columns are nodes; a cell is 1 if an edge exists (0 otherwise). For a directed graph the matrix is asymmetric — entry \((i, j) = 1\) means an edge from \(i\) to \(j\). For an undirected graph it is symmetric.

Code
set.seed(7)
n_nodes <- 5
node_names <- LETTERS[1:n_nodes]

adj <- matrix(
  sample(c(0L, 1L), n_nodes^2, replace = TRUE, prob = c(0.5, 0.5)),
  nrow = n_nodes, ncol = n_nodes,
  dimnames = list(node_names, node_names)
)
diag(adj) <- 0L  # no self-loops

kable(adj, caption = "Adjacency matrix — rows are sources, columns are targets")
Adjacency matrix — rows are sources, columns are targets
A B C D E
A 0 0 1 1 0
B 1 0 1 0 1
C 1 0 0 1 0
D 1 1 1 0 0
E 1 1 1 1 0

Convert to an igraph object and plot:

Code
g_adj <- graph_from_adjacency_matrix(adj, mode = "directed")
plot(g_adj, vertex.color = "steelblue", vertex.label.color = "white",
     edge.arrow.size = 0.6, main = "From adjacency matrix")

Graph built from the adjacency matrix

Visualise the same matrix as a heatmap — useful for spotting asymmetry (directed) or symmetry (undirected):

Code
as.data.frame(adj) |>
  rownames_to_column("from") |>
  pivot_longer(-from, names_to = "to", values_to = "edge") |>
  mutate(from = factor(from, levels = rev(sort(unique(from))))) |>
  ggplot(aes(x = to, y = from, fill = factor(edge))) +
  geom_tile(colour = "white") +
  scale_fill_manual(values = c("0" = "grey92", "1" = "steelblue"),
                    labels = c("absent", "present")) +
  scale_x_discrete(position = "top") +
  coord_fixed() +
  labs(x = "To", y = "From", fill = "Edge", title = "Adjacency matrix heatmap") +
  theme_net()

Adjacency matrix as a heatmap — asymmetry signals a directed network

1.9.2 Edge list

An edge list stores only the existing edges — one row per edge. It is more memory-efficient than a matrix for sparse networks (most real biological networks have density < 1 %).

Code
el <- as.data.frame(which(adj == 1, arr.ind = TRUE)) |>
  transmute(from = node_names[row], to = node_names[col])

kable(el, caption = "Edge list derived from the same network")
Edge list derived from the same network
from to
B B A
C C A
D D A
E E A
D.1 D B
E.1 E B
A A C
B.1 B C
D.2 D C
E.2 E C
A.1 A D
C.1 C D
E.3 E D
B.2 B E

Build a graph directly from the edge list:

Code
g_el <- graph_from_edgelist(as.matrix(el), directed = TRUE)
plot(g_el, vertex.color = "coral2", vertex.label.color = "white",
     edge.arrow.size = 0.6, main = "From edge list")

The same graph built from the edge list — identical result

1.9.3 Weighted edges

Edges can carry a numeric weight (interaction strength, distance, correlation). Weights extend binary graphs to continuous ones.

Code
el_w <- el |> mutate(weight = round(runif(n(), 0.1, 1.0), 2))

kable(el_w, caption = "Weighted edge list")
Weighted edge list
from to weight
B B A 0.52
C C A 0.50
D D A 0.61
E E A 0.86
D.1 D B 0.51
E.1 E B 0.16
A A C 0.49
B.1 B C 0.43
D.2 D C 0.21
E.2 E C 0.17
A.1 A D 0.16
C.1 C D 0.90
E.3 E D 0.34
B.2 B E 0.51

1.10 Network types

Real networks differ in four properties you must check before any analysis:

Property Values Example
Direction directed / undirected synapses (directed); co-expression (undirected)
Weight weighted / binary interaction strength; presence/absence
Sign signed / unsigned activation (+) / inhibition (−) in gene regulation
Simplicity simple / multigraph single edge per pair; multiple experimental evidence
Code
g_w <- graph_from_data_frame(el_w, directed = TRUE)
E(g_w)$weight <- el_w$weight

tibble(
  Property  = c("Directed", "Weighted", "Simple", "Has loops"),
  Value     = c(is_directed(g_w), is_weighted(g_w),
                is_simple(g_w),   any(which_loop(g_w)))
) |> kable(caption = "Network type checklist for our toy graph")
Network type checklist for our toy graph
Property Value
Directed TRUE
Weighted TRUE
Simple TRUE
Has loops FALSE

1.11 R objects for network data

Three representations are used across this workshop:

Object Package When to use
igraph igraph All network analysis — fast C backend
matrix / tibble base / tidyverse Raw import, pre-processing, and statistics

For a practical guide to network analysis in R see Douglas (2015); for the statistical foundations see Kolaczyk (2009).

Code
# igraph object — the primary representation used throughout this workshop
g_ig <- graph_from_data_frame(el_w, directed = TRUE)

g_ig
IGRAPH 5606457 DNW- 5 14 -- 
+ attr: name (v/c), weight (e/n)
+ edges from 5606457 (vertex names):
 [1] B->A C->A D->A E->A D->B E->B A->C B->C D->C E->C A->D C->D E->D B->E

Exercise 0: Make the toy graph undirected (as.undirected(g_ig)). Check again: is the adjacency matrix now symmetric? Does the edge count change, and why?


2 Part 1 · Graph Models [~35 min]

Goal: Understand the three canonical random graph models as null hypotheses for real biological networks.

2.1 Erdős–Rényi: the null model

The ER model places edges between N nodes with probability p, independently. It predicts a Poisson degree distribution — no hubs. Real biological networks almost never look like this, which is exactly why it is useful as a null (Newman 2010; Bollobas 2001).

Code
set.seed(42)
n       <- 500
probs   <- seq(0, 0.012, length.out = 80)

er_stats <- map_dfr(probs, function(p) {
  g     <- sample_gnp(n, p, directed = FALSE)
  comps <- components(g)
  tibble(mean_k     = mean(degree(g)),
         giant_frac = max(comps$csize) / n)
})

ggplot(er_stats, aes(x = mean_k, y = giant_frac)) +
  geom_point(colour = "steelblue", size = 1.5) +
  geom_vline(xintercept = 1, linetype = "dashed", colour = "firebrick") +
  labs(x = "Mean degree ⟨k⟩", y = "Giant component / N",
       title = "ER percolation threshold",
       caption = "Dashed line: ⟨k⟩ = 1 — the point where a giant component emerges") +
  theme_net()

Phase transition in ER networks: a giant component suddenly appears at ⟨k⟩ = 1

Q: A pathogen progressively disables random host proteins. Which side of this threshold matters for the host? For the pathogen’s spread through a PPI network?

2.2 Barabási–Albert: preferential attachment

Each new node attaches with probability proportional to existing degree — “rich get richer”. This produces a power-law degree distribution and explains why hubs appear in PPIs, metabolic networks, and co-authorship graphs (Barabasi and Albert 1999; Barabási et al. 1999; Albert and Barabasi 2002; Dorogovtsev and Mendes 2003).

Code
set.seed(42)
ba <- sample_pa(2000, power = 1, directed = FALSE)

tibble(degree = degree(ba)) |>
  count(degree) |>
  ggplot(aes(x = degree, y = n)) +
  geom_point(colour = "coral2") +
  scale_x_log10() + scale_y_log10() +
  labs(x = "Degree (log)", y = "Count (log)",
       title = "BA model degree distribution") +
  theme_net()

BA model: straight line on log-log axes is the signature of a power law

2.3 Watts–Strogatz: small-world networks

A regular lattice rewired with probability β. At intermediate β: short average path lengths (like a random graph) AND high clustering (like a lattice) — simultaneously. This is the small-world regime (Watts and Strogatz 1998; Strogatz 2001).

Code
set.seed(42)
betas   <- 10^seq(-3, 0, length.out = 40)
lattice <- make_lattice(length = 200, dim = 1, nei = 4, circular = TRUE)
C0      <- transitivity(lattice, type = "average")
L0      <- mean_distance(lattice, directed = FALSE)

sw_stats <- map_dfr(betas, function(b) {
  g <- sample_smallworld(1, 200, 4, b)
  tibble(beta   = b,
         C_norm = transitivity(g, type = "average") / C0,
         L_norm = mean_distance(g, directed = FALSE)  / L0)
})

sw_stats |>
  pivot_longer(c(C_norm, L_norm), names_to = "metric", values_to = "value") |>
  ggplot(aes(x = beta, y = value, colour = metric)) +
  geom_line(linewidth = 1) +
  scale_x_log10() +
  scale_colour_manual(values   = c(C_norm = "steelblue", L_norm = "coral2"),
                      labels   = c("C / C₀  (clustering)", "L / L₀  (path length)")) +
  labs(x = "Rewiring probability β (log)", y = "Normalised value",
       colour = NULL, title = "Watts–Strogatz small-world regime") +
  theme_net()

Small-world window: C stays high while L drops, between β ≈ 0.01 and β ≈ 0.1

Exercise 1: Generate ER and BA networks each with N = 300 and approximately the same number of edges as a Watts–Strogatz graph with N = 300, K = 4, β = 0.05. Compare clustering coefficient and mean path length across the three. Which model is “most small-world”?


3 Part 2 · C. elegans Connectome [~40 min]

The frontal neuronal network of C. elegans: 302 neurons (nodes), chemical synapses and gap junctions (directed edges). Fully mapped from electron microscopy — one of the few complete connectomes in biology (Kaiser and Hilgetag 2006; Varshney et al. 2011).

3.1 Import and network checklist

Every network analysis should start with this checklist before computing anything.

Code
edges <- read.table("../Data/C-elegans-frontal.txt", header = TRUE, sep = " ") |>
  mutate(across(c(FromNodeId, ToNodeId), as.character))

meta  <- read.csv("../Data/C-elegans-frontal-meta.csv") |>
  rename(x = posx, y = posy) |>
  mutate(node_id = as.character(node_id))

net <- graph_from_data_frame(edges, directed = TRUE, vertices = meta)

tibble(
  property = c("Nodes", "Edges", "Directed", "Simple",
               "Weighted", "Connected", "Components"),
  value    = c(vcount(net), ecount(net), is_directed(net),
               is_simple(net), is_weighted(net), is_connected(net),
               components(net)$no)
) |> kable(caption = "C. elegans connectome — network checklist")
C. elegans connectome — network checklist
property value
Nodes 131
Edges 764
Directed 1
Simple 1
Weighted 0
Connected 1
Components 1
Code
# Work with the largest weakly connected component
giant <- decompose(net, min.vertices = 10)[[1]]
cat("Giant component:", vcount(giant), "nodes |", ecount(giant), "edges\n")
Giant component: 131 nodes | 764 edges

3.2 Spatial visualisation

Neuron positions are anatomical (inferred from EM data), so this layout is biologically meaningful — not a force-directed aesthetic choice.

Code
layout_anat <- cbind(V(giant)$x, V(giant)$y)

plot(giant,
     layout          = layout_anat,
     vertex.size     = 3,
     vertex.color    = "steelblue",
     vertex.label    = NA,
     edge.arrow.size = 0.2,
     edge.color      = adjustcolor("grey50", alpha.f = 0.25),
     main = "C. elegans frontal connectome — anatomical layout")

C. elegans frontal connectome in anatomical space

3.3 Degree distribution and power-law test

Code
deg_all <- degree(giant, mode = "total")
deg_pos <- deg_all[deg_all > 0]

# Maximum-likelihood fit following Clauset, Shalizi & Newman [@Clauset2009]; implementation via poweRlaw [@Gillespie2014]
pl      <- displ$new(deg_pos)
pl$setXmin(estimate_xmin(pl))
pl$setPars(estimate_pars(pl))

cat(sprintf("Power-law: α = %.3f  |  xmin = %d  |  tail n = %d\n",
            pl$pars, pl$xmin, sum(deg_pos >= pl$xmin)))
Power-law: α = 6.147  |  xmin = 18  |  tail n = 26
Code
# Empirical complementary CDF
ccdf_df <- tibble(k = sort(unique(deg_pos))) |>
  mutate(p = map_dbl(k, \(x) mean(deg_pos >= x)))

# Theoretical power-law CCDF
k_range     <- seq(pl$xmin, max(deg_pos))
tail_frac   <- mean(deg_pos >= pl$xmin)
pl_ccdf_df  <- tibble(
  k = k_range,
  p = tail_frac * (k_range / pl$xmin) ^ (-(pl$pars - 1))
)

ggplot(ccdf_df, aes(x = k, y = p)) +
  geom_point(colour = "steelblue", size = 1.5) +
  geom_line(data = pl_ccdf_df, aes(x = k, y = p),
            colour = "firebrick", linewidth = 1) +
  scale_x_log10() + scale_y_log10() +
  labs(x = "Degree k (log)", y = "P(K ≥ k)  [log]",
       title = "C. elegans degree distribution",
       subtitle = sprintf("Power-law fit: α = %.2f, xmin = %d", pl$pars, pl$xmin)) +
  theme_net()

Degree distribution: empirical (blue) vs power-law fit (red line from xmin)

3.4 Centralities

Code
node_tbl <- igraph::as_data_frame(giant, what = "vertices") |>
  mutate(
    degree      = degree(giant, mode = "total"),
    degree_in   = degree(giant, mode = "in"),
    degree_out  = degree(giant, mode = "out"),
    betweenness = betweenness(giant, directed = TRUE),
    closeness   = closeness(giant, mode = "all"),
    pagerank    = page_rank(giant)$vector
  )

Degree measures local connectivity. Betweenness measures how often a neuron lies on the shortest path between other pairs — a bottleneck (Freeman 1979; Freeman et al. 1991). PageRank propagates influence through the directed structure. For a systematic treatment of centrality axioms see Boldi and Vigna (2014); for an overview of applications see Lü et al. (2016). In neural networks specifically, centrality measures have been used to identify functionally critical regions (Bullmore et al. 2009; Rubinov and Sporns 2010).

Code
ggplot(node_tbl, aes(x = degree, y = betweenness)) +
  geom_point(colour = "steelblue", alpha = 0.7) +
  geom_smooth(method = "lm", se = FALSE, colour = "firebrick", linewidth = 0.8) +
  labs(x = "Degree", y = "Betweenness centrality",
       title = "Hubs vs bottlenecks in C. elegans") +
  theme_net()

Nodes above the trend are bottlenecks: high betweenness despite moderate degree
Code
node_tbl |>
  arrange(desc(betweenness)) |>
  select(name, degree, betweenness, closeness, pagerank) |>
  head(10) |>
  kable(digits = 3,
        caption = "Top-10 neurons by betweenness centrality",
        col.names = c("Neuron", "Degree", "Betweenness", "Closeness", "PageRank"))
Top-10 neurons by betweenness centrality
Neuron Degree Betweenness Closeness PageRank
RIH RIH 33 2910.497 0.004 0.057
ADLL ADLL 18 2657.330 0.003 0.026
AVAL AVAL 24 2621.502 0.004 0.013
RIAR RIAR 38 2602.884 0.004 0.014
AIZL AIZL 18 1317.127 0.004 0.022
CEPVR CEPVR 17 1141.717 0.003 0.017
SAADR SAADR 11 1004.858 0.003 0.013
AIAL AIAL 14 982.800 0.003 0.010
RIBR RIBR 15 967.890 0.003 0.007
OLLR OLLR 22 920.125 0.004 0.020

The power-law fit should be interpreted with caution. Stumpf and Porter (2012) and Broido and Clauset (2018) show that strict scale-free degree distributions are rare in empirical networks, and that random subsamples of scale-free networks are not themselves scale-free (Stumpf et al. 2005). The statistical test provided by Clauset et al. (2009) and implemented in poweRlaw (Gillespie 2014) is necessary but not sufficient. Newman (2005) provides a broader treatment of heavy-tailed distributions.

Exercise 2: Identify neurons in the top-10 by betweenness that are not in the top-10 by degree. These are the biological bottlenecks. Look up one of them — what is its known function in C. elegans neurobiology?


Note☕ Break — 45 minutes

4 Part 3 · Community Structure [~30 min]

Neurons do not operate in isolation — they form functional modules (Hartwell et al. 1999; Koch 2012). Community detection asks: which sets of neurons are more densely interconnected with each other than with the rest? For a review of algorithms and quality metrics see Newman (2006), Newman and Peixoto (2015), and Emmons et al. (2016). Detected communities should not be over-interpreted: Ingram et al. (2006) showed that network structure alone does not determine function.

4.1 Leiden algorithm

Leiden (Traag et al. 2019) solves a resolution-limit problem in Louvain: it guarantees that all communities are internally well-connected and produces more stable partitions. It is the current standard for biological network analysis.

Code
# Leiden requires an undirected simple graph
g_ud      <- as.undirected(giant) |> simplify()
comm_leid <- cluster_leiden(g_ud, resolution_parameter = 0.5)
V(g_ud)$community <- as.character(membership(comm_leid))

n_comm <- max(membership(comm_leid))
cat("Communities detected (resolution = 0.5):", n_comm, "\n")
Communities detected (resolution = 0.5): 48 
Code
comm_pal       <- hcl.colors(n_comm, palette = "Dark 2")
layout_anat_ud <- cbind(V(g_ud)$x, V(g_ud)$y)

plot(g_ud,
     layout       = layout_anat_ud,
     vertex.size  = 4,
     vertex.color = comm_pal[as.integer(V(g_ud)$community)],
     vertex.label = NA,
     edge.color   = adjustcolor("grey70", alpha.f = 0.12),
     main = "C. elegans communities (Leiden, resolution = 0.5)")

Community membership mapped onto anatomical positions — do communities cluster spatially?
Code
set.seed(42)
plot(g_ud,
     layout       = layout_with_kk(g_ud),
     vertex.size  = 4,
     vertex.color = comm_pal[as.integer(V(g_ud)$community)],
     vertex.label = NA,
     edge.color   = adjustcolor("grey70", alpha.f = 0.12),
     main = "C. elegans communities (Kamada-Kawai layout)")

Force-directed layout reveals intra-community density regardless of anatomy

4.2 Resolution sensitivity

The resolution parameter controls granularity: higher values split communities further. There is no universally correct value — choose based on the biological question.

Code
resolutions <- seq(0.1, 2.5, by = 0.1)

res_df <- map_dfr(resolutions, function(r) {
  n <- max(membership(cluster_leiden(g_ud, resolution_parameter = r)))
  tibble(resolution = r, n_communities = n)
})

ggplot(res_df, aes(x = resolution, y = n_communities)) +
  geom_line(colour = "steelblue") +
  geom_point(colour = "steelblue") +
  labs(x = "Resolution parameter", y = "Number of communities",
       title = "Leiden resolution sensitivity") +
  theme_net()

Number of detected communities increases with resolution parameter

4.3 Degree assortativity

Do highly connected neurons preferentially synapse onto other highly connected neurons? This tendency — assortative mixing by degree — was formalised by Newman (2010) and has been found to differ systematically between social (assortative) and biological networks (disassortative).

Code
cat("Degree assortativity:", round(assortativity_degree(giant, directed = TRUE), 3), "\n")
Degree assortativity: -0.152 
Code
knn_vals <- knn(giant)$knn
tibble(name = names(knn_vals), knn = knn_vals) |>
  left_join(node_tbl, by = "name") |>
  ggplot(aes(x = degree, y = knn)) +
  geom_point(colour = "steelblue", alpha = 0.7) +
  geom_smooth(method = "lm", se = FALSE, colour = "firebrick") +
  labs(x = "Degree", y = "Mean neighbour degree",
       title = "Degree assortativity in C. elegans") +
  theme_net()

Exercise 3: Change the Leiden resolution to 1.5. Do the communities still align with anatomical positions, or do they fragment? What does a negative assortativity value mean biologically in a neural circuit?


5 Part 4 · E. coli PPI and Network Robustness [~40 min]

5.1 Protein–protein interaction network

BioGRID curates experimentally validated PPIs from the literature (Stark et al. 2006; Krogan et al. 2006). We keep only physical interactions (direct binding or stable complex), not genetic interactions (functional relationships without physical contact) (Boucher and Jenna 2013; Boone et al. 2007). Large-scale genetic interaction maps such as Costanzo et al. (2016) reveal how functional buffering is distributed across the network.

Code
biogrid_raw <- read.delim(
  "../Data/BIOGRID-ORGANISM-Escherichia_coli_K12_W3110-3.5.165.mitab.txt",
  sep = "\t", header = TRUE
)

physical_types <- c(
  "psi-mi:MI:0407(direct interaction)",
  "psi-mi:MI:0915(physical association)"
)

ppi_edges <- biogrid_raw |>
  filter(Interaction.Types %in% physical_types) |>
  select(from = Alt.IDs.Interactor.A, to = Alt.IDs.Interactor.B)

ppi_net <- graph_from_data_frame(ppi_edges, directed = FALSE) |>
  simplify()

cat("E. coli PPI — nodes:", vcount(ppi_net),
    "| edges:", ecount(ppi_net),
    "| density:", round(edge_density(ppi_net), 5), "\n")
E. coli PPI — nodes: 2045 | edges: 12803 | density: 0.00613 
Code
deg_ppi <- degree(ppi_net)

tibble(degree = deg_ppi) |>
  count(degree) |>
  ggplot(aes(x = degree, y = n)) +
  geom_point(colour = "coral2") +
  scale_x_log10() + scale_y_log10() +
  labs(x = "Degree (log)", y = "Count (log)",
       title = "E. coli PPI degree distribution") +
  theme_net()

E. coli PPI degree distribution — linear on log-log, consistent with a power law

5.2 Robustness: random failures vs targeted attacks

Scale-free networks tolerate random node removal (rare hubs are unlikely to be hit) but collapse quickly under targeted hub removal (Reka et al. 2000; Albert and Barabasi 2002). This asymmetry has direct implications for antibiotic target selection and network-based drug design. The same topological vulnerability shapes epidemic spreading: highly connected nodes act as super-spreaders (Vespignani 2012; Barrat et al. 2008). For dynamical systems perspectives on network processes see Porter and Gleeson (2016).

Code
percolate <- function(g, strategy = c("random", "targeted"), n_steps = 80) {
  strategy <- match.arg(strategy)
  N        <- vcount(g)
  V(g)$id  <- seq_len(N)

  step_at <- round(seq(1, N - 1, length.out = n_steps))

  results <- vector("list", n_steps)
  for (i in seq_along(step_at)) {
    comps <- components(g)
    results[[i]] <- tibble(
      removed_frac = (N - vcount(g)) / N,
      giant_frac   = max(comps$csize)  / N
    )
    del <- if (strategy == "random") {
      sample(V(g)$id, 1)
    } else {
      which.max(degree(g))
    }
    g <- delete_vertices(g, del)
    V(g)$id <- seq_len(vcount(g))
  }
  bind_rows(results) |> mutate(strategy = strategy)
}

set.seed(42)
ppi_giant  <- decompose(ppi_net)[[1]]
robust_df  <- bind_rows(
  percolate(ppi_giant, "random"),
  percolate(ppi_giant, "targeted")
)

ggplot(robust_df, aes(x = removed_frac, y = giant_frac, colour = strategy)) +
  geom_line(linewidth = 1) +
  scale_colour_manual(values = c(random = "steelblue", targeted = "firebrick"),
                      labels = c("Random failure", "Targeted hub removal")) +
  labs(x = "Fraction of proteins removed",
       y = "Giant component fraction",
       colour = NULL,
       title  = "E. coli PPI robustness") +
  theme_net()

Percolation: the PPI network is robust to random failures but fragile under hub removal

5.3 Network inference: from correlation to edges

Most biological networks are not directly observed — they are inferred from expression data (Ideker and Krogan 2012; Teschendorff et al. 2014; Glass and Girvan 2014). The simplest approach: correlate gene expression profiles across conditions and apply a threshold. The threshold choice is a biological decision, not a statistical one.

Code
set.seed(42)
n_genes   <- 60
n_samples <- 25
expr      <- matrix(rnorm(n_genes * n_samples), nrow = n_genes,
                    dimnames = list(paste0("g", seq_len(n_genes)), NULL))

# Plant a co-expression module in genes 1–15
expr[1:15, ] <- expr[1:15, ] + rnorm(n_samples, mean = 2.5, sd = 0.5)

cor_mat    <- cor(t(expr))
thresholds <- seq(0.3, 0.9, by = 0.05)

thr_stats <- map_dfr(thresholds, function(thr) {
  adj       <- (abs(cor_mat) > thr) * 1L
  diag(adj) <- 0L
  g         <- graph_from_adjacency_matrix(adj, mode = "undirected")
  tibble(threshold    = thr,
         density      = edge_density(g),
         n_components = components(g)$no,
         mean_degree  = mean(degree(g)))
})

thr_stats |>
  pivot_longer(-threshold, names_to = "metric", values_to = "value") |>
  ggplot(aes(x = threshold, y = value)) +
  geom_line(colour = "steelblue") + geom_point(colour = "steelblue") +
  facet_wrap(~ metric, scales = "free_y",
             labeller = as_labeller(c(
               density      = "Edge density",
               n_components = "Connected components",
               mean_degree  = "Mean degree"
             ))) +
  labs(x = "Correlation threshold", y = NULL,
       title = "Effect of threshold on inferred network structure") +
  theme_net()

Network structure is highly sensitive to the correlation threshold — there is no free lunch

Exercise 4: At what threshold does the inferred network fragment into more than 10 components? Is a denser or sparser network more biologically credible for a 25-sample experiment? What additional information (besides the threshold) would you need to validate this network?


6 Part 5 · Networks as Computation [~30 min]

Goal: Show that the network formalism we used all day is also the foundation of the most powerful AI tools applied to biological data. No optimisation theory or GPU systems — the bridge is relational structure and information flow.

6.1 Artificial neural networks are graphs

An artificial neural network is a directed weighted graph. Nodes are neurons. Edges are weights. That is not a metaphor — it is the definition.

The first difference from biological networks is topological. A multi-layer perceptron (MLP) enforces a strict layered structure: all edges go forward, from layer \(l\) to layer \(l+1\), with no shortcuts, no feedback, no hubs.

Code
layer_sizes <- c(input = 4, hidden1 = 6, hidden2 = 6, output = 2)
layer_names <- names(layer_sizes)

nodes_mlp <- map_dfr(seq_along(layer_sizes), function(i) {
  tibble(layer      = layer_names[i],
         layer_idx  = i,
         neuron_idx = seq_len(layer_sizes[i]),
         name       = paste0(layer_names[i], "_", seq_len(layer_sizes[i])))
}) |> mutate(x = layer_idx,
             y = neuron_idx - (layer_sizes[layer] + 1) / 2)

edges_mlp <- map_dfr(seq_len(length(layer_sizes) - 1), function(i) {
  expand.grid(from = nodes_mlp$name[nodes_mlp$layer == layer_names[i]],
              to   = nodes_mlp$name[nodes_mlp$layer == layer_names[i + 1]],
              stringsAsFactors = FALSE)
})

g_mlp <- graph_from_data_frame(edges_mlp,
           vertices = select(nodes_mlp, name, everything()), directed = TRUE)
Code
layer_pal <- hcl.colors(length(layer_sizes), palette = "Dark 2")
layer_idx <- match(V(g_mlp)$layer, layer_names)

plot(g_mlp,
     layout             = as.matrix(select(nodes_mlp, x, y)),
     vertex.color       = layer_pal[layer_idx],
     vertex.size        = 20,
     vertex.label       = V(g_mlp)$neuron_idx,
     vertex.label.color = "white",
     vertex.label.cex   = 0.8,
     edge.arrow.size    = 0.3,
     edge.color         = adjustcolor("grey50", alpha.f = 0.3),
     main = "Multi-layer perceptron")
legend("topright", legend = layer_names, fill = layer_pal, title = "Layer", cex = 0.8)

MLP as a directed graph. Every neuron in one layer connects to every neuron in the next — fully connected, no hubs, no shortcuts.

The degree distribution makes the structural contrast with biological networks immediate:

Code
p_mlp <- tibble(degree = degree(g_mlp, mode = "total"), network = "MLP (4-6-6-2)")
p_cel <- tibble(degree = degree(giant, mode = "total"),  network = "C. elegans")

bind_rows(p_mlp, p_cel) |>
  ggplot(aes(x = degree)) +
  geom_histogram(bins = 20, fill = "steelblue", colour = "white") +
  facet_wrap(~ network, scales = "free") +
  labs(x = "Degree", y = "Count",
       title = "Degree distributions: artificial vs biological network") +
  theme_net()

MLP degree distribution (left) vs C. elegans (right). One is a spike, the other a heavy tail. Same type of object — completely different organisation.

The MLP is complicated — many edges, but completely regular. The connectome is complex — sparse, heterogeneous, with hubs that matter.

6.2 Message passing: how information flows through a graph

The most important conceptual development linking network science to modern AI is message passing. The idea is simple:

  1. Each node holds a value (a signal, a feature).
  2. At each step, every node collects the values of its neighbours and aggregates them (sum, mean, max).
  3. The node updates its own value based on what it received.
  4. Repeat for \(k\) steps.

After \(k\) steps, a node’s value reflects information from its \(k\)-hop neighbourhood. This is not new to biologists — it is how signals propagate through neural circuits, how activation cascades through a signalling network, how a disturbance spreads through a food web.

We can run this directly on the C. elegans connectome. Start each neuron with its betweenness centrality. After one step of message passing, each neuron holds the mean betweenness of its direct neighbours.

Code
# Initial signal: normalised betweenness
btw <- betweenness(giant, directed = TRUE, normalized = TRUE)

# Adjacency matrix (unweighted, row = sender, col = receiver)
A   <- as_adjacency_matrix(giant, sparse = FALSE)

# One message-passing step: each node averages its in-neighbours' signals
deg_in <- degree(giant, mode = "in")
deg_in[deg_in == 0] <- 1  # avoid division by zero for source nodes
msg1   <- as.vector((t(A) %*% btw) / deg_in)

tibble(
  neuron    = names(btw),
  before    = as.numeric(btw),
  after_1   = msg1
) |>
  ggplot(aes(x = before, y = after_1)) +
  geom_point(colour = "steelblue", alpha = 0.6) +
  geom_abline(slope = 1, intercept = 0, linetype = "dashed", colour = "firebrick") +
  labs(x = "Betweenness (original)",
       y = "Mean neighbour betweenness (after 1 step)",
       title = "Message passing on C. elegans — 1 hop") +
  theme_net()

One step of message passing on C. elegans. Each neuron’s value before and after receiving neighbour signals. High-betweenness neurons pull their neighbours’ values up.

Neurons above the dashed line are connected to high-betweenness hubs. Neurons below are connected to peripheral nodes. The graph structure determines what information reaches each node — independently of any learning.

This is the operation at the heart of Graph Neural Networks (GNNs): learn what to aggregate from neighbours, and how to update. Applied to a protein contact graph, this is how AlphaFold 2 predicts protein structure — amino acids are nodes, proximity in 3D space defines edges, message passing over this graph propagates structural context to every residue. The broader programme of applying network thinking to disease and drug targets is network medicine (Barabasi 2015; Ideker and Krogan 2012).

6.3 What network science and relational AI share

Code
tribble(
  ~Concept,              ~`Network science`,              ~`Graph Neural Networks`,
  "Node",                "Neuron, protein, species",      "Amino acid, atom, gene",
  "Edge",                "Synapse, interaction, flux",    "Contact, bond, co-expression",
  "Centrality",          "Fixed metric (degree, betw.)",  "Learned aggregation function",
  "Community",           "Dense subgraph partition",      "Latent cluster in embedding",
  "Information flow",    "Shortest paths, diffusion",     "Iterated message passing",
  "Key question",        "What is the structure?",        "What does the structure predict?"
) |> kable(caption = "Network science and GNNs: same objects, different questions")
Network science and GNNs: same objects, different questions
Concept Network science Graph Neural Networks
Node Neuron, protein, species Amino acid, atom, gene
Edge Synapse, interaction, flux Contact, bond, co-expression
Centrality Fixed metric (degree, betw.) Learned aggregation function
Community Dense subgraph partition Latent cluster in embedding
Information flow Shortest paths, diffusion Iterated message passing
Key question What is the structure? What does the structure predict?

The shift is in the question, not the object. Network science asks: what is the structure of this system? Relational AI asks: given this structure, what can we predict? The graph is the same. The analytical machinery carries over directly.

Exercise 5: Run two steps of message passing instead of one (apply the aggregation twice). Do the values converge or diverge? What does convergence mean biologically — and why might too many steps be a problem for a real GNN on a protein graph?


Epilogue · The Unstable Landscape of Tools

In Q1 2026 — the same quarter this workshop was taught — global venture capital investment hit $297 billion, of which $239 billion (81%) flowed into AI startups. Four of the five largest private funding rounds in history closed in a single 90-day window.

Code
tribble(
  ~Company,        ~Billion_USD, ~Category,
  "OpenAI",        122,          "AI",
  "Anthropic",      30,          "AI",
  "xAI",            20,          "AI",
  "Waymo",          16,          "AI",
  "Other AI",       51,          "AI",
  "Non-AI",         58,          "Other"
) |>
  mutate(Company = reorder(Company, Billion_USD)) |>
  ggplot(aes(x = Company, y = Billion_USD, fill = Category)) +
  geom_col() +
  geom_text(aes(label = paste0("$", Billion_USD, "B")),
            hjust = -0.1, size = 3.5) +
  scale_fill_manual(values = c(AI = "steelblue", Other = "grey70")) +
  scale_y_continuous(limits = c(0, 145), labels = function(x) paste0("$", x, "B")) +
  coord_flip() +
  labs(x = NULL, y = "USD (billions)", fill = NULL,
       title  = "Q1 2026 global VC funding — $297B total",
       subtitle = "81% captured by AI; OpenAI alone raised more than all non-AI startups combined") +
  theme_net() +
  theme(legend.position = "bottom")

Q1 2026 venture capital: four companies absorbed more than half of all global startup investment. Source: Crunchbase, TechInsider, IndexBox (April 2026).

These numbers are not a celebration. They are a map of concentration and fragility.

Tools come and go. The companies absorbing this capital are building platforms, APIs, and proprietary models. They will be acquired, pivoted, deprecated, or superseded — on timescales shorter than a PhD. The history of bioinformatics is a graveyard of tools that were indispensable and then vanished: Galaxy workflows that no longer run, R packages removed from CRAN, web servers that returned 404 before their papers were even cited ten times. AI tools will follow the same trajectory, only faster, because the capital pressure to replace them is now measured in hundreds of billions per quarter.

What is irreplaceable is data. Not the model weights, not the API, not the interface — the data. BioGRID, the SNAP connectome library, UniProt, the PDB: open, documented, machine-readable, format-stable datasets that have outlasted dozens of tool generations. The PSI-MI MITAB format we read in Part 4 was defined in 2004 and still works today. The edge list format we used for C. elegans is a text file with two columns. It will be readable in 2050.

The key word is standardised. A dataset with no metadata is not data — it is noise. The Genomic Standards Consortium (GSC) formalised this insight into the MIxS framework: Minimum Information about any (x) Sequence (Yilmaz et al. 2011). Whether the sequence is an amplicon, a metagenome, or a single genome, MIxS defines the minimum metadata fields — sample origin, environment, collection date, sequencing method — that make a record reusable by anyone, with any tool, ten years later. This is technology-agnostic design: the standard does not care whether the sequences were produced on a 454, an Illumina, or a platform that does not exist yet. The metadata schema outlives every instrument generation.

Reproducibility and open practice are the institutional expression of the same principle. The Turing Way is a community-maintained handbook covering reproducible workflows, open data, version control, and FAIR principles (The Turing Way Community 2022). It is itself an example of what it advocates: free, open-source, collaboratively written, and designed to remain useful regardless of which software stack its readers use.

The practical upshot for your career:

Invest in Avoid depending on
Standardised, open data formats Proprietary APIs and cloud platforms
Mathematical and biological literacy Any single tool or framework
Version-controlled, documented datasets Model weights without provenance
The concepts behind algorithms The algorithms’ current implementation

AI is not a gamble for its users. It is a gamble taken by a handful of corporations betting hundreds of billions on a single technological trajectory. The risk is theirs. The utility — or the disruption — lands on everyone else. As scientists, the most robust position is not to chase the tools but to produce the kind of data and the kind of rigorous analysis that retains value regardless of which tool reads it next.

The graph formalism we used today — adjacency matrices, edge lists, centralities, communities — predates the computer. It will outlast the current AI cycle. Learn the mathematics. Curate your data. The tools will sort themselves out.


References

Albert, Réka, and Albert Laszlo Barabasi. 2002. Statistical mechanics of complex networks.” Rev. Mod. Phys. 74 (1): 47–97. https://doi.org/10.1088/1478-3967/1/3/006.
Alon, U. 2003. Biological networks: The tinkerer as an engineer.” Science (80-. ). 301 (5641): 1866–67. https://doi.org/10.1126/science.1089072.
Anderson, Philip W. 1972. “More Is Different.” Science 177 (4047): 393–96. https://doi.org/10.1126/science.177.4047.393.
Antoniou, Ioannis, and Ilya Prigogine. 1993. “Intrinsic Irreversibility and Integrability of Dynamics.” Physica A: Statistical Mechanics and Its Applications 192: 443–64. https://doi.org/10.1016/0378-4371(93)90047-8.
Barabasi, AL. 2015. Network Science.” Chapter 8 in Netw. Sci. Cambridge University Press.
Barabasi, Albert, and Reka Albert. 1999. Emergence of Scaling in Random Networks.” Science (80-. ). 286 (October): 509–12.
Barabási, Albert-László, Réka Albert, and Hawoong Jeong. 1999. Diameter of the World-Wide Web.” Nature 401 (September): 398–99. https://doi.org/10.1038/43601.
Barrat, Alain, Marc Barthelemy, and Alessandro Vespignani. 2008. Dynamical Processes on complex networks. Vol. 1. Cambridge University Press. https://doi.org/10.1017/CBO9781107415324.004.
Bascompte, J. 2009. Disentangling the Web of Life.” Science (80-. ). 325 (5939): 416–19. https://doi.org/10.1126/science.1170749.
Boccaletti, Stefano, V. Latora, Y. Moreno, M. Chavez, and D. U. Hwang. 2006. Complex networks: Structure and dynamics.” Phys. Rep. 424 (4-5): 175–308. https://doi.org/10.1016/j.physrep.2005.10.009.
Boldi, Paolo, and Sebastiano Vigna. 2014. Axioms for centrality.” Internet Math. 10 (3-4): 222–62. https://doi.org/10.1080/15427951.2013.865686.
Bollobas, B. 2001. Random Graphs. Second Edi. Edited by W Fulton, A Katok, F Kirwan, and P Sarnak. Cambridge University Press.
Boone, Charles, Howard Bussey, and Brenda J Andrews. 2007. Exploring genetic interactions and networks with yeast. Nat. Rev. Genet. 8 (6): 437–49. https://doi.org/10.1038/nrg2085.
Boucher, Benjamin, and Sarah Jenna. 2013. Genetic interaction networks: Better understand to better predict.” Front. Genet. 4 (DEC): 1–16. https://doi.org/10.3389/fgene.2013.00290.
Broido, Anna D., and Aaron Clauset. 2018. Scale-free networks are rare.” Nat. Commun., no. 2019: 1–10. https://doi.org/10.1038/s41467-019-08746-5.
Bullmore, Edward T., Olaf Sporns, and Sara A Solla. 2009. Complex brain networks: graph theoretical analysis of structural and functional systems. Nat. Rev. Neurosci. 10 (3): 186–98. https://doi.org/10.1038/nrn2575.
Clauset, Aaron, Cosma Rohilla Shalizi, and M. E. J. Newman. 2009. Power-Law Distributions in Empirical Data.” SIAM Rev. 51 (4): 661–703. https://doi.org/10.1137/070710111.
Costanzo, M., B. VanderSluis, E. N. Koch, et al. 2016. A global genetic interaction network maps a wiring diagram of cellular function.” Science (80-. ). 353 (6306): aaf1420–20. https://doi.org/10.1126/science.aaf1420.
Cozzo, Emanuele, Guilherme Ferraz de Arruda, Francisco Aparecido Rodrigues, and Yamir Moreno. 2017. Multiplex Networks. Springer Nature. https://doi.org/10.1007/978-3-319-92255-3.
Crane, Harry. 2018. Probabilistic Foundations of Statistical Network Analysis. Vol. 39. CRC Press.
Dorogovtsev, S. N., and J. F. F. Mendes. 2003. Evolution of Networks: From Biological Nets to the Internet and WWW. Oxford University Press. http://www.amazon.it/Evolution-Networks-From-Biological-Internet/dp/0199686718/ref=pd{\_}sim{\_}14{\_}1?ie=UTF8{\&}refRID=1QKPBHN7GY6EJB8F7SYY{\&}dpID=41O4hYegPEL{\&}dpSrc=sims{\&}preST={\_}AC{\_}UL160{\_}SR108,160{\_}.
Douglas, Luke A. 2015. A User’s Guide to Network Analysis in R. Springer International Publishing. https://doi.org/10.1007/978-3-319-23883-8.
Emmons, Scott, Stephen Kobourov, Mike Gallant, and Katy Börner. 2016. Analysis of network clustering algorithms and cluster quality metrics at scale.” PLoS One 11 (7): 1–18. https://doi.org/10.1371/journal.pone.0159161.
Freeman, L C. 1979. Centrality in social networks 1: conceptual clarification.” Soc. Networks 1 (3): 215–39. https://doi.org/10.1016/0378-8733(78)90021-7.
Freeman, Linton C., Stephen P. Borgatti, and Douglas R. White. 1991. Centrality in valued graphs: A measure of betweenness based on network flow.” Soc. Networks 13 (2): 141–54. https://doi.org/10.1016/0378-8733(91)90017-N.
Gillespie, Colin S. 2014. Fitting heavy tailed distributions: the poweRlaw package. https://doi.org/10.18637/jss.v064.i02.
Glass, Kimberly, and Michelle Girvan. 2014. Annotation enrichment analysis: an alternative method for evaluating the functional properties of gene sets. Sci. Rep. 4: 4191. https://doi.org/10.1038/srep04191.
Hartwell, L H, J J Hopfield, S Leibler, and A W Murray. 1999. From molecular to modular cell biology. Nature 402 (6761 Suppl): C47–52. https://doi.org/10.1038/35011540.
Holme, Petter, and Jari Saramaki. 2012. Temporal networks.” Phys. Rep. 519 (3): 97–125. https://doi.org/10.1016/j.physrep.2012.03.001.
Ideker, Trey, and Nevan J Krogan. 2012. Differential network biology.” Mol. Syst. Biol. 8 (565): 1–9. https://doi.org/10.1038/msb.2011.99.
Ingram, Piers J, Michael P H Stumpf, and Jaroslav Stark. 2006. Network motifs: structure does not determine function. BMC Genomics 7: 108. https://doi.org/10.1186/1471-2164-7-108.
Kaiser, Marcus, and Claus C. Hilgetag. 2006. Nonoptimal component placement, but short processing paths, due to long-distance projections in neural systems.” PLoS Comput. Biol. 2 (7): 0805–15. https://doi.org/10.1371/journal.pcbi.0020095.
Kauffman, Stuart A. 1993. The Origins of Order Self-Organization and Selection in Evolution. Oxford University Press.
Koch, C. 2012. Modular Biological Complexity.” Science (80-. ). 337 (6094): 531–32. https://doi.org/10.1126/science.1218616.
Kolaczyk, Eric. 2009. Statistical analysis of network data. https://doi.org/10.1007/978-0-387-88146-1.
Krogan, Nevan J, Gerard Cagney, Haiyuan Yu, et al. 2006. Global landscape of protein complexes in the yeast Saccharomyces cerevisiae. Nature 440 (7084): 637–43. https://doi.org/10.1038/nature04670.
Lü, Linyuan, Duanbing Chen, Xiao Long Ren, Qian Ming Zhang, Yi Cheng Zhang, and Tao Zhou. 2016. Vital nodes identification in complex networks.” Phys. Rep., ahead of print. https://doi.org/10.1016/j.physrep.2016.06.007.
McCann, Kevin Shear. 2000. “The Diversity–Stability Debate.” Nature 405: 228–33. https://doi.org/10.1038/35012234.
Montoya, J M, S L Pimm, and R V Sole. 2006. Ecological networks and their fragility.” Nature 442 (7100): 259–64. https://doi.org/Doi 10.1038/Nature04927.
Newman, M. E J, and Tiago P. Peixoto. 2015. Generalized Communities in Networks.” Phys. Rev. Lett. 115 (8): 1–5. https://doi.org/10.1103/PhysRevLett.115.088701.
Newman, M. E. J. 2005. Power laws, Pareto distributions and Zipf’s law.” Contemp. Phys. 46 (5): 323–51. https://doi.org/10.1080/00107510500052444.
Newman, M. E. J. 2006. Modularity and community structure in networks. https://doi.org/10.1073/pnas.0601602103.
Newman, Mark E. J. 2010. Networks: An Introduction. Oxford University Press. https://doi.org/10.1093/acprof:oso/9780199206650.001.0001.
Pascual, Mercedes, and Jennifer A. Dunne. 2007. Ecological networks: linking structure and dynamics in food webs. Vol. 88. https://doi.org/10.1890/0012-9658(2007)88[265:ENLSAD]2.0.CO;2.
Pearson, John E. 1993. “Complex Patterns in a Simple System.” Science 261: 189–92. https://doi.org/10.1126/science.261.5118.189.
Porter, Mason A., and James P. Gleeson. 2016. Dynamical Systems on Networks: A Tutorial. Springer. https://doi.org/10.1007/978-3-319-26641-1.
Prigogine, Ilya, and Isabelle Stengers. 1984. Order Out of Chaos: Man’s New Dialogue with Nature. Bantam Books.
Reka, A, H Jeong, and Albert-László Barabási. 2000. Error and Attack Tolerance of Complex Networks.” Nature 406 (July): 378–81. https://doi.org/10.1103/PhysRevE.76.026103.
Rubinov, Mikail, and Olaf Sporns. 2010. Complex network measures of brain connectivity: Uses and interpretations.” Neuroimage 52 (3): 1059–69. https://doi.org/10.1016/j.neuroimage.2009.10.003.
Sole, R. V., R. Ferrer-Cancho, J. M. Montoya, and S. Valverde. 2002. Selection, tinkering, and emergence in complex networks.” Complexity 8 (1): 20–33. https://doi.org/10.1002/cplx.10055.
Solé, Ricard V., and Sergi Valverde. 2004. Information Theory of Complex Networks: On Evolution and Architectural Constraints. 207: 189–207. https://doi.org/10.1007/978-3-540-44485-5_9.
Stark, Chris, Bobby-Joe Breitkreutz, Teresa Reguly, Lorrie Boucher, Ashton Breitkreutz, and Mike Tyers. 2006. BioGRID: A General Repository for Interaction Datasets.” Nucleic Acids Research 34: D535–39. https://doi.org/10.1093/nar/gkj109.
Strogatz, S H. 2001. Exploring complex networks. Nature 410 (6825): 268–76. https://doi.org/10.1038/35065725.
Stumpf, M. P. H., and M. a. Porter. 2012. Critical Truths About Power Laws.” Science (80-. ). 335 (6069): 665–66. https://doi.org/10.1126/science.1216142.
Stumpf, Michael P H, Carsten Wiuf, and Robert M May. 2005. Subnets of scale-free networks are not scale-free: sampling properties of networks. Proc. Natl. Acad. Sci. U. S. A. 102 (12): 4221–24. https://doi.org/10.1073/pnas.0501179102.
Teschendorff, Andrew E., Peter Sollich, and Reimer Kuehn. 2014. Signalling entropy: A novel network-theoretical framework for systems analysis and interpretation of functional omic data.” Methods 67 (3): 282–93. https://doi.org/10.1016/j.ymeth.2014.03.013.
The Turing Way Community. 2022. The Turing Way: A Handbook for Reproducible, Ethical and Collaborative Data Science. Zenodo. https://doi.org/10.5281/zenodo.3233853.
Traag, V A, L Waltman, and N J van Eck. 2019. “From Louvain to Leiden: Guaranteeing Well-Connected Communities.” Scientific Reports 9: 5233. https://doi.org/10.1038/s41598-019-41695-z.
Turing, Alan Mathison. 1952. “The Chemical Basis of Morphogenesis.” Philosophical Transactions of the Royal Society B 237: 37–72. https://doi.org/10.1098/rstb.1952.0012.
Varshney, Lav R., Beth L. Chen, Eric Paniagua, David H. Hall, and Dmitri B. Chklovskii. 2011. Structural properties of the Caenorhabditis elegans neuronal network.” PLoS Comput. Biol. 7 (2). https://doi.org/10.1371/journal.pcbi.1001066.
Vespignani, Alessandro. 2012. Modelling dynamical processes in complex socio-technical systems.” Nat. Phys. 8 (4): 32–39. https://doi.org/DOI: 10.1038/NPHYS2160.
Wasserman, Stanley, and Katherine Faust. 1994. Social Network Analysis: Methods and Applications. Vol. 47. Cambridge University Press. http://www.jstor.org/stable/591741?origin=crossref.
Watts, D., and S. H. Strogatz. 1998. Collective Dynamics of ’Small-World’ Networks.” Nature 393 (6684): 440–42. https://doi.org/10.1038/30918.
Yilmaz, Pelin, Renzo Kottmann, Dawn Field, et al. 2011. “Minimum Information about a Marker Gene Sequence (MIMARKS) and Minimum Information about Any (x) Sequence (MIxS) Specifications.” Nature Biotechnology 29: 415–20. https://doi.org/10.1038/nbt.1823.