API reference

andrey.Knowledge#

class andrey.Knowledge(*, required=None, forbidden=None, allowed=None, tiers=None)[source]#

Background knowledge a search honors: required and forbidden edges, the pairs that may be adjacent, and tiers that order the variables.

A node is named by a column label, a string (a DataFrame’s column name as text, or the data file’s header on the command line), or by its 0-based position, an integer. Labels stay attached to their columns, so the same knowledge fits a reordered table; positions follow the column order. An integer is always a position: a column named 1 is named by the label "1". Each method’s knowledge parameter states the parts it honors and raises NotImplementedError for another.

Parameters:
  • required (Iterable[Sequence[str | int]] | None) – Directed edges cause -> effect the result must have.

  • forbidden (Iterable[Sequence[str | int]] | None) – Directed edges cause -> effect the result must not have. Forbidding a -> b forbids that edge only, not a directed path from a to b through other variables. Forbid both directions to make two variables non-adjacent.

  • allowed (Iterable[Sequence[str | int]] | None) – The only pairs that may be adjacent, in either direction. Listing a pair permits an edge but does not require one, and does not override forbidden or tiers. None allows every pair, and an empty iterable allows none. Listing few pairs narrows the search, which can make it faster.

  • tiers (Iterable[Iterable[str | int]] | None) – Temporal tiers, variables grouped in time order: no edge points from a variable of a later tier into one of an earlier tier. A variable in no tier is unconstrained; a variable may be in one tier only.

Raises:
  • TypeError – If required, forbidden, or allowed is not an iterable of (a, b) pairs, tiers is not an iterable of node iterables, or a node is neither a column label nor a 0-based position.

  • ValueError – If an edge is a self-loop or a node is in two tiers. A method raises ValueError when the knowledge names a column the data lacks, or contradicts itself: a required edge that is forbidden, directly, by the tiers, or by allowed, or required edges forming a cycle. boss, grasp, and direct_lingam also raise it when the required edges and the tiers admit no causal order, through a column in no tier. pc and cdnod raise it when the edges the knowledge orients form a directed cycle over the adjacencies the search kept; fci when a directed path of required edges leads from a to b while forbidden or tiers put an arrowhead at a on the edge between them.

Notes

PC, FCI, and CD-NOD start their adjacency search from the allowed pairs in place of the complete graph and never test a required pair, so no required adjacency is removed; the tests can remove any other allowed pair. GFCI’s GES phase searches only the allowed pairs, and its test phase starts from the adjacencies GES found. A pair the knowledge makes non-adjacent is still tested for a separating set, so colliders over it are oriented as usual; when the search finds none within max_depth, no rule reads its unshielded triples as colliders or non-colliders.

A method whose result is a CPDAG directs every edge the knowledge decides and then applies Meek’s rules R1-R4 [1], R4 being needed only with background knowledge. For a CPDAG and knowledge consistent with it, the result is the maximally oriented PDAG (MPDAG) [2], which stands for the DAGs of the equivalence class that satisfy the knowledge; it keeps the cpdag tag. On sample data the tests can disagree with the knowledge; the rules then skip the orientations that conflict, so the result can leave undirected an edge the MPDAG would direct.

In a PAG, the edges are those of an ancestral graph over the measured variables, where a -> b says that a is an ancestor of b, not necessarily its direct cause. A forbidden a -> b on an adjacent pair puts an arrowhead at a and can leave a circle at b, and a required a -> b is a -> b. The methods that search over causal orders (boss, grasp, direct_lingam) also keep each variable of a tier after those of earlier tiers; with every variable in a tier, this is the same as forbidding the edges.

References

Examples

>>> import numpy as np
>>> import pandas as pd
>>> from andrey import Knowledge, pc
>>> rng = np.random.default_rng(0)
>>> x = rng.standard_normal(500)
>>> y = x + 0.5 * rng.standard_normal(500)
>>> data = pd.DataFrame({"x": x, "y": y})
>>> pc(data, knowledge=Knowledge(tiers=[["x"], ["y"]])).structure.oriented_edges()
[('x', 'y', 'directed')]
classmethod from_skeleton(skeleton, *, required=None, forbidden=None, tiers=None)[source]#

Knowledge whose allowed pairs are the adjacent pairs of skeleton.

A later search then looks for edges only among those pairs. Edge directions, endpoint marks, and weights are ignored, and an allowed pair is not required to stay adjacent.

Parameters:
Returns:

The knowledge, with allowed set to every adjacent pair of skeleton.

Return type:

Knowledge

Raises:

ValueError – If skeleton is an array that is not a square matrix.

parts()[source]#

The parts this knowledge sets, in KNOWLEDGE_PARTS order.

Return type:

tuple[Literal['required', 'forbidden', 'allowed', 'tiers'], ...]