PAG Causal Effects
This guide builds on Equivalence Classes and Causal Identification and covers causal adjustment when the graph is known only as a PAG, i.e. when the underlying MAG is identified only up to Markov equivalence.
using CausalStructures
using CairoMakie
using NetworkLayoutAdjustment with a PAG
Consider the MAG from Equivalence Classes, where A and B share a hidden common cause, and C and B each cause their successor:
mag = MAG("A <-> B, C --> B --> D")
pag = mag_to_pag(mag)
plot(pag)The resulting PAG contains the circle-marked edge A o-> B. Depending on which MAG in the equivalence class is the true graph, this edge can correspond to either A --> B or A <-> B.
Consequently, backdoor_set cannot determine an adjustment set from the PAG alone:
backdoor_set(pag, :A, :D) === nothingtrueIn particular, whether B needs to be included in an adjustment set depends on how the circle at A is resolved.
Not every effect here is uncertain. For the effect of B on D, the PAG admits an adjustment set that is valid for every MAG in the equivalence class. The generalized adjustment criterion (GAC) can therefore be applied directly to the PAG with all_adjustment_sets:
all_adjustment_sets(pag, :B, :D; minimal=false)4-element Vector{Vector{Symbol}}:
[]
[:A]
[:C]
[:A, :C]Adjustment sets
pagcauses considers MAGs compatible with the PAG and returns adjustment sets that are valid in at least one such MAG (Wang et al., 2025):
pagcauses(pag, :A, :D)1-element Vector{Vector{Symbol}}:
[]For this example, the empty set is valid when A --> B, since there is then no backdoor path from A to D through B. When A <-> B, B must instead be included to block the path through the hidden common cause. Thus {B} is a valid adjustment set in that case; nonminimal supersets such as {B, C} may also be returned when they are valid.
The same procedure applies when the ambiguity occurs elsewhere in the graph. For example:
pagcauses(pag, :C, :D)1-element Vector{Vector{Symbol}}:
[]If there is no MAG compatible with the PAG in which A can have a causal effect on D, pagcauses returns no possible causal effect:
pagcauses(pag, :D, :A)Vector{Symbol}[]This is consistent with possible_ancestors.
pagcauses, possible_local_structures, and maximal_local_mag assume that cg has no selection bias, matching the assumptions in the source papers. Selection bias shows up as an undirected edge in a PAG, and all three functions throw an ArgumentError if cg has one:
julia> selection_pag = PAG(
undirected(:A, :B), undirected(:B, :C), undirected(:C, :D), undirected(:A, :D))
PAG with 4 nodes and 4 edges:
nodes: A, B, C, D
edges:
A --- B, B --- C, C --- D, A --- D
julia> pagcauses(selection_pag, :A, :B)
ERROR: ArgumentError: pagcauses (Wang, Tao, Qin & Zhou 2025) assumes no selection variables (no undirected edges)One could enumerate all MAGs with enumerate_mags and apply backdoor_set to each one. However, the number of MAGs in a Markov equivalence class grows as $O(3^{(d^2-d)/2})$ with the number of nodes d.
pagcauses avoids constructing these MAGs. Instead, it uses graphical conditions to check whether a candidate adjustment set can be valid for a MAG compatible with the PAG. This reduces the complexity to $O(5^d d^6)$ (Wang et al. (2025), Section 3.4).
Depending on your graph size, the brute force approach could of course still be faster in practice.
Local structures
pagcauses builds on a smaller graphical primitive: possible_local_structures. For a single node x, this function enumerates which subsets of its circle-marked neighbors could resolve to x <-> v, with the remaining circle-marked edges resolving to x --> v, in some consistent MAG (Wang et al., 2023):
possible_local_structures(pag, :A)2-element Vector{Vector{Symbol}}:
[]
[:B]A has one circle-marked neighbor, B, so there are two local structures: the empty set, corresponding to A --> B, and {B}, corresponding to A <-> B. Pairing a local structure with maximal_local_mag resolves the edges around A implied by this local background knowledge:
maximal_local_mag(pag, :A, Symbol[])UNKNOWN with 4 nodes and 3 edges:
nodes: A, B, C, D
edges:
A --> B, C o-> B, B --> D
maximal_local_mag(pag, :A, :B)UNKNOWN with 4 nodes and 3 edges:
nodes: A, B, C, D
edges:
A <-> B, C o-> B, B --> D
These correspond to the two cases considered by pagcauses(pag, :A, :D): the first gives {} as a valid adjustment set for A and D, while the second gives {B}. The result is returned as UNKNOWN rather than MAG, since local background knowledge about one node need not resolve every circle in the graph.
General identification
The methods above are each sufficient, not necessary: an effect can be identifiable even when no adjustment set exists. idp and cidp implement the IDP/CIDP algorithms of Jaber et al. (2022), the PAG analogues of id and idc (see General identification): they decide identifiability of P(y | do(x)) and P(y | do(x), z) from a PAG and, when identifiable, return the estimand.
Consider a MAG where A confounds X, B --> X, and X causes Y, and view the Markov equivalence class:
mag3 = MAG("B --> X, A <-> X, A --> Y, X --> Y")
pag3 = mag_to_pag(mag3)
plot(pag3)We can then check whether the effect of X on Y is identifiable with idp:
idp(pag3, :X, :Y)\[\sum_{A, B} \left(\frac{P(A, B, X, Y)}{P(X \mid A, B)}\right)\]
The effect is identifiable (through the witnessed backdoor structure), and conditioning on the confounder A also identifies the conditional effect:
cidp(pag3, :X, :Y; given = :A)\[\frac{\sum_{B} \left(\frac{P(A, B, X, Y)}{P(X \mid A, B)}\right)}{P(A)}\]