discovery.TBFPC
Target-first Bayes Factor PC (TBF-PC) causal-discovery algorithm.
Usage
discovery.TBFPC(
target,
*,
target_edge_rule="any",
bf_thresh=1.0,
max_conditioning_set_size=3,
forbidden_edges=None,
required_edges=None
)A target-oriented variant of the Peter–Clark (PC) algorithm that uses Bayes factors (via a ΔBIC approximation) as the conditional-independence test.
For each conditional-independence test of the form
.. math::
H_0 : Y \perp X \mid S
\quad \text{vs.} \quad
H_1 : Y \not\!\perp X \mid S
two linear models are compared:
.. math::
M_0 : Y \sim S
\\
M_1 : Y \sim S + X
where :math:S is a conditioning set of variables.
The Bayesian Information Criterion (BIC) is defined as
.. math::
\mathrm{BIC}(M) = n \log\!\left(\frac{\mathrm{RSS}}{n}\right)
+ k \log(n),
with residual sum of squares :math:\mathrm{RSS}, sample size :math:n, and number of parameters :math:k. The Bayes factor is approximated by
.. math::
\log \mathrm{BF}_{10} \approx -\tfrac{1}{2}
\left[ \mathrm{BIC}(M_1) - \mathrm{BIC}(M_0) \right].
Independence is declared when :math:\mathrm{BF}_{10} < \tau, where :math:\tau is set via bf_thresh.
Target Edge Rules
Different rules govern how driver → target edges are retained:
"any": keep :math:X \to Yunless any conditioning set renders :math:X \perp Y \mid S."conservative": keep :math:X \to Yif at least one conditioning set shows dependence."fullS": test only with the full set of other drivers as :math:S.
Parameters
target: str-
Name of the outcome variable used to orient the search. Must be present in the data passed to fit().
target_edge_rule: ("any", "conservative", "fullS") = "any"-
Rule controlling which driver → target edges are retained.
bf_thresh: float = 1.0-
Positive Bayes-factor threshold for the conditional-independence tests.
max_conditioning_set_size: int = 3-
Largest conditioning set
|S|searched in the"any"and"conservative"target phases and in the driver-skeleton phase (default 3). This bounds the combinatorial cost of the search; a pair that is only separable by a larger conditioning set will not be separated, so its edge is retained. The"fullS"target rule ignores this and always conditions on the full set of other drivers. It also controls the separating sets used by the v-structure orientation, so an overly small value can leave colliders undetected. forbidden_edges: Sequence[tuple[str, str]] | None = None-
Node pairs that must never be connected in the learned graph (background knowledge / orientation constraints). Symmetric: an entry
(u, v)also forbidsv—u. required_edges: Sequence[tuple[str, str]] | None = None-
Directed
(u, v)pairs that must appear asu -> vin the learned graph.
Examples
Basic usage with the full conditioning set::
import numpy as np, pandas as pd
from pathmc import TBFPC
rng = np.random.default_rng(7)
n = 2000
C = rng.gamma(2, 1, n)
A = 0.7 * C + rng.gamma(2, 1, n)
D = 0.5 * C + rng.gamma(2, 1, n)
B = 0.8 * A + rng.gamma(2, 1, n)
Y = 0.9 * B + 0.6 * D + 0.7 * C + rng.gamma(2, 1, n)
df = pd.DataFrame({"A": A, "B": B, "C": C, "D": D, "Y": Y})
df = (df - df.mean()) / df.std() # recommended scaling
model = TBFPC(target="Y", target_edge_rule="fullS")
model.fit(df, drivers=["A", "B", "C", "D"])
print(model.to_digraph())
Background knowledge — forbid an edge, force another::
model = TBFPC(
target="Y",
forbidden_edges=[("A", "C")],
required_edges=[("B", "Y")],
)
model.fit(df, drivers=["A", "B", "C", "D"])
References
- Spirtes, Glymour, Scheines (2000). Causation, Prediction, and Search. MIT Press. [PC algorithm]
- Spirtes & Glymour (1991). “An Algorithm for Fast Recovery of Sparse Causal Graphs.”
- Kass, R. & Raftery, A. (1995). “Bayes Factors.”
Methods
| Name | Description |
|---|---|
| fit() | Fit the TBF-PC procedure to df. |
| get_all_cdags_from_cpdag() | Enumerate the member DAGs of the CPDAG’s Markov equivalence class. |
| get_directed_edges() | Return the directed edges learned by the algorithm. |
| get_test_results() |
Return ΔBIC diagnostics for the unordered pair {x, y}.
|
| get_undirected_edges() | Return the undirected edges remaining after orientation. |
| summary() | Render a text summary of the learned graph and the CI-test count. |
| to_digraph() | Return the learned CPDAG encoded in DOT format. |
fit()
Fit the TBF-PC procedure to df.
Usage
fit(df, drivers)Parameters
df: pandas.DataFrame-
Dataset containing the target column and every candidate driver. Standardizing the columns (zero mean, unit variance) is recommended so the ΔBIC test is well scaled.
drivers: Sequence[str]- Column names to treat as potential drivers of the target.
Returns
TBFPC-
The fitted instance (
self) with internal adjacency structures populated.
Examples
::
model = TBFPC(target="Y", target_edge_rule="fullS")
model.fit(df, drivers=["A", "B", "C"])
get_all_cdags_from_cpdag()
Enumerate the member DAGs of the CPDAG’s Markov equivalence class.
Usage
get_all_cdags_from_cpdag(dot_cpdag=None)This is what makes the discovery output a set of equally plausible graphs rather than one arbitrary DAG: every undirected edge is oriented both ways, then an orientation is kept only if it (a) stays acyclic and (b) introduces no new v-structure (unshielded collider) beyond those already compelled by the CPDAG’s directed edges. Those two filters together are exactly the membership test for the Markov equivalence class, so every returned DAG satisfies pathmc.same_markov_equivalence_class() against the input CPDAG. Downstream model averaging fits each returned DAG and pools the effect posteriors.
Because fit() already orients the compelled edges (v-structures + Meek rules), a CPDAG with no reversible edges collapses to a single DAG, while genuinely reversible structure (chains/forks, cliques) yields several members.
Parameters
dot_cpdag: str | None = None-
If provided, parse the CPDAG from this DOT string (undirected edges encoded as
[style=dashed, dir=none]). IfNone, use this model’s current CPDAG from get_directed_edges() and get_undirected_edges().
Returns
list[str]- DOT strings, each a fully oriented DAG (no dashed edges) and a member of the same Markov equivalence class as the CPDAG.
get_directed_edges()
Return the directed edges learned by the algorithm.
Usage
get_directed_edges()Returns
list[tuple[str, str]]-
Sorted list of
(u, v)oriented edges.
get_test_results()
Return ΔBIC diagnostics for the unordered pair {x, y}.
Usage
get_test_results(x, y)Parameters
x: str-
The two variables in the pair (order does not matter).
y: str- The two variables in the pair (order does not matter).
Returns
list[TestResult]-
One entry per conditioning set tested, each holding
bic0,bic1,delta_bic,logBF10,BF10,independent, and theconditioning_setused.
get_undirected_edges()
Return the undirected edges remaining after orientation.
Usage
get_undirected_edges()Returns
list[tuple[str, str]]-
Sorted list of
(u, v)pairs for unresolved adjacencies.
summary()
Render a text summary of the learned graph and the CI-test count.
Usage
summary()Returns
str- Multiline string listing directed edges, undirected edges, and the number of conditional-independence tests executed.
to_digraph()
Return the learned CPDAG encoded in DOT format.
Usage
to_digraph()Directed edges render as u -> v; undirected (unoriented) edges as u -> v [style=dashed, dir=none]; required edges are highlighted, and the target node is filled.
Returns
str-
DOT string compatible with Graphviz rendering utilities and with
pathmc.same_markov_equivalence_class().