--- name: 20261105-andrey-0-14-0 description: "What interventional data are, why an experiment shows which way an effect runs, and the methods Andrey has for them: GIES, IGSP, GnIES, UT-IGSP, and ICP." meta: type: post blogpost: true date: 2026-11-05 author: Shu Wan tags: [Release, Interventional data] version: 0.14.0 cover: dominoes --- # Interventional data ## TL;DR - **What it is:** data from experiments. Some rows are observed as they come; in others, a variable was changed from outside by an intervention. Where observation leaves the direction of an edge open, an experiment on either end can settle it. - **What Andrey has:** GIES and IGSP when you know what each experiment changed, GnIES and UT-IGSP when you do not, CD-NOD for data from several domains, and ICP for the causes of one variable, all taking one `env` and `targets`, and a simulator for interventions. - **As accurate as the authors' code:** GnIES is worse than the authors' package in none of 21 settings and about 34 times faster; UT-IGSP and IGSP are worse than the authors' library in none of 112 and 114 comparisons; ICP accepts the same sets as the authors' R package on every paired fit with two environments, 41 to 760 times faster. --- An experiment answers a question that observation cannot: what happens to the rest of a system when one part of it is changed. Interventional data mix observed rows with rows from such experiments, and they show which way an effect runs. ## Observational data A [structural causal model](20261103-andrey-0-13-0.md#structural-causal-models) writes each variable as a function of its parents and a noise term of its own. From observational data alone, graphs that differ in the direction of some edges imply the same independences, so PC and GES return those edges undirected. A functional causal model breaks the tie by assuming the shape of each effect. Interventional data break it with an experiment. ## Interventions and the do operator An intervention changes the mechanism of one variable, its target, from outside, and leaves every other variable's mechanism as it was. Pearl writes a hard intervention with the do operator: $\mathrm{do}(x_j = a)$ replaces the equation of $x_j$ with $x_j = a$, so $x_j$ no longer listens to its parents, and the graph loses every arrow into $x_j$ (Pearl, 2009). A soft intervention changes the mechanism and keeps the parents: it shifts the mean of the noise, changes its spread, or changes the weights of the parents. Intervening is not conditioning. $P(y \mid x = a)$ is the distribution of $y$ in the rows where $x$ happens to equal $a$, and it carries every path between $x$ and $y$, including a common cause. $P(y \mid \mathrm{do}(x = a))$ is the distribution of $y$ under the intervention, and it changes only through what $x$ causes. Observational data give the first; an experiment gives the second. Data from several experiments come in environments: the rows of one environment share one set of interventions, and its targets are the variables they change. An observational environment has no targets. Take the smallest graph, $x \to y$, with $y = 0.8\,x + e$ and $x$ and $e$ both Gaussian. The figure draws the observed rows (a), and the rows of two experiments: $\mathrm{do}(x)$ (b) and $\mathrm{do}(y)$ (c), each drawing its target from outside, centered at 2.5. Under (b) and (c), a two-sample test asks whether the other variable changed. ```{raw} html :file: 0.14.0/intervention.html ``` In (a), the data are a symmetric Gaussian cloud, and the models $x \to y$ and $y \to x$ fit them alike. No method can tell the direction from these rows. In (b), $\mathrm{do}(x)$ moves the cloud along the line: $y$ follows $x$, and the test finds that $y$ changed. In (c), $\mathrm{do}(y)$ moves the cloud up, but $x$ keeps its place and spread, and the test finds that $x$ did not change. An intervention changes what lies downstream of its target and leaves the rest alone, so an experiment on either end of the edge orients it. With experiments on several variables, the data identify the graph up to a smaller class, its interventional equivalence class (Hauser and Bühlmann, 2012): every edge at a target is directed, and so is every edge those directions imply. ## Interventional data in Andrey Observational data are one table, a row per sample and a column per variable. Interventional data add two things: the environment of each row, as one more column or an array, and the targets of each environment. Here are two rows of each of three environments: observed, $\mathrm{do}(x_1 = 2)$, and the noise of $x_3$ tripled. ```{literalinclude} 0.14.0/data.py :language: python :caption: data.py ``` ```{literalinclude} 0.14.0/data-output.txt :language: text :caption: data.py ``` Under $\mathrm{do}(x_1 = 2)$, $x_1$ is 2.00 in every row, and GIES recovers the true graph. `data` is a pandas DataFrame or a NumPy array, or the environments' tables apart. `env` and `targets` take these forms: | Argument | Form | Meaning | |---|---|---| | `env` | an array, one label per row | each row's environment; the labels are strings or numbers, not both | | `env` | a column name, such as `"env"` | the DataFrame column that holds the labels; it is not a variable | | `targets` | a mapping `{label: [variables]}` | each environment's targets: `[]` for an observational environment, a list for known targets, `None` for unknown ones | | `targets` | a column name | a DataFrame column whose cells give each row's targets, empty for an observational row | | `targets` | a 0/1 array of shape (rows, variables) | 1 where the row's environment intervenes on the variable | | `data` | a list or dict of tables, one per environment | each environment's rows apart, labeled by position in a list and by key in a dict; `env` is left out, and `targets` maps those labels | Variables are named by column label or 0-based position. With `targets` as a column or an array, `env` can be left out: the rows with the same targets form one environment. GIES and IGSP need every environment's targets; GnIES and UT-IGSP take `None` for the ones you do not know and learn them; ICP needs no targets but uses them, leaving out the environments that intervened on its response; CD-NOD takes `env` alone. GIES, IGSP, GnIES, UT-IGSP, CD-NOD, and ICP also take the tables apart: ```python andrey.gies([observed, knockout], targets={0: [], 1: ["x"]}) ``` ## Targets known and unknown An experiment does not always say what it changed. A drug can act on more than the protein it was made for, and a gene knockout can have side effects. Each method in Andrey assumes its own kind of experiment and learns what it is not told: | Method | Data | Targets | Output | |---|---|---|---| | GIES | an observational environment and experiments | known | interventional essential graph | | IGSP | an observational environment and experiments | known | interventional essential graph | | GnIES | environments that differ in the spread of some variables' noise | learned | interventional essential graph and the targets | | UT-IGSP | an observational environment and experiments | known in part, the rest learned | interventional essential graph and each experiment's targets | | CD-NOD | domains whose mechanisms differ | not needed | CPDAG | | ICP | environments that leave one variable's mechanism alone | not needed | that variable's causal parents | **GIES** (Hauser and Bühlmann, 2012) runs GES's search over interventional equivalence classes, scoring the rows of each environment with the targets left out of their own fit. **IGSP** (Wang, Solus, Yang, and Uhler, 2017) searches variable orders for the sparsest graph the tests allow, and uses each experiment to decide which directions it supports. **GnIES** (Gamella, Taeb, Heinze-Deml, and Bühlmann, 2022) learns the targets with the graph, when every experiment changes the spread of its targets' noise. It fits one set of targets for all environments: the variables whose noise differs in some environment. **UT-IGSP** (Squires, Wang, and Uhler, 2020) takes the targets you know and learns the rest, for each experiment, from tests of which variables' mechanisms changed. It accepts any change of mechanism: a shift, a new spread, new weights, or a hard intervention. **CD-NOD** (Huang et al., 2020) reads the environment as one more variable that can cause the others, and needs no targets. **ICP** (Peters, Bühlmann, and Meinshausen, 2016) asks a narrower question: which variables are the causes of one response. It accepts the sets of variables whose regression of the response stays the same in every environment, and returns the variables in all of them. ## A simulated example We simulate ten variables on a known graph: 1,000 observed rows, and 1,000 rows from each of three experiments that triple the noise of one variable. We ask GES for the graph from the observed rows, GIES with the targets given, and GnIES and UT-IGSP without them. ```{literalinclude} 0.14.0/example.py :language: python :caption: example.py ``` ```{literalinclude} 0.14.0/example-output.txt :language: text :caption: example.py ``` GES finds the right connections but leaves two of them undirected: the observed rows cannot orient them. GIES, told the targets, recovers the true graph. GnIES and UT-IGSP recover it too, and name the three targets they were not told. ## Simulating interventions `andrey.data` draws interventional data. `hard(value)` is $\mathrm{do}(x_j = \text{value})$: it fixes the variable and cuts it from its parents; `soft(shift=..., weight_scale=..., noise_scale=...)` changes its mechanism and keeps its parents. `SCM.do` is the do operator: it applies them to a model and returns the intervened model. `envs=` on `SCM.sample` draws several environments, each with its own interventions, into one dataset whose `env` and `targets` the methods take directly: ```python import andrey.data as data from andrey.data.interventions import hard, soft scm = data.SCM(graph=data.graphs.erdos_renyi(d=8, avg_degree=2), functional=data.functional.linear(), noise=data.noise.gaussian()) ds = scm.sample(n=[500, 200, 200], seed=0, envs=[{}, {3: hard(2.0)}, {5: soft(shift=1.0)}]) ds.targets # {0: [], 1: [3], 2: [5]} ``` ## Checked against the authors' code We compared each method with its authors' code on simulated data from the experiments it assumes. - GnIES: the authors' gnies package. Worse in none of 21 settings, better in 2 on fresh data, and about 34 times faster. - UT-IGSP: the authors' library. Worse in none of 112 comparisons. It is 1.7 times slower per fit at 20 variables; at 100 variables it takes 15 s against the library's 17 s. - IGSP: the authors' library, and a copy of it that follows the paper's definition where a setting has two or more targets. Worse in none of 114 comparisons at Andrey's defaults, and in none of 684 at three fixed levels. It is slower in 84 of 87 settings, up to 8.0 times per fit, at most about 2 s at these sizes. - ICP: the authors' R package. The same accepted sets on all 5,220 paired fits with two environments, and every other difference traced to a documented choice. 41 to 760 times faster. The [release card](../docs/benchmarks/0.14.0.md) has every comparison. ## Limits **The experiments must fit the method.** GnIES assumes linear effects, Gaussian noise, and experiments that change only the spread of their targets' noise; UT-IGSP and IGSP assume that the observational environment is among the data. **Few rows per experiment.** With 10 rows per environment, GnIES beats the empty graph in some settings and not in others, and so does the authors' package. **Dense graphs.** At 50 variables with mean degree 5, neither UT-IGSP nor the authors' library makes fewer errors than the empty graph. **Interventions on ICP's response.** ICP's guarantee needs environments that leave its response's mechanism alone. Where an experiment hits the response, a set holding one of its children can look invariant, and the share of fits that name a wrong parent rises to 0.18, in the authors' package too. Pass the targets you know, and ICP leaves those environments out. **Experiments that change nothing.** An intervention can leave a variable without causes as it was, for example by drawing its noise again from the same distribution; such an experiment says nothing about the arcs out of it. IGSP still marks those arcs and reverses about one in five, as the authors' library does; GIES reverses almost none. ## Try it ```{code-block} shell :class: window pip install -U andrey-core ``` The [score-based search example](../docs/examples/score_search.ipynb) runs GnIES, and the [permutation-based search example](../docs/examples/permutation_based.ipynb) runs IGSP and UT-IGSP. ## Help us build it Andrey is in alpha. The API may change between releases. When you try it on your own data, [tell us what breaks](https://github.com/Abel-ai-lab/andrey/issues/new) or which method you need. We also welcome pull requests. The [contributing guide](https://github.com/Abel-ai-lab/andrey/blob/main/CONTRIBUTING.md) explains how you can set up the development environment. ## References - Gamella, J. L., Taeb, A., Heinze-Deml, C., and Bühlmann, P. (2022). Characterization and greedy learning of Gaussian structural causal models under unknown interventions. [arXiv:2211.14897](https://arxiv.org/abs/2211.14897). - Hauser, A., and Bühlmann, P. (2012). Characterization and greedy learning of interventional Markov equivalence classes of directed acyclic graphs. *JMLR* 13, 2409-2464. [jmlr.org](https://jmlr.org/papers/v13/hauser12a.html). - Huang, B., Zhang, K., Zhang, J., Ramsey, J., Sanchez-Romero, R., Glymour, C., and Schölkopf, B. (2020). Causal discovery from heterogeneous/nonstationary data. *JMLR* 21(89), 1-53. [jmlr.org](https://jmlr.org/papers/v21/19-232.html). - Pearl, J. (2009). *Causality: Models, Reasoning, and Inference*, 2nd ed. Cambridge University Press. [doi:10.1017/CBO9780511803161](https://doi.org/10.1017/CBO9780511803161). - Peters, J., Bühlmann, P., and Meinshausen, N. (2016). Causal inference by using invariant prediction: identification and confidence intervals. *Journal of the Royal Statistical Society Series B* 78(5), 947-1012. [doi:10.1111/rssb.12167](https://doi.org/10.1111/rssb.12167). - Squires, C., Wang, Y., and Uhler, C. (2020). Permutation-based causal structure learning with unknown intervention targets. *UAI*, PMLR 124, 1039-1048. [proceedings.mlr.press](https://proceedings.mlr.press/v124/squires20a.html). - Wang, Y., Solus, L., Yang, K. D., and Uhler, C. (2017). Permutation-based causal inference algorithms with interventions. *NeurIPS* 30. [proceedings.neurips.cc](https://proceedings.neurips.cc/paper/2017/hash/275d7fb2fd45098ad5c3ece2ed4a2824-Abstract.html). --- - [Getting started](../docs/guides/getting-started.md) - [Release cards](../docs/benchmarks.md) - [Changelog](../docs/changelog.md) - [GitHub](https://github.com/Abel-ai-lab/andrey)