Understand the dyad as the fundamental unit of causal structure
Recognise how the dyad (two nodes, one edge) is the minimal unit of directed dependence
See how all complex causal structures are built from combinations of dyads
Connect dyad-first thinking to structural causal models
Apply dyad-first thinking to structural causal models
4.2 Introduction
In causal graphs, the dyad (two nodes connected by a single directed edge) is the minimal unit of structure. This chapter treats the dyad as the primary unit of causal modelling, showing how all complex causal graphs are built from combinations of dyads.
In graph theory and causal inference (Peters et al. 2017), the dyad is the minimal unit of directed dependence and a building block for larger structures (chains, forks, colliders, and networks).
4.3 The Dyad: Two Nodes, One Edge
A dyad consists of two nodes connected by a single directed edge: \(X \rightarrow Y\). This is the smallest structure that encodes a direct dependence of \(Y\) on \(X\).
The dyad = the minimal unit of directed dependence
The dyad \(X \rightarrow Y\) encodes:
Which occasions are in relation: \(X\) and \(Y\)
How they relate: the edge direction \(X \to Y\)
The mechanism: encoded in the structural equation
In structural causal models, the dyad is expressed through a structural equation:
\[
Y \coloneqq f(X, U)
\]
where:
\(X\) is the source variable
\(Y\) is the target variable
\(f\) encodes the mechanism mapping inputs to output
Intervention semantics: When we intervene \(do(X = x)\), we modify the generating process by fixing \(X\) to a specific value, changing the distribution of \(Y\) through the mechanism.
4.4 Why the Dyad is Fundamental
4.4.1 Why the Dyad is Fundamental
The graph structure \(G = (V, E)\) is composed entirely of dyads. Every edge connects two nodes, making the dyad the basic unit from which more complex causal structure is built.
4.5 Dyad Semantics
4.5.1 Structural Equation
A dyad \(X \rightarrow Y\) is encoded in a structural equation:
\[
Y \coloneqq f(X, U)
\]
where:
\(X\) is the source variable
\(Y\) is the target variable
\(f\) encodes how \(X\) influences \(Y\) (the mechanism)
\(U\) is exogenous noise
4.5.2 Intervention on a Dyad
When we intervene \(do(X = x)\), we fix the input:
\[
Y \coloneqq f(x, U)
\]
The dyad structure remains (the edge still exists), but the source is now fixed exogenously rather than generated by its usual mechanism.
4.5.3 The Dyad Encodes a Local Mechanism
The dyad \(X \rightarrow Y\) is not merely a connection; it encodes a complete local dependency:
Which variables are in relation: \(X\) and \(Y\) (the two nodes)
How they relate: the edge direction \(X \to Y\)
The mechanism: encoded in the function \(f\)
What novelty is introduced: encoded in the exogenous noise \(U\)
Key insight: The dyad is the fundamental unit: two nodes and a directed edge form a minimal unit of causal structure.
4.5.4 Implementation: Visualising the Dyad
We can visualise the fundamental dyad structure using Graphs.jl and GraphMakie:
# Find project root and include ensure_packages.jlproject_root =let current =pwd()while !isfile(joinpath(current, "Project.toml")) && !isfile(joinpath(current, "_quarto.yml")) parent =dirname(current) parent == current &&break current = parentend currentendinclude(joinpath(project_root, "scripts", "ensure_packages.jl"))@auto_using DAGMakie CairoMakie Graphs# The dyad: X → Y (two nodes, one edge)g_dyad =SimpleDiGraph(2)add_edge!(g_dyad, 1, 2) # X → Y
Resolving package versions...
Updating `~/Documents/Work/CDCS/Project.toml`
[d4a9c1e2] + DAGMakie v0.1.0 `~/Documents/Work/CDCS/packages/DAGMakie.jl` Updating `~/Documents/Work/CDCS/Manifest.toml`
[d4a9c1e2] + DAGMakie v0.1.0 `~/Documents/Work/CDCS/packages/DAGMakie.jl`Precompiling packages...
5945.5 ms ✓ QuartoNotebookWorkerMakieExt (serial)
1 dependency successfully precompiled in 6 seconds
Precompiling packages...
4881.7 ms ✓ QuartoNotebookWorkerCairoMakieExt (serial)
1 dependency successfully precompiled in 5 seconds
Precompiling packages...
5560.4 ms ✓ DAGMakie
1 dependency successfully precompiled in 6 seconds. 273 already precompiled.
true
The dyad: the fundamental unit of causal structure
4.5.5 Nodes Defined by Their Incident Edges
In a process-first ontology, nodes are defined by their incident edges:
A node with no incoming edges is a source (e.g., an exogenous variable)
A node with no outgoing edges is a sink (a final occasion, or an observed variable)
A node with both incoming and outgoing edges is an intermediate occasion (prehending some and prehended by others)
The node’s identity and role emerge from the pattern of edges incident to it.
4.5.6 Implementation: Nodes Defined by Their Incident Edges
We can demonstrate how nodes are defined by their incident edges:
# Find project root and include ensure_packages.jlproject_root =let current =pwd()while !isfile(joinpath(current, "Project.toml")) && !isfile(joinpath(current, "_quarto.yml")) parent =dirname(current) parent == current &&break current = parentend currentendinclude(joinpath(project_root, "scripts", "ensure_packages.jl"))@auto_using DAGMakie CairoMakie Graphs# Example: Graph with source, intermediate, and sink nodesg =SimpleDiGraph(4)add_edge!(g, 1, 2) # Source → Intermediateadd_edge!(g, 2, 3) # Intermediate → Intermediateadd_edge!(g, 3, 4) # Intermediate → Sink# Identify node rolesfunctionnode_role(g::AbstractGraph, node::Integer)""" Determine node role based on incident edges. Returns: "Source", "Sink", "Intermediate", or "Isolated" """ has_incoming =any(has_edge(g, src, node) for src in1:nv(g)) has_outgoing =any(has_edge(g, node, dst) for dst in1:nv(g))if !has_incoming && has_outgoingreturn"Source"elseif has_incoming && !has_outgoingreturn"Sink"elseif has_incoming && has_outgoingreturn"Intermediate"elsereturn"Isolated"endend# Visualise with role labelslet node_colors = [:yellow, :lightblue, :lightblue, :lightgreen] # Source, Intermediate, Intermediate, Sink node_labels = [string(i, ": ", node_role(g, i)) for i in1:nv(g)] fig, ax, p =dagplot(g, figure_size = (800, 400), layout_mode =:acyclic, node_color = node_colors, nlabels = node_labels, nlabels_fontsize =12 ) fig # Only this gets displayedendprintln("Node roles (defined by incident edges):")for i in1:nv(g)println(" Node ", i, ": ", node_role(g, i))end
When we have multiple dyads, we have a larger dependency structure:
Two dyads: \(X \rightarrow Y\) and \(Y \rightarrow Z\) form a chain (a two-step dependency, three nodes)
Two dyads sharing a source: \(X \leftarrow Y \rightarrow Z\) form a fork (one occasion prehended by two others, three nodes)
Two dyads sharing a target: \(X \rightarrow Y \leftarrow Z\) form a collider (two occasions prehending the same target, three nodes)
Each pattern is a combination of dyads, not a fundamental structure in itself. The fundamental unit remains the dyad.
4.6.2 Complex Structures = Combinations of Dyads
A graph \(G = (V, E)\) where:
\(V\) is the set of vertices (nodes / variables)
\(E\) is the set of edges (directed dependencies)
In a dyad-first view: A graph is a collection of dyads (two nodes connected by one directed edge). Each edge in \(E\) represents a dyad, and complex structures emerge from how dyads connect and overlap.
4.6.3 Sparsity of Edges
Most possible dyads don’t exist. In a system with \(n\) nodes, there are \(n(n-1)\) possible directed dyads, but typically only a small fraction of these exist. This sparsity is a common modelling assumption (and empirical regularity) in networked systems, and it is what makes many algorithms and computations scalable.
4.6.4 Implementation: Sparsity of Dyads
We can demonstrate sparsity:
# Find project root and include ensure_packages.jlproject_root =let current =pwd()while !isfile(joinpath(current, "Project.toml")) && !isfile(joinpath(current, "_quarto.yml")) parent =dirname(current) parent == current &&break current = parentend currentendinclude(joinpath(project_root, "scripts", "ensure_packages.jl"))@auto_using DAGMakie Graphs# Example: System with n nodesn =10# Total possible directed dyadstotal_possible = n * (n -1)println("System with ", n, " nodes:")println(" Total possible directed dyads: ", total_possible)# Typical sparse structure: each node prehends only a few others# Example: each node has on average 2 incoming edgesavg_edges_per_node =2typical_edges = n * avg_edges_per_nodesparsity_ratio =1- typical_edges / total_possibleprintln(" Typical number of edges: ", typical_edges)println(" Sparsity ratio: ", round(sparsity_ratio *100, digits=1), "%")println(" Most possible dyads (", round(sparsity_ratio *100, digits=1), "%) don't exist")# Create a sparse graph exampleg_sparse =SimpleDiGraph(n)# Add a few edges (sparse structure)for i in1:n# Each node connects to 1-2 others targets =rand(1:n, rand(1:2))for t in targetsif t != i && !has_edge(g_sparse, i, t)add_edge!(g_sparse, i, t)endendendactual_edges =ne(g_sparse)actual_sparsity =1- actual_edges / total_possibleprintln("\nExample sparse graph:")println(" Actual edges: ", actual_edges, " out of ", total_possible, " possible")println(" Sparsity: ", round(actual_sparsity *100, digits=1), "%")println("\nThis sparsity reflects that most variables do not directly depend on most others")
System with 10 nodes:
Total possible directed dyads: 90
Typical number of edges: 20
Sparsity ratio: 77.8%
Most possible dyads (77.8%) don't exist
Example sparse graph:
Actual edges: 9 out of 90 possible
Sparsity: 90.0%
This sparsity reflects that most variables do not directly depend on most others
4.7 Key Takeaways
The dyad is the primary unit: Two nodes connected by one edge form a minimal unit of causal structure
The dyad encodes a local mechanism: The dyad \(X \rightarrow Y\) specifies a direct dependency plus a mechanism and noise term
All complex structures are combinations of dyads: Chains, forks, colliders, and networks are all built from multiple dyads
Dyad-first thinking: Many structural questions can be phrased in terms of edges and local mechanisms
Intervention modifies the generating process: \(do(X = x)\) changes the distribution of \(Y\) by fixing the input
Sparsity of dyads: Most possible dyads don’t exist, and sparsity drives both modelling choices and computational scalability