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) -> GraphNodeWrap a symbol as an isolated node for inclusion in a graph constructor such as DAG.
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, >:
| Marker | Equivalent constructor |
|---|---|
--> | directed(src, dst) |
<-- | directed(dst, src) |
--- | undirected(src, dst) |
<-> | bidirected(src, dst) |
o-> | partially_directed(src, dst) |
<-o | partially_directed(dst, src) |
o-- | partially_undirected(src, dst) |
--o | partially_undirected(dst, src) |
o-o | partial(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.
CausalStructures.CausalGraph — Type
CausalGraphAbstract supertype for all causal graph classes. Concrete subtypes: DAG, UG, AbstractPDAG, ADMG, AbstractAG, PAG, and UNKNOWN.
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) -> DAGA 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 --> CPartially 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.AbstractPDAG — Type
AbstractPDAG <: CausalGraphAbstract supertype for partially directed acyclic graphs. Concrete subtypes: PDAG, CPDAG, and MPDAG.
CausalStructures.PDAG — Type
PDAG(items...) -> PDAG
PDAG(s::AbstractString) -> PDAGA 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 --- CCausalStructures.CPDAG — Type
CPDAG(items...) -> CPDAG
CPDAG(s::AbstractString) -> CPDAGA 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 --- CReferences
CausalStructures.MPDAG — Type
MPDAG(items...) -> MPDAG
MPDAG(s::AbstractString) -> MPDAGA 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 --> CReferences
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) -> ADMGAn 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 <-> YReferences
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) -> AGAn 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)
falseReferences
CausalStructures.MAG — Type
MAG(items...) -> MAG
MAG(s::AbstractString) -> MAGA 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 --> DReferences
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) -> PAGA 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-> BReferences
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) -> UGAn 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 --- CUnknown 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) -> UNKNOWNA 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-> EEdges
CausalStructures.CausalEdge — Type
CausalEdgeAn 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.
CausalStructures.directed — Function
directed(src, dst) -> CausalEdge # src --> dstConstruct the directed edge src --> dst.
CausalStructures.undirected — Function
undirected(src, dst) -> CausalEdge # src --- dstConstruct the undirected edge src --- dst.
CausalStructures.bidirected — Function
bidirected(src, dst) -> CausalEdge # src <-> dstConstruct the bidirected edge src <-> dst.
CausalStructures.partially_directed — Function
partially_directed(src, dst) -> CausalEdge # src o-> dstConstruct the partially directed edge src o-> dst.
CausalStructures.partially_undirected — Function
partially_undirected(src, dst) -> CausalEdge # src o-- dstConstruct the partially undirected edge src o-- dst.
CausalStructures.partial — Function
partial(src, dst) -> CausalEdge # src o-o dstConstruct the partial edge src o-o dst, with a circle mark at both endpoints.
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.RequiredEdge — Type
RequiredEdgeA background-knowledge constraint stating that the directed edge src --> dst must be present, displayed as src --> dst. It is not a graph edge (unlike the CausalEdge from directed): it only carries meaning inside BackgroundKnowledge and is rejected by graph constructors such as DAG. Use required_directed to construct one.
CausalStructures.ForbiddenEdge — Type
ForbiddenEdgeA background-knowledge constraint stating that the directed edge src --> dst must not be present, displayed as src !--> dst. It is not a graph edge: it only carries meaning inside BackgroundKnowledge and is rejected by graph constructors such as DAG. Use forbidden_directed to construct one.
CausalStructures.required_directed — Function
required_directed(src, dst) -> RequiredEdge # src --> dstDeclare the directed edge src --> dst as required background knowledge, for use in BackgroundKnowledge.
CausalStructures.forbidden_directed — Function
forbidden_directed(src, dst) -> ForbiddenEdge # src !--> dstDeclare the directed edge src --> dst as forbidden background knowledge, for use in BackgroundKnowledge.
CausalStructures.BackgroundKnowledge — Type
BackgroundKnowledge(items...) -> BackgroundKnowledge
BackgroundKnowledge(s::AbstractString) -> BackgroundKnowledgeCausal 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 !--> DReferences