{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "lingam-0",
   "metadata": {},
   "source": [
    "# Linear non-Gaussian models (DirectLiNGAM / ICA-LiNGAM / MultiGroupDirectLiNGAM / GIN)\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/lingam.ipynb)\n",
    "\n",
    "When the mechanisms are linear and the noise is non-Gaussian, the data identify a single DAG, not\n",
    "only its equivalence class. DirectLiNGAM finds a causal order one variable at a time with a\n",
    "pairwise independence measure. ICA-LiNGAM runs independent component analysis (FastICA) and reads\n",
    "the order from the unmixing matrix. Both return a fully oriented DAG, a causal order, and edge\n",
    "weights. MultiGroupDirectLiNGAM fits several datasets that share one causal order, and GIN finds\n",
    "the latent variables that observed indicators measure."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7fb27b941602401d91542211134fc71a",
   "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": "acae54e37e7d407bbb7b55eff062a284",
   "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": "lingam-1",
   "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.metrics as metrics\n",
    "import andrey.viz\n",
    "from andrey.data import benchmarks"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "lingam-2",
   "metadata": {},
   "source": [
    "## Data\n",
    "\n",
    "`lingam_sf` draws a scale-free graph with linear mechanisms and uniform noise: here 8 variables\n",
    "and 2,000 samples."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "lingam-3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2000 observations, 8 variables, seed 7\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 564 262\" width=\"100%\" font-family=\"Inter, system-ui, sans-serif\"><rect width=\"564\" height=\"262\" fill=\"#fafbfc\"/><line x1=\"442.84\" y1=\"153.67\" x2=\"512.82\" y2=\"107.40\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"521.16,101.88 510.06,103.23 515.57,111.57\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"444.19\" y1=\"165.36\" x2=\"510.44\" y2=\"189.94\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"519.81,193.41 512.18,185.25 508.70,194.62\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"409.00\" y1=\"160.84\" x2=\"345.00\" y2=\"160.84\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"419.00,160.84 409.00,155.84 409.00,165.84\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"139.84\" y1=\"219.82\" x2=\"218.13\" y2=\"116.28\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"224.16,108.31 214.14,113.27 222.12,119.30\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"144.96\" y1=\"229.15\" x2=\"509.07\" y2=\"199.78\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"519.04,198.98 508.67,194.80 509.48,204.77\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"112.82\" y1=\"217.51\" x2=\"42.84\" y2=\"171.24\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"121.16,223.02 115.57,213.34 110.06,221.68\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"510.16\" y1=\"101.93\" x2=\"344.34\" y2=\"156.76\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"519.66,98.79 508.59,97.18 511.73,106.68\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"509.28\" y1=\"91.14\" x2=\"144.84\" y2=\"33.83\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"519.16,92.69 510.06,86.20 508.50,96.08\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"210.16\" y1=\"105.16\" x2=\"44.34\" y2=\"159.98\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"219.66,102.02 208.59,100.41 211.73,109.90\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"243.00\" y1=\"104.86\" x2=\"312.53\" y2=\"148.59\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"321.00,153.92 315.19,144.36 309.87,152.82\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"212.82\" y1=\"85.25\" x2=\"142.84\" y2=\"38.98\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"221.16,90.76 215.57,81.08 210.06,89.42\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"45.00\" y1=\"163.92\" x2=\"309.00\" y2=\"161.09\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"319.00,160.98 308.95,156.09 309.06,166.09\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><circle cx=\"432.00\" cy=\"160.84\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"432.00\" y=\"160.84\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">0</text><circle cx=\"132.00\" cy=\"230.19\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"132.00\" y=\"230.19\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">1</text><circle cx=\"532.00\" cy=\"94.71\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"532.00\" y=\"94.71\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">2</text><circle cx=\"232.00\" cy=\"97.94\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"232.00\" y=\"97.94\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">3</text><circle cx=\"532.00\" cy=\"197.94\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"532.00\" y=\"197.94\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">4</text><circle cx=\"32.00\" cy=\"164.06\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"32.00\" y=\"164.06\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">5</text><circle cx=\"332.00\" cy=\"160.84\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"332.00\" y=\"160.84\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">6</text><circle cx=\"132.00\" cy=\"31.81\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"132.00\" y=\"31.81\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">7</text></svg>"
      ],
      "text/plain": [
       "'<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 564 262\" width=\"100%\" font-family=\"Inter, system-ui, sans-serif\"><rect width=\"564\" height=\"262\" fill=\"#fafbfc\"/><line x1=\"442.84\" y1=\"153.67\" x2=\"512.82\" y2=\"107.40\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"521.16,101.88 510.06,103.23 515.57,111.57\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"444.19\" y1=\"165.36\" x2=\"510.44\" y2=\"189.94\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"519.81,193.41 512.18,185.25 508.70,194.62\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"409.00\" y1=\"160.84\" x2=\"345.00\" y2=\"160.84\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"419.00,160.84 409.00,155.84 409.00,165.84\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"139.84\" y1=\"219.82\" x2=\"218.13\" y2=\"116.28\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"224.16,108.31 214.14,113.27 222.12,119.30\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"144.96\" y1=\"229.15\" x2=\"509.07\" y2=\"199.78\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"519.04,198.98 508.67,194.80 509.48,204.77\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"112.82\" y1=\"217.51\" x2=\"42.84\" y2=\"171.24\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"121.16,223.02 115.57,213.34 110.06,221.68\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"510.16\" y1=\"101.93\" x2=\"344.34\" y2=\"156.76\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"519.66,98.79 508.59,97.18 511.73,106.68\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"509.28\" y1=\"91.14\" x2=\"144.84\" y2=\"33.83\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"519.16,92.69 510.06,86.20 508.50,96.08\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"210.16\" y1=\"105.16\" x2=\"44.34\" y2=\"159.98\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"219.66,102.02 208.59,100.41 211.73,109.90\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"243.00\" y1=\"104.86\" x2=\"312.53\" y2=\"148.59\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"321.00,153.92 315.19,144.36 309.87,152.82\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"212.82\" y1=\"85.25\" x2=\"142.84\" y2=\"38.98\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"221.16,90.76 215.57,81.08 210.06,89.42\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"45.00\" y1=\"163.92\" x2=\"309.00\" y2=\"161.09\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"319.00,160.98 308.95,156.09 309.06,166.09\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><circle cx=\"432.00\" cy=\"160.84\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"432.00\" y=\"160.84\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">0</text><circle cx=\"132.00\" cy=\"230.19\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"132.00\" y=\"230.19\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">1</text><circle cx=\"532.00\" cy=\"94.71\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"532.00\" y=\"94.71\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">2</text><circle cx=\"232.00\" cy=\"97.94\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"232.00\" y=\"97.94\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">3</text><circle cx=\"532.00\" cy=\"197.94\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"532.00\" y=\"197.94\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">4</text><circle cx=\"32.00\" cy=\"164.06\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"32.00\" y=\"164.06\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">5</text><circle cx=\"332.00\" cy=\"160.84\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"332.00\" y=\"160.84\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">6</text><circle cx=\"132.00\" cy=\"31.81\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"132.00\" y=\"31.81\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">7</text></svg>'"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "seed = 7\n",
    "dataset = benchmarks.scm(\"lingam_sf\", d=8).sample(n=2000, seed=seed, scale=\"standardize\")\n",
    "X, truth = dataset.data, dataset.observed_graph\n",
    "print(f\"{X.shape[0]} observations, {X.shape[1]} variables, seed {seed}\")\n",
    "display(andrey.viz.draw(truth))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "lingam-4",
   "metadata": {},
   "source": [
    "## Fit both methods\n",
    "\n",
    "DirectLiNGAM is deterministic. ICA-LiNGAM starts FastICA from a random point; `random_state` fixes\n",
    "it, so a run repeats exactly."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "lingam-5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "DirectLiNGAM: dag, causal order (7, 5, 1, 3, 6, 0, 4, 2)\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 564 262\" width=\"100%\" font-family=\"Inter, system-ui, sans-serif\"><rect width=\"564\" height=\"262\" fill=\"#fafbfc\"/><line x1=\"442.84\" y1=\"153.67\" x2=\"512.82\" y2=\"107.40\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"521.16,101.88 510.06,103.23 515.57,111.57\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"444.19\" y1=\"165.36\" x2=\"510.44\" y2=\"189.94\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"519.81,193.41 512.18,185.25 508.70,194.62\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"409.00\" y1=\"160.84\" x2=\"345.00\" y2=\"160.84\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"419.00,160.84 409.00,155.84 409.00,165.84\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"139.84\" y1=\"219.82\" x2=\"218.13\" y2=\"116.28\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"224.16,108.31 214.14,113.27 222.12,119.30\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"144.96\" y1=\"229.15\" x2=\"509.07\" y2=\"199.78\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"519.04,198.98 508.67,194.80 509.48,204.77\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"112.82\" y1=\"217.51\" x2=\"42.84\" y2=\"171.24\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"121.16,223.02 115.57,213.34 110.06,221.68\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"510.16\" y1=\"101.93\" x2=\"344.34\" y2=\"156.76\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"519.66,98.79 508.59,97.18 511.73,106.68\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"509.28\" y1=\"91.14\" x2=\"144.84\" y2=\"33.83\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"519.16,92.69 510.06,86.20 508.50,96.08\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"210.16\" y1=\"105.16\" x2=\"44.34\" y2=\"159.98\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"219.66,102.02 208.59,100.41 211.73,109.90\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"243.00\" y1=\"104.86\" x2=\"312.53\" y2=\"148.59\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"321.00,153.92 315.19,144.36 309.87,152.82\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"212.82\" y1=\"85.25\" x2=\"142.84\" y2=\"38.98\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"221.16,90.76 215.57,81.08 210.06,89.42\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"45.00\" y1=\"163.92\" x2=\"309.00\" y2=\"161.09\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"319.00,160.98 308.95,156.09 309.06,166.09\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><circle cx=\"432.00\" cy=\"160.84\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"432.00\" y=\"160.84\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">0</text><circle cx=\"132.00\" cy=\"230.19\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"132.00\" y=\"230.19\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">1</text><circle cx=\"532.00\" cy=\"94.71\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"532.00\" y=\"94.71\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">2</text><circle cx=\"232.00\" cy=\"97.94\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"232.00\" y=\"97.94\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">3</text><circle cx=\"532.00\" cy=\"197.94\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"532.00\" y=\"197.94\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">4</text><circle cx=\"32.00\" cy=\"164.06\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"32.00\" y=\"164.06\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">5</text><circle cx=\"332.00\" cy=\"160.84\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"332.00\" y=\"160.84\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">6</text><circle cx=\"132.00\" cy=\"31.81\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"132.00\" y=\"31.81\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">7</text></svg>"
      ],
      "text/plain": [
       "'<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 564 262\" width=\"100%\" font-family=\"Inter, system-ui, sans-serif\"><rect width=\"564\" height=\"262\" fill=\"#fafbfc\"/><line x1=\"442.84\" y1=\"153.67\" x2=\"512.82\" y2=\"107.40\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"521.16,101.88 510.06,103.23 515.57,111.57\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"444.19\" y1=\"165.36\" x2=\"510.44\" y2=\"189.94\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"519.81,193.41 512.18,185.25 508.70,194.62\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"409.00\" y1=\"160.84\" x2=\"345.00\" y2=\"160.84\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"419.00,160.84 409.00,155.84 409.00,165.84\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"139.84\" y1=\"219.82\" x2=\"218.13\" y2=\"116.28\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"224.16,108.31 214.14,113.27 222.12,119.30\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"144.96\" y1=\"229.15\" x2=\"509.07\" y2=\"199.78\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"519.04,198.98 508.67,194.80 509.48,204.77\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"112.82\" y1=\"217.51\" x2=\"42.84\" y2=\"171.24\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"121.16,223.02 115.57,213.34 110.06,221.68\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"510.16\" y1=\"101.93\" x2=\"344.34\" y2=\"156.76\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"519.66,98.79 508.59,97.18 511.73,106.68\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"509.28\" y1=\"91.14\" x2=\"144.84\" y2=\"33.83\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"519.16,92.69 510.06,86.20 508.50,96.08\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"210.16\" y1=\"105.16\" x2=\"44.34\" y2=\"159.98\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"219.66,102.02 208.59,100.41 211.73,109.90\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"243.00\" y1=\"104.86\" x2=\"312.53\" y2=\"148.59\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"321.00,153.92 315.19,144.36 309.87,152.82\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"212.82\" y1=\"85.25\" x2=\"142.84\" y2=\"38.98\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"221.16,90.76 215.57,81.08 210.06,89.42\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"45.00\" y1=\"163.92\" x2=\"309.00\" y2=\"161.09\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"319.00,160.98 308.95,156.09 309.06,166.09\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><circle cx=\"432.00\" cy=\"160.84\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"432.00\" y=\"160.84\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">0</text><circle cx=\"132.00\" cy=\"230.19\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"132.00\" y=\"230.19\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">1</text><circle cx=\"532.00\" cy=\"94.71\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"532.00\" y=\"94.71\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">2</text><circle cx=\"232.00\" cy=\"97.94\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"232.00\" y=\"97.94\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">3</text><circle cx=\"532.00\" cy=\"197.94\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"532.00\" y=\"197.94\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">4</text><circle cx=\"32.00\" cy=\"164.06\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"32.00\" y=\"164.06\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">5</text><circle cx=\"332.00\" cy=\"160.84\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"332.00\" y=\"160.84\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">6</text><circle cx=\"132.00\" cy=\"31.81\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"132.00\" y=\"31.81\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">7</text></svg>'"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ICA-LiNGAM: dag, causal order (5, 7, 1, 3, 6, 0, 2, 4)\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 564 262\" width=\"100%\" font-family=\"Inter, system-ui, sans-serif\"><rect width=\"564\" height=\"262\" fill=\"#fafbfc\"/><line x1=\"442.84\" y1=\"153.67\" x2=\"512.82\" y2=\"107.40\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"521.16,101.88 510.06,103.23 515.57,111.57\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"444.19\" y1=\"165.36\" x2=\"510.44\" y2=\"189.94\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"519.81,193.41 512.18,185.25 508.70,194.62\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"409.00\" y1=\"160.84\" x2=\"345.00\" y2=\"160.84\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"419.00,160.84 409.00,155.84 409.00,165.84\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"139.84\" y1=\"219.82\" x2=\"218.13\" y2=\"116.28\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"224.16,108.31 214.14,113.27 222.12,119.30\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"144.96\" y1=\"229.15\" x2=\"509.07\" y2=\"199.78\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"519.04,198.98 508.67,194.80 509.48,204.77\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"112.82\" y1=\"217.51\" x2=\"42.84\" y2=\"171.24\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"121.16,223.02 115.57,213.34 110.06,221.68\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"510.16\" y1=\"101.93\" x2=\"344.34\" y2=\"156.76\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"519.66,98.79 508.59,97.18 511.73,106.68\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"509.28\" y1=\"91.14\" x2=\"144.84\" y2=\"33.83\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"519.16,92.69 510.06,86.20 508.50,96.08\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"210.16\" y1=\"105.16\" x2=\"44.34\" y2=\"159.98\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"219.66,102.02 208.59,100.41 211.73,109.90\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"243.00\" y1=\"104.86\" x2=\"312.53\" y2=\"148.59\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"321.00,153.92 315.19,144.36 309.87,152.82\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"212.82\" y1=\"85.25\" x2=\"142.84\" y2=\"38.98\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"221.16,90.76 215.57,81.08 210.06,89.42\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"45.00\" y1=\"163.92\" x2=\"309.00\" y2=\"161.09\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"319.00,160.98 308.95,156.09 309.06,166.09\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><circle cx=\"432.00\" cy=\"160.84\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"432.00\" y=\"160.84\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">0</text><circle cx=\"132.00\" cy=\"230.19\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"132.00\" y=\"230.19\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">1</text><circle cx=\"532.00\" cy=\"94.71\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"532.00\" y=\"94.71\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">2</text><circle cx=\"232.00\" cy=\"97.94\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"232.00\" y=\"97.94\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">3</text><circle cx=\"532.00\" cy=\"197.94\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"532.00\" y=\"197.94\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">4</text><circle cx=\"32.00\" cy=\"164.06\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"32.00\" y=\"164.06\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">5</text><circle cx=\"332.00\" cy=\"160.84\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"332.00\" y=\"160.84\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">6</text><circle cx=\"132.00\" cy=\"31.81\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"132.00\" y=\"31.81\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">7</text></svg>"
      ],
      "text/plain": [
       "'<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 564 262\" width=\"100%\" font-family=\"Inter, system-ui, sans-serif\"><rect width=\"564\" height=\"262\" fill=\"#fafbfc\"/><line x1=\"442.84\" y1=\"153.67\" x2=\"512.82\" y2=\"107.40\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"521.16,101.88 510.06,103.23 515.57,111.57\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"444.19\" y1=\"165.36\" x2=\"510.44\" y2=\"189.94\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"519.81,193.41 512.18,185.25 508.70,194.62\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"409.00\" y1=\"160.84\" x2=\"345.00\" y2=\"160.84\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"419.00,160.84 409.00,155.84 409.00,165.84\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"139.84\" y1=\"219.82\" x2=\"218.13\" y2=\"116.28\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"224.16,108.31 214.14,113.27 222.12,119.30\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"144.96\" y1=\"229.15\" x2=\"509.07\" y2=\"199.78\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"519.04,198.98 508.67,194.80 509.48,204.77\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"112.82\" y1=\"217.51\" x2=\"42.84\" y2=\"171.24\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"121.16,223.02 115.57,213.34 110.06,221.68\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"510.16\" y1=\"101.93\" x2=\"344.34\" y2=\"156.76\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"519.66,98.79 508.59,97.18 511.73,106.68\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"509.28\" y1=\"91.14\" x2=\"144.84\" y2=\"33.83\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"519.16,92.69 510.06,86.20 508.50,96.08\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"210.16\" y1=\"105.16\" x2=\"44.34\" y2=\"159.98\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"219.66,102.02 208.59,100.41 211.73,109.90\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"243.00\" y1=\"104.86\" x2=\"312.53\" y2=\"148.59\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"321.00,153.92 315.19,144.36 309.87,152.82\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"212.82\" y1=\"85.25\" x2=\"142.84\" y2=\"38.98\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"221.16,90.76 215.57,81.08 210.06,89.42\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"45.00\" y1=\"163.92\" x2=\"309.00\" y2=\"161.09\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"319.00,160.98 308.95,156.09 309.06,166.09\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><circle cx=\"432.00\" cy=\"160.84\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"432.00\" y=\"160.84\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">0</text><circle cx=\"132.00\" cy=\"230.19\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"132.00\" y=\"230.19\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">1</text><circle cx=\"532.00\" cy=\"94.71\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"532.00\" y=\"94.71\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">2</text><circle cx=\"232.00\" cy=\"97.94\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"232.00\" y=\"97.94\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">3</text><circle cx=\"532.00\" cy=\"197.94\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"532.00\" y=\"197.94\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">4</text><circle cx=\"32.00\" cy=\"164.06\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"32.00\" y=\"164.06\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">5</text><circle cx=\"332.00\" cy=\"160.84\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"332.00\" y=\"160.84\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">6</text><circle cx=\"132.00\" cy=\"31.81\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"132.00\" y=\"31.81\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">7</text></svg>'"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "results = {\n",
    "    \"DirectLiNGAM\": andrey.direct_lingam(X),\n",
    "    \"ICA-LiNGAM\": andrey.ica_lingam(X, random_state=0),\n",
    "}\n",
    "for name, result in results.items():\n",
    "    print(f\"{name}: {result.structure.kind}, causal order {result.ordering}\")\n",
    "    display(andrey.viz.draw(result.structure))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "lingam-6",
   "metadata": {},
   "source": [
    "## Edge weights\n",
    "\n",
    "`weighted_adjacency[i, j]` is the coefficient of variable `i` in the equation for variable `j`, so\n",
    "each nonzero entry is an edge `i -> j`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "lingam-7",
   "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>0</th>\n",
       "      <th>1</th>\n",
       "      <th>2</th>\n",
       "      <th>3</th>\n",
       "      <th>4</th>\n",
       "      <th>5</th>\n",
       "      <th>6</th>\n",
       "      <th>7</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>0.00</td>\n",
       "      <td>0.00</td>\n",
       "      <td>-0.72</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.51</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>0.00</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.94</td>\n",
       "      <td>-0.71</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>0.00</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>0.00</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.76</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>0.00</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>0.00</td>\n",
       "      <td>-0.72</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.38</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.66</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>0.79</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.47</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>0.00</td>\n",
       "      <td>0.00</td>\n",
       "      <td>-0.77</td>\n",
       "      <td>0.38</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "      0     1     2     3     4    5     6    7\n",
       "0  0.00  0.00 -0.72  0.00  0.51  0.0  0.00  0.0\n",
       "1  0.00  0.00  0.00  0.94 -0.71  0.0  0.00  0.0\n",
       "2  0.00  0.00  0.00  0.00  0.00  0.0  0.00  0.0\n",
       "3  0.00  0.00  0.00  0.00  0.00  0.0  0.76  0.0\n",
       "4  0.00  0.00  0.00  0.00  0.00  0.0  0.00  0.0\n",
       "5  0.00 -0.72  0.00  0.38  0.00  0.0  0.66  0.0\n",
       "6  0.79  0.00  0.47  0.00  0.00  0.0  0.00  0.0\n",
       "7  0.00  0.00 -0.77  0.38  0.00  0.0  0.00  0.0"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pd.DataFrame(results[\"ICA-LiNGAM\"].weighted_adjacency).round(2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "lingam-8",
   "metadata": {},
   "source": [
    "## Check accuracy over ten datasets\n",
    "\n",
    "SHD counts errors against the true DAG; lower is better. The empty graph's SHD is the number of\n",
    "true edges. The table covers ten seeds and their median."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "lingam-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>seed</th>\n",
       "      <th>0</th>\n",
       "      <th>1</th>\n",
       "      <th>2</th>\n",
       "      <th>3</th>\n",
       "      <th>4</th>\n",
       "      <th>5</th>\n",
       "      <th>6</th>\n",
       "      <th>7</th>\n",
       "      <th>8</th>\n",
       "      <th>9</th>\n",
       "      <th>median</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Empty graph</th>\n",
       "      <td>12</td>\n",
       "      <td>12</td>\n",
       "      <td>12</td>\n",
       "      <td>12</td>\n",
       "      <td>12</td>\n",
       "      <td>12</td>\n",
       "      <td>12</td>\n",
       "      <td>12</td>\n",
       "      <td>12</td>\n",
       "      <td>12</td>\n",
       "      <td>12.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>DirectLiNGAM</th>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>ICA-LiNGAM</th>\n",
       "      <td>0</td>\n",
       "      <td>8</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>3</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "seed           0   1   2   3   4   5   6   7   8   9  median\n",
       "Empty graph   12  12  12  12  12  12  12  12  12  12    12.0\n",
       "DirectLiNGAM   0   0   0   0   0   0   0   0   0   0     0.0\n",
       "ICA-LiNGAM     0   8   0   0   3   0   0   0   0   0     0.0"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "methods = {\n",
    "    \"DirectLiNGAM\": lambda X: andrey.direct_lingam(X),\n",
    "    \"ICA-LiNGAM\": lambda X: andrey.ica_lingam(X, random_state=0),\n",
    "}\n",
    "rows = {}\n",
    "for s in range(10):\n",
    "    draw = benchmarks.scm(\"lingam_sf\", d=8).sample(n=2000, seed=s, scale=\"standardize\")\n",
    "    d = draw.observed_graph.n_nodes\n",
    "    empty = andrey.GraphStructure.from_numpy(np.zeros((d, d), dtype=np.uint8), kind=\"dag\")\n",
    "    rows[s] = {\"Empty graph\": metrics.score(empty, draw.observed_graph)[\"shd\"]}\n",
    "    for name, fit in methods.items():\n",
    "        rows[s][name] = metrics.score(fit(draw.data), draw.observed_graph)[\"shd\"]\n",
    "shd = pd.DataFrame(rows)\n",
    "shd.columns.name = \"seed\"\n",
    "shd[\"median\"] = shd.median(axis=1)\n",
    "shd"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "lingam-10",
   "metadata": {},
   "source": [
    "## When to use them\n",
    "\n",
    "Both methods rely on non-Gaussian noise. With Gaussian noise the order is not identified; PC, GES,\n",
    "BOSS, or GRaSP return the CPDAG that the data do support. ICA-LiNGAM needs many samples: at\n",
    "`n = 10d`, the size the [release cards](https://andrey.abel.ai/docs/benchmarks.html) use, it scores\n",
    "worse than the empty graph, and this notebook uses `n = 250d`. Change `seed`, `d`, or `n` and rerun\n",
    "to see how each method responds."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "lingam-11",
   "metadata": {},
   "source": [
    "## Several datasets: MultiGroupDirectLiNGAM\n",
    "\n",
    "`multi_group_direct_lingam` takes datasets over the same variables that share one causal order\n",
    "and returns one result per dataset, each with its own weights. Here two groups follow the chain\n",
    "`x0 -> x1 -> x2`, with different weights on `x0 -> x1`, their columns in one order drawn from the\n",
    "seed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "lingam-12",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "group 0: [('x0', 'x1', 'directed'), ('x1', 'x2', 'directed')], weight 0.74\n",
      "group 1: [('x0', 'x1', 'directed'), ('x1', 'x2', 'directed')], weight -1.17\n"
     ]
    }
   ],
   "source": [
    "CHAIN = [(\"x0\", \"x1\"), (\"x1\", \"x2\")]  # the true edges, cause first\n",
    "\n",
    "\n",
    "def sample_groups(seed: int, n: int = 1000, shuffle: bool = True) -> list[pd.DataFrame]:\n",
    "    \"\"\"Draw n rows of the chain per weight on x0 -> x1, all in one column order from the seed.\"\"\"\n",
    "    rng = np.random.default_rng(seed)\n",
    "    groups = []\n",
    "    for weight in (0.8, -1.2):\n",
    "        x0 = rng.uniform(-1, 1, n)\n",
    "        x1 = weight * x0 + rng.uniform(-1, 1, n)\n",
    "        x2 = 0.6 * x1 + rng.uniform(-1, 1, n)\n",
    "        groups.append(pd.DataFrame({\"x0\": x0, \"x1\": x1, \"x2\": x2}))\n",
    "    columns = rng.permutation(groups[0].columns) if shuffle else groups[0].columns\n",
    "    return [frame[columns] for frame in groups]\n",
    "\n",
    "\n",
    "groups = sample_groups(seed=3)\n",
    "for g, out in enumerate(andrey.multi_group_direct_lingam(groups)):\n",
    "    weights = pd.DataFrame(\n",
    "        out.weighted_adjacency, index=groups[g].columns, columns=groups[g].columns\n",
    "    )\n",
    "    print(f\"group {g}: {out.structure.oriented_edges()}, weight {weights.loc['x0', 'x1']:.2f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "lingam-13",
   "metadata": {},
   "source": [
    "## Latent variables: GIN\n",
    "\n",
    "`gin` groups observed indicators under the latent variables that cause them and orders the\n",
    "latents. Here `a1` to `a3` measure one latent and `b1` to `b3` another, which the first causes. The\n",
    "indicators come in an order drawn from the seed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "lingam-14",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[('L1', 'a1', 'directed'), ('L2', 'b1', 'directed'), ('L2', 'b2', 'directed'), ('L1', 'a2', 'directed'), ('L1', 'a3', 'directed'), ('L2', 'b3', 'directed'), ('L1', 'L2', 'directed')]\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 360 348\" width=\"100%\" font-family=\"Inter, system-ui, sans-serif\"><rect width=\"360\" height=\"348\" fill=\"#fafbfc\"/><line x1=\"164.25\" y1=\"48.83\" x2=\"88.90\" y2=\"129.04\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"171.10,41.54 160.61,45.40 167.90,52.25\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"261.24\" y1=\"187.31\" x2=\"190.60\" y2=\"237.44\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"269.40,181.52 258.35,183.23 264.14,191.39\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"257.00\" y1=\"244.97\" x2=\"193.00\" y2=\"244.97\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"267.00,244.97 257.00,239.97 257.00,249.97\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"158.32\" y1=\"110.72\" x2=\"92.25\" y2=\"134.17\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"167.75,107.38 156.65,106.01 160.00,115.44\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"158.32\" y1=\"166.31\" x2=\"92.25\" y2=\"142.86\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"167.75,169.65 160.00,161.60 156.65,171.02\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"261.24\" y1=\"302.62\" x2=\"190.60\" y2=\"252.49\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"269.40,308.41 264.14,298.55 258.35,306.70\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"88.90\" y1=\"147.99\" x2=\"164.25\" y2=\"228.20\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"171.10,235.49 167.90,224.78 160.61,231.63\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><circle cx=\"180.00\" cy=\"32.06\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"180.00\" y=\"32.06\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">a1</text><circle cx=\"280.00\" cy=\"174.00\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"280.00\" y=\"174.00\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">b1</text><circle cx=\"280.00\" cy=\"244.97\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"280.00\" y=\"244.97\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">b2</text><circle cx=\"180.00\" cy=\"103.03\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"180.00\" y=\"103.03\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">a2</text><circle cx=\"180.00\" cy=\"174.00\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"180.00\" y=\"174.00\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">a3</text><circle cx=\"280.00\" cy=\"315.94\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"280.00\" y=\"315.94\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">b3</text><circle cx=\"80.00\" cy=\"138.52\" r=\"13.00\" fill=\"#fafbfc\" stroke=\"#5c6675\" stroke-width=\"1.8\" stroke-dasharray=\"3 3\"/><text x=\"80.00\" y=\"138.52\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#5c6675\" dominant-baseline=\"central\">L1</text><circle cx=\"180.00\" cy=\"244.97\" r=\"13.00\" fill=\"#fafbfc\" stroke=\"#5c6675\" stroke-width=\"1.8\" stroke-dasharray=\"3 3\"/><text x=\"180.00\" y=\"244.97\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#5c6675\" dominant-baseline=\"central\">L2</text></svg>"
      ],
      "text/plain": [
       "'<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 360 348\" width=\"100%\" font-family=\"Inter, system-ui, sans-serif\"><rect width=\"360\" height=\"348\" fill=\"#fafbfc\"/><line x1=\"164.25\" y1=\"48.83\" x2=\"88.90\" y2=\"129.04\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"171.10,41.54 160.61,45.40 167.90,52.25\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"261.24\" y1=\"187.31\" x2=\"190.60\" y2=\"237.44\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"269.40,181.52 258.35,183.23 264.14,191.39\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"257.00\" y1=\"244.97\" x2=\"193.00\" y2=\"244.97\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"267.00,244.97 257.00,239.97 257.00,249.97\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"158.32\" y1=\"110.72\" x2=\"92.25\" y2=\"134.17\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"167.75,107.38 156.65,106.01 160.00,115.44\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"158.32\" y1=\"166.31\" x2=\"92.25\" y2=\"142.86\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"167.75,169.65 160.00,161.60 156.65,171.02\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"261.24\" y1=\"302.62\" x2=\"190.60\" y2=\"252.49\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"269.40,308.41 264.14,298.55 258.35,306.70\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"88.90\" y1=\"147.99\" x2=\"164.25\" y2=\"228.20\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"171.10,235.49 167.90,224.78 160.61,231.63\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><circle cx=\"180.00\" cy=\"32.06\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"180.00\" y=\"32.06\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">a1</text><circle cx=\"280.00\" cy=\"174.00\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"280.00\" y=\"174.00\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">b1</text><circle cx=\"280.00\" cy=\"244.97\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"280.00\" y=\"244.97\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">b2</text><circle cx=\"180.00\" cy=\"103.03\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"180.00\" y=\"103.03\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">a2</text><circle cx=\"180.00\" cy=\"174.00\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"180.00\" y=\"174.00\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">a3</text><circle cx=\"280.00\" cy=\"315.94\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"280.00\" y=\"315.94\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">b3</text><circle cx=\"80.00\" cy=\"138.52\" r=\"13.00\" fill=\"#fafbfc\" stroke=\"#5c6675\" stroke-width=\"1.8\" stroke-dasharray=\"3 3\"/><text x=\"80.00\" y=\"138.52\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#5c6675\" dominant-baseline=\"central\">L1</text><circle cx=\"180.00\" cy=\"244.97\" r=\"13.00\" fill=\"#fafbfc\" stroke=\"#5c6675\" stroke-width=\"1.8\" stroke-dasharray=\"3 3\"/><text x=\"180.00\" y=\"244.97\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#5c6675\" dominant-baseline=\"central\">L2</text></svg>'"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "INDICATORS = [(\"l1\", \"l2\")]  # the latents l1 -> l2 have no column\n",
    "INDICATORS += [(\"l1\", f\"a{i}\") for i in (1, 2, 3)] + [(\"l2\", f\"b{i}\") for i in (1, 2, 3)]\n",
    "\n",
    "\n",
    "def sample_indicators(seed: int, n: int = 1000, shuffle: bool = True) -> pd.DataFrame:\n",
    "    \"\"\"Draw n rows of the six indicators of l1 -> l2, in an order drawn from the seed.\"\"\"\n",
    "    rng = np.random.default_rng(seed)\n",
    "    l1 = rng.uniform(-1, 1, n)\n",
    "    l2 = 0.9 * l1 + rng.uniform(-1, 1, n)\n",
    "    loadings = {\"a1\": (l1, 1.0), \"a2\": (l1, 0.8), \"a3\": (l1, -1.2)}\n",
    "    loadings |= {\"b1\": (l2, 1.0), \"b2\": (l2, 1.1), \"b3\": (l2, -0.7)}\n",
    "    frame = pd.DataFrame(\n",
    "        {name: w * latent + 0.3 * rng.uniform(-1, 1, n) for name, (latent, w) in loadings.items()}\n",
    "    )\n",
    "    return frame[rng.permutation(frame.columns)] if shuffle else frame\n",
    "\n",
    "\n",
    "gin_out = andrey.gin(sample_indicators(seed=5))\n",
    "print(gin_out.structure.oriented_edges())\n",
    "display(andrey.viz.draw(gin_out.structure))"
   ]
  }
 ],
 "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
}
