Graph & Edge Types

Here we explain how to build a causal graph and briefly describe the meaning of each causal graph class. For a comprehensive introduction to these and the underlying theory, see, for instance, Pearl (2009) or Peters et al. (2017); Richardson and Spirtes (2002) covers AG/MAG specifically, and Zhang (2008) covers PAG.

Constructing graphs

Each graph type is its own constructor: call DAG, ADMG, PAG, etc. with some edges (and, if you have isolated nodes, some nodes).

CausalStructures.node — Function
node(name::Symbol) -> GraphNode

Wrap a symbol as an isolated node for inclusion in a graph constructor such as DAG.

Examples

julia> DAG(node(:A), node(:B), node(:C))
DAG with 3 nodes and 0 edges:
  nodes: A, B, C
  edges:
    (none)
source

String syntax

The string form uses a compact syntax instead of composing CausalEdge values by hand. Statements are separated by commas (or newlines); each connects node names with an edge marker built from <, -, o, >:

MarkerEquivalent constructor
-->directed(src, dst)
<--directed(dst, src)
---undirected(src, dst)
<->bidirected(src, dst)
o->partially_directed(src, dst)
<-opartially_directed(dst, src)
o--partially_undirected(src, dst)
--opartially_undirected(dst, src)
o-opartial(src, dst)

+ fans a marker out to (or in from) several nodes at once, and chaining markers connects consecutive node groups pairwise, so "A --> B --> C" yields two edges (A-->B, B-->C) while "A --> B + C" yields A-->B and A-->C. A statement with no marker (e.g. "F") declares isolated node(s).

Graph classes

Every graph type is a subtype of CausalGraph, and each graph class is verified on construction to be a valid graph.

Directed Acyclic Graphs

A DAG (Directed Acyclic Graph) is the standard causal graph. All edges are directed (-->), and the graph contains no directed cycles.

CausalStructures.DAG — Type
DAG(items...) -> DAG
DAG(s::AbstractString) -> DAG

A Directed Acyclic Graph. Directed edges only, and no directed cycles allowed.

Examples

julia> dag = DAG(directed(:A, :B), directed(:B, :C))
DAG with 3 nodes and 2 edges:
  nodes: A, B, C
  edges:
    A --> B, B --> C

julia> dag_iso = DAG(directed(:A, :B), node(:C))
DAG with 3 nodes and 1 edge:
  nodes: A, B, C
  edges:
    A --> B

julia> DAG("A --> B --> C")
DAG with 3 nodes and 2 edges:
  nodes: A, B, C
  edges:
    A --> B, B --> C
source

Partially Directed Acyclic Graphs

The subtypes of AbstractPDAG all have directed (-->) and undirected (---) edges, and the graph contains no directed cycles. Undirected edges represent edges whose orientation is not specified. A PDAG is the general case; a CPDAG is the special PDAG that represents an entire Markov equivalence class of DAGs (an edge stays undirected exactly when its orientation varies across the class); an MPDAG is a PDAG in which background knowledge may specify orientations that are not determined by the underlying equivalence class.

See Equivalence Classes for worked examples of all three.

CausalStructures.PDAG — Type
PDAG(items...) -> PDAG
PDAG(s::AbstractString) -> PDAG

A Partially Directed Acyclic Graph. Directed and undirected edges only, and no directed cycles allowed.

Examples

julia> PDAG(directed(:A, :B), undirected(:B, :C))
PDAG with 3 nodes and 2 edges:
  nodes: A, B, C
  edges:
    A --> B, B --- C

julia> PDAG("A --> B --- C")
PDAG with 3 nodes and 2 edges:
  nodes: A, B, C
  edges:
    A --> B, B --- C
source
CausalStructures.CPDAG — Type
CPDAG(items...) -> CPDAG
CPDAG(s::AbstractString) -> CPDAG

A Completed Partially Directed Acyclic Graph. The unique graph representing a Markov equivalence class (MEC) of DAGs. Directed edges represent compelled orientations shared by all DAGs in the class. Undirected edges represent adjacencies whose orientation differs across DAGs in the class. Consequently, every edge is directed exactly when its orientation is invariant within the MEC.

Examples

julia> CPDAG("A --- B --- C")
CPDAG with 3 nodes and 2 edges:
  nodes: A, B, C
  edges:
    A --- B, B --- C

References

source
CausalStructures.MPDAG — Type
MPDAG(items...) -> MPDAG
MPDAG(s::AbstractString) -> MPDAG

A Maximally Partially Directed Acyclic Graph. A PDAG that is closed under Meek's orientation rules R1-R4: no further edge orientation can be implied. MPDAGs arise when background knowledge (forced edge orientations) is present.

Examples

julia> MPDAG("A --> B --> C")
MPDAG with 3 nodes and 2 edges:
  nodes: A, B, C
  edges:
    A --> B, B --> C

References

source

Acyclic Directed Mixed Graphs

An ADMG allows directed (-->) and bidirected (<->) edges. Directed edges represent causal relations, while bidirected edges represent unobserved confounding between their endpoints. For example, an ADMG is what you obtain when you use latent_project to project latent variables out of a DAG.

CausalStructures.ADMG — Type
ADMG(items...) -> ADMG
ADMG(s::AbstractString) -> ADMG

An Acyclic Directed Mixed Graph. Directed and bidirected edges only, and no directed cycles allowed.

Examples

julia> ADMG("X --> Y, X <-> Y")
ADMG with 2 nodes and 2 edges:
  nodes: X, Y
  edges:
    X --> Y, X <-> Y

References

source

Ancestral Graphs

AbstractAG allows for directed (-->), bidirected (<->), and undirected edges (---). In an ancestral graph, an arrowhead at a node indicates that the node is not an ancestor of the other endpoint. Thus, a bidirected edge (<->) indicates that neither endpoint is an ancestor of the other. In causal applications, such edges commonly represent unobserved confounding. Undirected edges have a different meaning here than in a PDAG: rather than representing uncertain orientation, they represent selection bias, arising from conditioning on variables that induce associations through common effects.

CausalStructures.AG — Type
AG(items...) -> AG
AG(s::AbstractString) -> AG

An Ancestral Graph. Directed, undirected, and bidirected edges only. It contains no directed cycles, and if X <-> Y then neither X is an ancestor of Y nor Y of X. Additionally, nodes incident to an undirected edge have no arrowheads pointing at them on any adjacent edge (i.e., no parents or spouses).

Examples

Every MAG is an AG, but not every AG is a MAG: below, A and B are non-adjacent but no subset of {C, D} m-separates them, so this AG is not maximal.

julia> ag = AG("C <-> A <-> B <-> D, A --> D, B --> C")
AG with 4 nodes and 5 edges:
  nodes: A, B, C, D
  edges:
    A <-> C, A <-> B, B <-> D, A --> D, B --> C

julia> is_mag(ag)
false

References

source
CausalStructures.MAG — Type
MAG(items...) -> MAG
MAG(s::AbstractString) -> MAG

A Maximal Ancestral Graph. An AG in which every pair of non-adjacent nodes is m-separated by some subset of the remaining nodes. MAGs are the canonical representatives of equivalence classes of DAGs with hidden variables.

Examples

julia> MAG("A <-> B, C --> B --> D")
MAG with 4 nodes and 3 edges:
  nodes: A, B, C, D
  edges:
    A <-> B, C --> B, B --> D

References

source

Partial Ancestral Graphs

A PAG plays a role for MAGs analogous to that of a CPDAG for DAGs: it represents a Markov equivalence class of MAGs (Zhang, 2008). PAG edges can have three endpoint marks: a tail, an arrowhead, or a circle. A circle indicates that the corresponding endpoint mark is not determined by the Markov equivalence class.

CausalStructures.PAG — Type
PAG(items...) -> PAG
PAG(s::AbstractString) -> PAG

A Partial Ancestral Graph. The graph representing a Markov equivalence class of MAGs (and thus of DAGs with latent confounders and selection bias). It has the same skeleton as every MAG in the class, and each endpoint carries an invariant mark shared by every MAG in the class: an arrowhead (>), a tail (-), or a circle (o) where the mark varies across the class. All six edge kinds are allowed (-->, ---, <->, o->, o--, o-o).

Validation is checked by doing a round-trip conversion to a MAG and back, and verifying that the original PAG is recovered.

Examples

julia> PAG("A o-> B <-o C")
PAG with 3 nodes and 2 edges:
  nodes: A, B, C
  edges:
    A o-> B, C o-> B

References

source

Undirected Graphs

Not commonly used in causal inference, but still present for users that want it. Only undirected (---) edges are allowed.

CausalStructures.UG — Type
UG(items...) -> UG
UG(s::AbstractString) -> UG

An Undirected Graph. Undirected edges only.

Examples

julia> UG(undirected(:A, :B), undirected(:B, :C))
UG with 3 nodes and 2 edges:
  nodes: A, B, C
  edges:
    A --- B, B --- C

julia> UG("A --- B --- C")
UG with 3 nodes and 2 edges:
  nodes: A, B, C
  edges:
    A --- B, B --- C
source

Unknown Graphs

UNKNOWN imposes no structural constraints on the graph. It accepts all supported edge types, including self-loops and parallel edges. Use it when you need to represent a graph that does not fit any of the other graph classes.

CausalStructures.UNKNOWN — Type
UNKNOWN(items...) -> UNKNOWN
UNKNOWN(s::AbstractString) -> UNKNOWN

A graph with no structural constraints enforced. Accepts all edge types, including self-loops and multiple edges between the same pair of nodes. Intended as a fallback for graph classes not yet natively supported.

Examples

julia> UNKNOWN("A --> B + C, D o-> E")
UNKNOWN with 5 nodes and 3 edges:
  nodes: A, B, C, D, E
  edges:
    A --> B, A --> C, D o-> E
source

Edges

CausalStructures.CausalEdge — Type
CausalEdge

An edge between two nodes, specified by source (src), destination (dst), and endpoint marks at each end (src_end, dst_end). Use the edge constructor functions (directed, undirected, bidirected, etc.) rather than constructing CausalEdge directly.

An edge whose two endpoint marks are identical (---, <->, o-o) means the same thing either way round, so its endpoints are stored in a canonical order: src is whichever node name sorts first.

source

Background knowledge

Sometimes you want to impose some knowledge into your graph, such as A must cause B, or that C definitely doesn't cause D.

CausalStructures.BackgroundKnowledge — Type
BackgroundKnowledge(items...) -> BackgroundKnowledge
BackgroundKnowledge(s::AbstractString) -> BackgroundKnowledge

Causal background knowledge: a set of required direct causes (A --> B must be present) and forbidden direct causes (C --> D must be absent).

items may be any combination of RequiredEdge values from required_directed and ForbiddenEdge values from forbidden_directed. The string form uses the DAG string syntax restricted to the markers -->, <--, !-->, and !<--.

Locally contradictory knowledge (the same edge both required and forbidden, or both directions required) raises an error at construction.

Apply to a graph with apply_background_knowledge or dag_to_mpdag.

Examples

julia> BackgroundKnowledge(required_directed(:A, :B), forbidden_directed(:C, :D))
BackgroundKnowledge with 1 required and 1 forbidden:
  required: A --> B
  forbidden: C !--> D

julia> BackgroundKnowledge("A --> B, C !--> D")
BackgroundKnowledge with 1 required and 1 forbidden:
  required: A --> B
  forbidden: C !--> D

References

source