API reference

andrey.direct_lingam#

andrey.direct_lingam(data, *, random_state=None, measure='pwling', knowledge=None)[source]#

Learn a fully oriented DAG with DirectLiNGAM (linear, non-Gaussian noise).

DirectLiNGAM uses non-Gaussianity - noise that is not Gaussian - to determine a complete causal order, then estimates linear edge weights. It returns a single fully oriented DAG (directed acyclic graph), with edge weights and a total ordering. PC and GES instead return an equivalence class.

Parameters:
  • data (ArrayLike) – Observed data, one row per sample. Coerced to float64. A DataFrame’s column names become the structure’s labels.

  • random_state (int | None) – Ignored. DirectLiNGAM with measure="pwling" is deterministic; the argument exists so that direct_lingam accepts random_state as ica_lingam does.

  • measure (str) – How the order search picks the most exogenous remaining variable at each step. Only "pwling", the pairwise likelihood-ratio measure [2], is supported.

  • knowledge (Knowledge | None) – Background knowledge, a Knowledge: required and forbidden edges, the pairs allowed to be adjacent, and temporal tiers, all honored. The causal order puts the cause of each required edge, and each earlier tier, first; the regression on that order drops the forbidden parents and does not penalize the required ones. A variable with a required parent takes least-squares weights on the parents it keeps; the others keep the adaptive Lasso’s. Every required edge is in the DAG, even where its least-squares weight is 0.

Returns:

Wraps a dag GraphStructure; metadata["algorithm"] is "DirectLiNGAM". ordering is the causal order as a tuple of column indices, causes first. weighted_adjacency[i, j] is the coefficient of edge i -> j, estimated by adaptive Lasso, or by least squares for a copy (see Notes) and for a variable with a required parent; the DAG has an edge where the coefficient is nonzero and at every required edge.

Return type:

StructureOutput

Raises:
  • NotImplementedError – If measure is not "pwling".

  • TypeError – If knowledge is neither None nor a Knowledge.

  • ValueError – If data is not a usable 2-D matrix: not numeric, holding NaN or inf, or with a constant column, named in the message. Also if it has fewer than n_variables + 1 rows, the fewest with which the pruning step can estimate the edge weights. Also if knowledge names a column data lacks or contradicts itself, as Knowledge lists.

See also

andrey.pc, andrey.ges, which

andrey.fci

Allows unmeasured common causes and selection bias.

Notes

When to use it. DirectLiNGAM suits continuous data with linear effects, independent non-Gaussian noise, and no unmeasured common causes [1]. These assumptions allow the causal order to be identified. The result includes a DAG with every edge oriented, the edge weights, and the order.

When not to. With Gaussian noise, the order cannot be identified, so the returned order is unreliable. Use pc() or ges(), which return the equivalence class. When unmeasured common causes are plausible, use fci().

Cost. Each step compares the remaining variables in pairs, so runtime grows quickly with the number of variables. With the torch extra and a CUDA GPU, entropy computations move to the GPU once they are large enough.

Ties. Two candidates that are interchangeable in the sample, such as two binary columns with the same number of ones, have equal scores up to rounding, which differs between CPUs. Each step treats candidates as tied when the square roots of their negated scores lie within 1e-10 of the best one, and picks among them by a rank computed from each column’s own data, as hc() does. The column-order guide says what the rank keeps under a new column order and on another machine.

Copies. Two columns are copies when one leaves at most 1e-12 of the other’s variance unexplained, as for a column and a rescaled or rank-reversed copy of it. Each step scores one column per group of copies, the one that ranks first by the same rank, and when it is picked the other copies follow it directly in the order. Each of them gets a single edge from it, weighted by the scale between the two, and no edges to later columns, which take their edges from the column it copies.

Limitations. Measured on simulated data from scale-free graphs with linear effects and uniform noise, with ten rows per variable. From 20 to 200 variables its median errors (SHD) stayed below the empty graph’s, 13 against 199 at 200, and the median fit took 0.19 s at 20 variables and 545 s at 200, on 16 CPU cores. In a separate run on shared nodes, a CPU fit at 300 variables did not finish its warm-up and timed fit within 3,600 s; with an NVIDIA A100 GPU, a fit took 528 s at 300 variables and 1,410 s at 400.

References

Examples

>>> import numpy as np
>>> from andrey import direct_lingam
>>> rng = np.random.default_rng(0)
>>> x = rng.uniform(-1, 1, 500)
>>> y = 0.8 * x + rng.uniform(-1, 1, 500)
>>> out = direct_lingam(np.column_stack([x, y]))
>>> out.structure.kind, out.ordering
('dag', (0, 1))