API reference

andrey.bottom_up_parce_lingam#

andrey.bottom_up_parce_lingam(data, *, alpha=0.1)[source]#

Learn a linear non-Gaussian causal order that tolerates hidden common causes.

BottomUpParceLiNGAM [1] finds sinks one at a time, from the last variable in the causal order up. Where a hidden common cause hides the order, it leaves the variables unordered rather than guessing.

Parameters:
  • data (ArrayLike) – Observations, one variable per column. A DataFrame’s column names become the structure’s labels.

  • alpha (float) – Significance level of the independence test that accepts a sink, Bonferroni-corrected across the remaining variables, in (0, 1).

Returns:

A GraphStructure of kind pag: i -> j for a direct cause and i o-o j between variables whose order a hidden common cause leaves unresolved. metadata["algorithm"] is "BottomUpParceLiNGAM"; metadata["causal_order"] gives the order, with the unordered variables nested in one list; weighted_adjacency holds the edge coefficients.

Return type:

StructureOutput

Raises:

ValueError – If data has fewer than 6 rows or no more rows than columns, or if alpha is not in (0, 1).

Notes

The search finds sinks from the bottom up and stops at the first variable it cannot separate, leaving it and every variable above it in one unordered block: the variables a hidden cause confounds, their observed ancestors, and any other confounded pairs. Candidates whose test statistics are equal after rounding are chosen by each column’s rank, as in hc(); the column-order guide says what the rank keeps. An o-o pair need not share a cause, and edges among the block’s variables stay unoriented. The edge weights come from an adaptive-Lasso regression of each variable on those before it, which needs more rows than columns.

Limitations. Suited to a few variables. Measured on linear data with uniform noise, one node in five hidden, and 2,000 rows: its median directed-edge errors were below the empty graph’s at 4 observed variables, and above it at every size from 8 to 24. From 16 variables it left none of the confounded pairs unordered. A fit took about 2 s at 4 variables and 96 s at 24, on two cores.

References

Examples

>>> import numpy as np
>>> import andrey
>>> rng = np.random.default_rng(0)
>>> u = lambda: rng.uniform(-1, 1, 2000)
>>> h = u()  # hidden: a common cause of x0 and x1
>>> x0, x1 = 1.5 * h + u(), 1.5 * h + u()
>>> data = np.column_stack([x0, x1, x1 + u(), x0 + u()])
>>> out = andrey.bottom_up_parce_lingam(data, alpha=0.01)
>>> out.metadata["causal_order"]
[[0, 1], 2, 3]