{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "functional-0",
   "metadata": {},
   "source": [
    "# Nonlinear additive-noise models (CAM and RESIT)\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/functional.ipynb)\n",
    "\n",
    "CAM, the causal additive model, learns a DAG from data in which every variable is a nonlinear\n",
    "function of its parents plus independent noise, `x_j = f_j(parents) + e_j`. When the functions are\n",
    "nonlinear, the direction of every edge is identifiable, so CAM returns a DAG instead of the\n",
    "equivalence class that PC and GES return. It fits a smooth function of each parent, adds edges\n",
    "greedily by the likelihood, and drops the parents whose smooth terms are not significant. It also\n",
    "returns a causal order.\n",
    "\n",
    "RESIT, regression with subsequent independence tests, fits the same kind of model with noise of\n",
    "any distribution, for a handful of variables."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "functional-1",
   "metadata": {},
   "source": [
    "## Start\n",
    "\n",
    "Choose a CPU runtime and run this cell first. In Colab it installs `andrey-core==0.13.0` from PyPI;\n",
    "local kernels use the installed environment."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "functional-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.13.0\" pandas"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "functional-3",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "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\n",
    "from andrey.metrics import shd"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "functional-4",
   "metadata": {},
   "source": [
    "## Data\n",
    "\n",
    "`sample_scm` draws six variables on an Erdos-Renyi graph, each the sum of a nonlinear function of\n",
    "its parents and Gaussian noise, with 1,000 rows and standardized columns. The columns come in an\n",
    "order drawn from the seed, so their order says nothing about the causal order."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "functional-5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1000 observations of 6 variables\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 364 258\" width=\"100%\" font-family=\"Inter, system-ui, sans-serif\"><rect width=\"364\" height=\"258\" fill=\"#fafbfc\"/><line x1=\"309.10\" y1=\"134.32\" x2=\"44.95\" y2=\"158.46\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"319.05,133.41 308.64,129.34 309.55,139.30\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"310.11\" y1=\"139.29\" x2=\"244.37\" y2=\"160.49\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"319.63,136.22 308.58,134.53 311.65,144.05\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"45.00\" y1=\"159.96\" x2=\"209.01\" y2=\"163.93\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"219.00,164.17 209.13,158.93 208.89,168.93\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"42.84\" y1=\"166.82\" x2=\"112.82\" y2=\"213.09\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"121.16,218.60 115.57,208.92 110.06,217.26\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"109.00\" y1=\"32.23\" x2=\"45.00\" y2=\"32.23\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"119.00,32.23 109.00,27.23 109.00,37.23\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"212.39\" y1=\"176.50\" x2=\"143.08\" y2=\"218.98\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"220.92,171.28 209.78,172.24 215.00,180.77\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><circle cx=\"332.00\" cy=\"132.23\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"332.00\" y=\"132.23\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">0</text><circle cx=\"32.00\" cy=\"159.65\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"32.00\" y=\"159.65\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">1</text><circle cx=\"132.00\" cy=\"32.23\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"132.00\" y=\"32.23\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">2</text><circle cx=\"232.00\" cy=\"164.48\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"232.00\" y=\"164.48\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">3</text><circle cx=\"132.00\" cy=\"225.77\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"132.00\" y=\"225.77\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">4</text><circle cx=\"32.00\" cy=\"32.23\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"32.00\" y=\"32.23\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">5</text></svg>"
      ],
      "text/plain": [
       "'<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 364 258\" width=\"100%\" font-family=\"Inter, system-ui, sans-serif\"><rect width=\"364\" height=\"258\" fill=\"#fafbfc\"/><line x1=\"309.10\" y1=\"134.32\" x2=\"44.95\" y2=\"158.46\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"319.05,133.41 308.64,129.34 309.55,139.30\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"310.11\" y1=\"139.29\" x2=\"244.37\" y2=\"160.49\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"319.63,136.22 308.58,134.53 311.65,144.05\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"45.00\" y1=\"159.96\" x2=\"209.01\" y2=\"163.93\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"219.00,164.17 209.13,158.93 208.89,168.93\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"42.84\" y1=\"166.82\" x2=\"112.82\" y2=\"213.09\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"121.16,218.60 115.57,208.92 110.06,217.26\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"109.00\" y1=\"32.23\" x2=\"45.00\" y2=\"32.23\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"119.00,32.23 109.00,27.23 109.00,37.23\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"212.39\" y1=\"176.50\" x2=\"143.08\" y2=\"218.98\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"220.92,171.28 209.78,172.24 215.00,180.77\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><circle cx=\"332.00\" cy=\"132.23\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"332.00\" y=\"132.23\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">0</text><circle cx=\"32.00\" cy=\"159.65\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"32.00\" y=\"159.65\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">1</text><circle cx=\"132.00\" cy=\"32.23\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"132.00\" y=\"32.23\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">2</text><circle cx=\"232.00\" cy=\"164.48\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"232.00\" y=\"164.48\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">3</text><circle cx=\"132.00\" cy=\"225.77\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"132.00\" y=\"225.77\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">4</text><circle cx=\"32.00\" cy=\"32.23\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"32.00\" y=\"32.23\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">5</text></svg>'"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "def draw(seed: int, d: int = 6, noise: str = \"gaussian\"):\n",
    "    \"\"\"Draw 1,000 rows of d variables with additive noise; return the data and the true DAG.\"\"\"\n",
    "    sample = sample_scm(\n",
    "        graph=\"erdos_renyi\",\n",
    "        functional=\"additive_noise\",\n",
    "        noise=noise,\n",
    "        d=d,\n",
    "        n=1000,\n",
    "        seed=seed,\n",
    "        scale=\"standardize\",\n",
    "        density=2.0,\n",
    "    )\n",
    "    return sample.data, sample.graph\n",
    "\n",
    "\n",
    "X, truth = draw(seed=0)\n",
    "print(f\"{X.shape[0]} observations of {X.shape[1]} variables\")\n",
    "display(andrey.viz.draw(truth))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "functional-6",
   "metadata": {},
   "source": [
    "## Fit CAM\n",
    "\n",
    "CAM returns a DAG and `ordering`, a causal order as column indices, causes first: the order of its\n",
    "search before pruning."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "functional-7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "causal order (5, 2, 4, 3, 0, 1)\n",
      "  0 -> 1\n",
      "  3 -> 0\n",
      "  3 -> 1\n",
      "  4 -> 1\n",
      "  5 -> 2\n",
      "  4 -> 3\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 364 240\" width=\"100%\" font-family=\"Inter, system-ui, sans-serif\"><rect width=\"364\" height=\"240\" fill=\"#fafbfc\"/><line x1=\"245.00\" y1=\"186.13\" x2=\"309.00\" y2=\"186.13\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"319.00,186.13 309.00,181.13 309.00,191.13\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"209.00\" y1=\"186.13\" x2=\"145.00\" y2=\"186.13\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"219.00,186.13 209.00,181.13 209.00,191.13\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><path d=\"M 310.57 177.77 Q 232.00 147.13 144.11 181.41\" fill=\"none\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"319.89,181.41 312.39,173.12 308.76,182.43\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><path d=\"M 309.85 179.94 Q 184.64 144.94 44.97 154.59\" fill=\"none\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"319.48,182.63 311.19,175.12 308.50,184.75\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"109.00\" y1=\"53.87\" x2=\"45.00\" y2=\"53.87\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"119.00,53.87 109.00,48.87 109.00,58.87\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"110.01\" y1=\"179.39\" x2=\"44.43\" y2=\"159.29\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"119.57,182.32 111.47,174.61 108.54,184.17\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><circle cx=\"232.00\" cy=\"186.13\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"232.00\" y=\"186.13\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">0</text><circle cx=\"332.00\" cy=\"186.13\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"332.00\" y=\"186.13\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">1</text><circle cx=\"132.00\" cy=\"53.87\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"132.00\" y=\"53.87\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">2</text><circle cx=\"132.00\" cy=\"186.13\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"132.00\" y=\"186.13\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">3</text><circle cx=\"32.00\" cy=\"155.49\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"32.00\" y=\"155.49\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">4</text><circle cx=\"32.00\" cy=\"53.87\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"32.00\" y=\"53.87\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">5</text></svg>"
      ],
      "text/plain": [
       "'<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 364 240\" width=\"100%\" font-family=\"Inter, system-ui, sans-serif\"><rect width=\"364\" height=\"240\" fill=\"#fafbfc\"/><line x1=\"245.00\" y1=\"186.13\" x2=\"309.00\" y2=\"186.13\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"319.00,186.13 309.00,181.13 309.00,191.13\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"209.00\" y1=\"186.13\" x2=\"145.00\" y2=\"186.13\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"219.00,186.13 209.00,181.13 209.00,191.13\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><path d=\"M 310.57 177.77 Q 232.00 147.13 144.11 181.41\" fill=\"none\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"319.89,181.41 312.39,173.12 308.76,182.43\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><path d=\"M 309.85 179.94 Q 184.64 144.94 44.97 154.59\" fill=\"none\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"319.48,182.63 311.19,175.12 308.50,184.75\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"109.00\" y1=\"53.87\" x2=\"45.00\" y2=\"53.87\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"119.00,53.87 109.00,48.87 109.00,58.87\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"110.01\" y1=\"179.39\" x2=\"44.43\" y2=\"159.29\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"119.57,182.32 111.47,174.61 108.54,184.17\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><circle cx=\"232.00\" cy=\"186.13\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"232.00\" y=\"186.13\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">0</text><circle cx=\"332.00\" cy=\"186.13\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"332.00\" y=\"186.13\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">1</text><circle cx=\"132.00\" cy=\"53.87\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"132.00\" y=\"53.87\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">2</text><circle cx=\"132.00\" cy=\"186.13\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"132.00\" y=\"186.13\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">3</text><circle cx=\"32.00\" cy=\"155.49\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"32.00\" y=\"155.49\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">4</text><circle cx=\"32.00\" cy=\"53.87\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"32.00\" y=\"53.87\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">5</text></svg>'"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "cam = andrey.cam(X)\n",
    "print(f\"causal order {cam.ordering}\")\n",
    "for source, target, _ in cam.structure.oriented_edges():\n",
    "    print(f\"  {source} -> {target}\")\n",
    "display(andrey.viz.draw(cam.structure))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "functional-8",
   "metadata": {},
   "source": [
    "## Compare with the truth\n",
    "\n",
    "The structural Hamming distance (SHD) counts the edges to add, remove, or reverse to reach the true\n",
    "graph; the empty graph's SHD is the number of true edges."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "functional-9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>edges</th>\n",
       "      <th>SHD</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>CAM</th>\n",
       "      <td>6</td>\n",
       "      <td>3</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>empty graph</th>\n",
       "      <td>0</td>\n",
       "      <td>6</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "             edges  SHD\n",
       "CAM              6    3\n",
       "empty graph      0    6"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rows = {\n",
    "    \"CAM\": {\n",
    "        \"edges\": len(cam.structure.oriented_edges()),\n",
    "        \"SHD\": shd(cam.structure, truth),\n",
    "    },\n",
    "    \"empty graph\": {\"edges\": 0, \"SHD\": len(truth.oriented_edges())},\n",
    "}\n",
    "pd.DataFrame(rows).T"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "functional-10",
   "metadata": {},
   "source": [
    "## Check over ten datasets\n",
    "\n",
    "The same model, drawn with ten seeds. The table gives CAM's SHD, with the empty graph's beside it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "functional-11",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>CAM</th>\n",
       "      <th>empty graph</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>seed</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>3</td>\n",
       "      <td>6</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>0</td>\n",
       "      <td>6</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>0</td>\n",
       "      <td>6</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>0</td>\n",
       "      <td>6</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>1</td>\n",
       "      <td>6</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>0</td>\n",
       "      <td>6</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>2</td>\n",
       "      <td>6</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>3</td>\n",
       "      <td>6</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>0</td>\n",
       "      <td>6</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>6</td>\n",
       "      <td>6</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "      CAM  empty graph\n",
       "seed                  \n",
       "0       3            6\n",
       "1       0            6\n",
       "2       0            6\n",
       "3       0            6\n",
       "4       1            6\n",
       "5       0            6\n",
       "6       2            6\n",
       "7       3            6\n",
       "8       0            6\n",
       "9       6            6"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "median SHD: {'CAM': 0.5, 'empty graph': 6.0}\n"
     ]
    }
   ],
   "source": [
    "rows = {}\n",
    "for seed in range(10):\n",
    "    data, graph = draw(seed)\n",
    "    rows[seed] = {\n",
    "        \"CAM\": shd(andrey.cam(data).structure, graph),\n",
    "        \"empty graph\": len(graph.oriented_edges()),\n",
    "    }\n",
    "table = pd.DataFrame(rows).T\n",
    "table.index.name = \"seed\"\n",
    "display(table)\n",
    "print(\"median SHD:\", table.median().to_dict())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "functional-12",
   "metadata": {},
   "source": [
    "## Rescaling changes nothing\n",
    "\n",
    "CAM does not depend on a column's scale or location, so multiplying or shifting a column leaves its\n",
    "graph unchanged. Here every column is rescaled and shifted differently."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "functional-13",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "same graph on the rescaled data: True\n"
     ]
    }
   ],
   "source": [
    "scale = np.array([0.01, 1.0, 5.0, 50.0, 300.0, 1e4])\n",
    "unit_free = X * scale + 7 * scale\n",
    "same = np.array_equal(andrey.cam(unit_free).structure.to_numpy(), cam.structure.to_numpy())\n",
    "print(f\"same graph on the rescaled data: {same}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "functional-115",
   "metadata": {},
   "source": [
    "## RESIT\n",
    "\n",
    "RESIT fits the same model with noise of any distribution. It finds a causal order from the sink up:\n",
    "the next sink is the variable whose additive-model residual an independence test finds most\n",
    "independent of the remaining variables. Then it removes each parent whose removal leaves the residual\n",
    "independent. Each independence test reads all the remaining variables at once, so RESIT suits a\n",
    "handful of variables: its accuracy claim holds up to 10, and it takes seconds at 5 and minutes at 30.\n",
    "Here it runs on five variables with Gumbel noise."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "functional-116",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "causal order (1, 2, 0, 3, 4)\n",
      "  1 -> 0\n",
      "  1 -> 2\n",
      "  2 -> 3\n",
      "  2 -> 4\n",
      "  3 -> 4\n",
      "SHD 0, empty graph 5\n"
     ]
    }
   ],
   "source": [
    "X5, truth5 = draw(seed=0, d=5, noise=\"gumbel\")\n",
    "resit = andrey.resit(X5)\n",
    "print(f\"causal order {resit.ordering}\")\n",
    "for source, target, _ in resit.structure.oriented_edges():\n",
    "    print(f\"  {source} -> {target}\")\n",
    "print(f\"SHD {shd(resit.structure, truth5)}, empty graph {len(truth5.oriented_edges())}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "functional-117",
   "metadata": {},
   "source": [
    "## Check RESIT over ten datasets\n",
    "\n",
    "The same model with ten seeds: RESIT's SHD, with the empty graph's beside it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "functional-118",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>RESIT</th>\n",
       "      <th>empty graph</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>seed</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>0</td>\n",
       "      <td>5</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>0</td>\n",
       "      <td>5</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>0</td>\n",
       "      <td>5</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>0</td>\n",
       "      <td>5</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>0</td>\n",
       "      <td>5</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>0</td>\n",
       "      <td>5</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>2</td>\n",
       "      <td>5</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>0</td>\n",
       "      <td>5</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>0</td>\n",
       "      <td>5</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>0</td>\n",
       "      <td>5</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "      RESIT  empty graph\n",
       "seed                    \n",
       "0         0            5\n",
       "1         0            5\n",
       "2         0            5\n",
       "3         0            5\n",
       "4         0            5\n",
       "5         0            5\n",
       "6         2            5\n",
       "7         0            5\n",
       "8         0            5\n",
       "9         0            5"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "median SHD: {'RESIT': 0.0, 'empty graph': 5.0}\n"
     ]
    }
   ],
   "source": [
    "rows = {}\n",
    "for seed in range(10):\n",
    "    data, graph = draw(seed, d=5, noise=\"gumbel\")\n",
    "    rows[seed] = {\n",
    "        \"RESIT\": shd(andrey.resit(data).structure, graph),\n",
    "        \"empty graph\": len(graph.oriented_edges()),\n",
    "    }\n",
    "table = pd.DataFrame(rows).T\n",
    "table.index.name = \"seed\"\n",
    "display(table)\n",
    "print(\"median SHD:\", table.median().to_dict())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "functional-14",
   "metadata": {},
   "source": [
    "## When to use it\n",
    "\n",
    "CAM needs effects that are nonlinear and additive in the parents, with additive noise and no hidden\n",
    "common causes. With linear effects and Gaussian noise the data identify only the equivalence class\n",
    "and the direction of the other edges is arbitrary, so use PC or GES; noise whose scale depends on\n",
    "the parents can reverse edges. The [0.13.0 release card](https://andrey.abel.ai/docs/benchmarks/0.13.0.html#cam)\n",
    "compares CAM with CAM 1.0 from 10 to 100 variables. Change `seed`, `d`, or the noise (`\"gumbel\"`)\n",
    "and rerun to see how it responds.\n",
    "\n",
    "RESIT needs the same kind of data with noise of any distribution, and no more than about ten\n",
    "variables: from 20 variables its errors come near the empty graph's on Gaussian noise and a fit\n",
    "takes about a minute, so use CAM for more variables. The release card compares RESIT with the\n",
    "authors' code and with lingam from 5 to 20 variables."
   ]
  }
 ],
 "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.14.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
