{ "cells": [ { "cell_type": "markdown", "id": "pairwise-0", "metadata": {}, "source": "# Pairwise direction (ANM / PNL)\n\n[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Abel-ai-lab/andrey/blob/main/examples/pairwise.ipynb) {download}`Download this notebook <../../../examples/pairwise.ipynb>`\n\nANM and PNL take two variables and test which one causes the other. Each fits a model in both\ndirections and tests whether the estimated noise is independent of the proposed cause, which holds\nonly in the causal direction. ANM fits an additive noise model, `y = f(x) + e`; PNL fits a\npost-nonlinear one, `y = f2(f1(x) + e)`. Both return a two-node DAG and both directions' p-values." }, { "cell_type": "markdown", "id": "pairwise-1", "metadata": {}, "source": [ "## Start\n", "\n", "Choose a CPU runtime and run this cell first. In Colab it installs `andrey-core==0.9.0` from PyPI;\n", "local kernels use the installed environment." ] }, { "cell_type": "code", "execution_count": 1, "id": "pairwise-2", "metadata": { "tags": [ "colab-setup" ] }, "outputs": [], "source": [ "import os\n", "import sys\n", "\n", "IN_COLAB = \"google.colab\" in sys.modules\n", "\n", "if IN_COLAB:\n", " os.environ[\"ANDREY_DEVICE\"] = \"cpu\"\n", " !pip install --quiet \"andrey-core[viz]==0.9.0\" pandas" ] }, { "cell_type": "code", "execution_count": 2, "id": "pairwise-3", "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "from IPython.display import display\n", "\n", "import andrey\n", "import andrey.viz\n", "from andrey.data import sample_scm" ] }, { "cell_type": "markdown", "id": "pairwise-4", "metadata": {}, "source": [ "## Data\n", "\n", "`sample_scm` draws a cause-effect pair: two variables and one edge, in a direction the seed draws.\n", "Here the effect is a post-nonlinear function of the cause with Laplace noise, 500 samples." ] }, { "cell_type": "code", "execution_count": 3, "id": "pairwise-5", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "500 observations of 2 variables, seed 3\n" ] }, { "data": { "text/html": [ "01" ], "text/plain": [ "'01'" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "seed = 3\n", "pair = sample_scm(\n", " graph=\"erdos_renyi\",\n", " functional=\"post_nonlinear\",\n", " noise=\"laplace\",\n", " d=2,\n", " n=500,\n", " seed=seed,\n", " scale=\"standardize\",\n", " density=1.0,\n", ")\n", "X, truth = pair.data, pair.graph\n", "print(f\"{X.shape[0]} observations of 2 variables, seed {seed}\")\n", "display(andrey.viz.draw(truth))" ] }, { "cell_type": "markdown", "id": "pairwise-6", "metadata": {}, "source": [ "## Fit ANM and PNL\n", "\n", "Column 0 is `x` and column 1 is `y`. `pval_forward` tests `x -> y` and `pval_backward` tests\n", "`y -> x`; by default the edge points in the direction with the larger p-value. Pass a significance\n", "level such as `alpha=0.05` to draw an edge only when exactly one direction's p-value is above it." ] }, { "cell_type": "code", "execution_count": 4, "id": "pairwise-7", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "ANM dag | 2 nodes | 1 edges (1 directed)\n", "\n", " 1 -> 0\n", "\n", " pval_forward: 9.834e-13\n", " pval_backward: 0.0021\n", " stat_forward: 5.487\n", " stat_backward: 2.195\n", "\n", "PNL dag | 2 nodes | 1 edges (1 directed)\n", "\n", " 1 -> 0\n", "\n", " pval_forward: 0.0001507\n", " pval_backward: 0.01781\n", " stat_forward: 2.71\n", " stat_backward: 1.811\n" ] } ], "source": [ "print(andrey.anm(X), end=\"\\n\\n\")\n", "print(andrey.pnl(X))" ] }, { "cell_type": "markdown", "id": "pairwise-8", "metadata": {}, "source": [ "## Compare over ten pairs\n", "\n", "Each method runs on the pairs its model describes: ANM on additive-noise pairs with Gaussian noise,\n", "and PNL on post-nonlinear pairs with Laplace noise. One pair can favor either answer, so the table\n", "covers ten seeds." ] }, { "cell_type": "code", "execution_count": 5, "id": "pairwise-9", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
ANMPNL
seed
0true directiontrue direction
1true directiontrue direction
2true directiontrue direction
3true directiontrue direction
4true directiontrue direction
5reversedtrue direction
6true directiontrue direction
7true directiontrue direction
8reversedtrue direction
9true directiontrue direction
\n", "
" ], "text/plain": [ " ANM PNL\n", "seed \n", "0 true direction true direction\n", "1 true direction true direction\n", "2 true direction true direction\n", "3 true direction true direction\n", "4 true direction true direction\n", "5 reversed true direction\n", "6 true direction true direction\n", "7 true direction true direction\n", "8 reversed true direction\n", "9 true direction true direction" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "models = {\n", " \"ANM\": (\"additive_noise\", \"gaussian\", lambda x: andrey.anm(x)),\n", " \"PNL\": (\"post_nonlinear\", \"laplace\", lambda x: andrey.pnl(x)),\n", "}\n", "rows = {}\n", "for s in range(10):\n", " rows[s] = {}\n", " for name, (functional, noise, fit) in models.items():\n", " draw = sample_scm(\n", " graph=\"erdos_renyi\",\n", " functional=functional,\n", " noise=noise,\n", " d=2,\n", " n=500,\n", " seed=s,\n", " scale=\"standardize\",\n", " density=1.0,\n", " )\n", " found = fit(draw.data).structure.oriented_edges()\n", " if found == draw.graph.oriented_edges():\n", " rows[s][name] = \"true direction\"\n", " else:\n", " rows[s][name] = \"reversed\" if found else \"no edge\"\n", "table = pd.DataFrame(rows).T\n", "table.index.name = \"seed\"\n", "table" ] }, { "cell_type": "markdown", "id": "pairwise-10", "metadata": {}, "source": [ "The [0.4.0 release card](https://andrey.abel.ai/docs/benchmarks/0.4.0.html) compares both\n", "methods with causal-learn on pairs of several sizes.\n", "\n", "## Use your own data\n", "\n", "Pass a two-column table, such as a DataFrame whose column names become the graph's labels, or\n", "change `seed` and `n` above and rerun." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.12" } }, "nbformat": 4, "nbformat_minor": 5 }