API reference

andrey.icp#

andrey.icp(data, env=None, *, target=None, targets=None, alpha=0.05, invariance_test='approximate', max_parents=None)[source]#

Learn one variable’s causal parents from data in several environments with ICP.

Invariant causal prediction [1] tests, for every set of the other variables, whether the regression of the target on that set stays the same in every environment. The target’s causal parents pass that test whenever the environments leave the target’s own mechanism alone, so the variables in every accepted set are causal parents with high probability. The result is a DAG with an edge into the target from each such variable.

Parameters:
  • data (ArrayLike) – Observed data, one row per sample. Coerced to float64. A DataFrame’s column names become the structure’s labels. A column that env or targets names is not a variable: it leaves the data before the fit, and target, targets, and the result’s positions count the other columns. A list of DataFrames or 2-D arrays gives each environment’s rows apart, labeled by position 0, 1, ..., and a mapping labels each by its key: the rows are stacked in that order, env stays None, and targets, when given, maps those labels to their variables.

  • env (ArrayLike | str | None) – The environment of each row: one label per row, strings or numbers, not both, or the name of a column of the DataFrame data that holds them. Every distinct label is one environment, and at least two are needed. Which variables an environment changed, and how, does not matter, as long as it left the target’s mechanism alone. None takes the environments from data given as each environment’s rows apart, or else from targets given as a column or a 0/1 matrix: rows with the same targets are one environment.

  • target (str | int | None) – Required. The variable whose parents to find: a column label (a string) or a 0-based position (an integer). None raises ValueError.

  • targets (Mapping | ArrayLike | str | None) – The variables intervened on in each environment, as for gies(), by column label or 0-based position: a mapping {label: [variables]} from labels of env, with [] for an observational environment and None or a missing label for unknown targets; the name of a column whose cells give each row’s targets; or a 0/1 matrix. ICP reads only whether an environment intervened on target: the rows of such an environment are left out of the fit, and an environment whose targets are unknown is kept. None keeps every environment.

  • alpha (float) – The significance level of every set’s invariance test, in (0, 1): the estimated parents are a subset of the true parents with probability at least 1 - alpha. Smaller values accept more sets and give fewer parents.

  • invariance_test (str) – The invariance test of each set. "approximate" fits one regression on every row and compares each environment’s residuals with the other rows’ by Welch’s t-test of their means and the F-test of their variances. "exact" fits the regression on the other rows, predicts each environment’s rows, and tests the prediction errors with Chow’s F-test, exact for Gaussian noise. See Notes.

  • max_parents (int | None) – A cap on the target’s parents, at least 0: only sets of at most max_parents variables are tested, so no estimate gives the target more than max_parents parents. The guarantee then holds when the target has at most max_parents parents. None tests every set, 2^(n_variables - 1) of them.

Returns:

Wraps a dag GraphStructure over every column, with an edge x -> target from each estimated parent x and no other edge; an absent edge says nothing about the pair. metadata["algorithm"] is "icp"; metadata["target"] names the target and metadata["parents"] the estimated parents, the variables in every accepted set, by column label or 0-based position. metadata["p_values"] gives one value per column, None at the target: the largest p-value of the tested sets without the column, so a variable is a parent exactly when its value is at most alpha, and every value is 1 when no set is accepted. metadata["confidence_intervals"] gives [low, high] per column, None at the target, for its coefficient in the target’s equation; None when no set is accepted. metadata["accepted_sets"] lists the accepted sets, by size then by position; metadata["best_p_value"] is the largest p-value of any tested set; metadata["model_rejected"] is True when no set is accepted; and metadata["environments_left_out"] lists the labels of the environments whose targets hold target.

Return type:

StructureOutput

Raises:
  • TypeError – If target is neither a column label nor a position.

  • ValueError – If an argument has the wrong type, lies outside its range, or is not one of its choices; the message names the method, the argument, what it accepts, and the value. Also if target is None or names a column data lacks; if data is not a 2-D numeric matrix with at least two rows, no NaN or inf, and no constant column; if a column repeats another up to scale and shift or, with more rows than columns, is a linear function of other columns, one message naming every group of copies; if data holds each environment’s rows apart with env or with targets other than a mapping, or its tables differ in kind or columns, or one has no rows; if env does not hold one label per row, misses a label, mixes strings and numbers, or names a column data lacks; if targets names a variable or an environment data or env lacks, or gives two rows of one environment different targets; if fewer than two environments remain once those that intervened on target are left out, as with one table and neither env nor targets, or one has fewer than 3 rows; if a column is constant on the rows that remain; or if a regression has as many coefficients as rows: under "exact", on the rows outside each environment, where a variable constant there also raises.

Warns:

DataTypeWarning – When a column holds at most 10 distinct whole numbers and so may hold category codes, which the fit reads as numbers. Each message warns once per process.

See also

andrey.gies

The whole graph from environments whose intervened variables are known.

andrey.cdnod

The whole graph from environments whose changes are unknown.

Notes

When to use. ICP suits continuous data pooled from several environments, such as observation and experiments, when you want the causal parents of one target with an error guarantee. Its claim [1]: when the target is a linear function of its parents plus noise whose distribution is the same in every environment, so that the environments change the other variables in any way but leave the target’s mechanism alone, the estimated parents are a subset of the true parents with probability at least 1 - alpha, without faithfulness or any condition on how strong the changes are; the exact test needs Gaussian noise. Weak or few changes give fewer parents, not wrong ones. It tests every set of the other variables, so it suits about 15 of them, or more with max_parents. For the whole graph from interventional data, use gies() when every environment’s intervened variables are known and cdnod() when they are not.

How it works. For every set S of the other variables, of at most max_parents of them, the null hypothesis is that the target’s regression on S, with an intercept, has the same coefficients and the same residual distribution in every environment [1]. A set is accepted when its p-value exceeds alpha, and the estimate is the intersection of the accepted sets. When no set is accepted, the model is rejected and the estimate is empty: an intervention on the target, a hidden common cause of the target and a variable, or a nonlinear effect can leave no set invariant.

Tests. Under "approximate" (the paper’s Method II), each environment’s residuals of the regression on every row are compared with the other rows’ residuals by Welch’s t-test and the two-sided F-test of the variances; the environment’s p-value is twice the smaller of the two. Under "exact" (Method I), the regression on the other rows predicts the environment’s rows, and Chow’s statistic [2] is (RSS_all - RSS_out) / n_e over RSS_out / (n_out - |S| - 1), which is F(n_e, n_out - |S| - 1) under the null with Gaussian noise, where RSS_all and RSS_out are the residual sums of squares of the regressions on every row and on the n_out rows outside the environment’s n_e. A set’s p-value is the smallest environment’s times the number of environments, the Bonferroni correction, capped at 1; with two environments the approximate test’s two comparisons are one test, so its factor is 1. Every set’s regression reads the environments’ Gram matrices, computed once, so a set costs the same at any number of rows and neither test subsamples.

Intervals. For an accepted set of s variables, each coefficient’s interval is the pooled estimate plus or minus t_{1 - alpha/(2s), n - s - 1} standard errors, and a variable outside the set has the interval [0, 0]; metadata["confidence_intervals"] is the union over the accepted sets. Together the intervals hold every coefficient of the target’s equation with probability at least 1 - 2 alpha [1].

Limitations. Measured on simulated linear Gaussian data drawn as in the paper’s simulation study [1]: two environments of 100 to 500 rows each, 5 to 12 variables, and noise interventions on one or more of them, with 200 datasets per scenario. When those interventions could also hit the target and targets did not name them, the estimate held a variable outside the true parents in 18% of the fits under "approximate" and 11% under "exact", over the 55 scenarios that hit it, against under 1% in the scenarios that left it alone. An intervention that changes the target’s noise makes the regression on its parents differ across environments, so their set is rejected, while a set with a child of the target, whose own equation does not change, stays close to invariant and is accepted: 88% and 87% of those fits held a child. targets leaves such environments out. A hidden common cause of the target and one other variable raised the share to 1.2% and 1.8% over 20 scenarios, and to 14.5% in one of them under "exact". A tanh effect of one parent on the target kept it at 1.0% at most, but the estimate equaled the true parents in under 0.5% of the fits. With max_parents=3 on 40 variables, it found 0.2% of the true parents under "exact". Without max_parents it tests 2^(n_variables - 1) sets: the median fit of the approximate test took 0.24 s on one core at 15 variables and 1,000 rows. The guarantee does not cover noise that is not Gaussian under "exact". The numbers are a reference, not a hard limit: they change with the data generator, the settings, and the machine.

References

Examples

>>> import warnings
>>> import numpy as np
>>> import pandas as pd
>>> from andrey import icp
>>> rng = np.random.default_rng(0)
>>> env = np.repeat(["observed", "shifted"], 500)
>>> x = rng.standard_normal(1000) + 2.0 * (env == "shifted")  # the shift moves x only
>>> y = x + rng.standard_normal(1000)
>>> z = y + rng.standard_normal(1000)
>>> data = pd.DataFrame({"x": x, "y": y, "z": z})
>>> with warnings.catch_warnings():
...     warnings.simplefilter("ignore")
...     out = icp(data, env, target="y")
>>> out.metadata["parents"], out.structure.oriented_edges()
(['x'], [('x', 'y', 'directed')])