{ "cells": [ { "cell_type": "markdown", "id": "permutation_based-0", "metadata": {}, "source": "# Permutation-based search (BOSS / GRaSP)\n\n[](https://colab.research.google.com/github/Abel-ai-lab/andrey/blob/main/examples/permutation_based.ipynb) {download}`Download this notebook <../../../examples/permutation_based.ipynb>`\n\nBOSS and GRaSP search over orderings of the variables. For an ordering, a BIC score chooses each\nvariable's parents from the variables before it; the search keeps the ordering with the best score.\nBoth return a CPDAG, like GES, and suit linear-Gaussian data." }, { "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": "permutation_based-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": "permutation_based-2", "metadata": {}, "source": [ "## Data\n", "\n", "`linear_gauss_er` draws an Erdos-Renyi graph with linear mechanisms and Gaussian noise: here 10\n", "variables and 2,000 samples." ] }, { "cell_type": "code", "execution_count": 3, "id": "permutation_based-3", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2000 observations, 10 variables, seed 7\n" ] }, { "data": { "text/html": [ "" ], "text/plain": [ "''" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "seed = 7\n", "dataset = benchmarks.scm(\"linear_gauss_er\", d=10).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": "permutation_based-4", "metadata": {}, "source": [ "## Fit BOSS and GRaSP\n", "\n", "BOSS starts from an ordering computed from the data and visits the variables in a random order;\n", "GRaSP starts from a random ordering. `seed` fixes the random choices, so a run repeats exactly;\n", "without `seed`, the `ANDREY_SEED` environment variable sets it (default 0). `metadata[\"score\"]` is\n", "the BIC objective of the final ordering, which the search minimizes. A larger `lambda_value` weighs\n", "the BIC penalty more and gives a sparser graph." ] }, { "cell_type": "code", "execution_count": 4, "id": "permutation_based-5", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "BOSS: cpdag, score -22398.8\n" ] }, { "data": { "text/html": [ "" ], "text/plain": [ "''" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "GRaSP: cpdag, score -22406.2\n" ] }, { "data": { "text/html": [ "" ], "text/plain": [ "''" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "results = {\n", " \"BOSS\": andrey.boss(X, seed=0),\n", " \"GRaSP\": andrey.grasp(X, seed=0),\n", "}\n", "for name, result in results.items():\n", " print(f\"{name}: {result.structure.kind}, score {result.metadata['score']:.1f}\")\n", " display(andrey.viz.draw(result.structure))" ] }, { "cell_type": "markdown", "id": "permutation_based-6", "metadata": {}, "source": [ "## Compare with GES over ten datasets\n", "\n", "GES searches over CPDAGs with a BIC score as well. Structural Hamming distance (SHD) counts errors\n", "against the truth's CPDAG; lower is better. The empty graph's SHD is the number of true edges, so\n", "an estimate at or above it has not improved on returning no edges. One dataset can favor any\n", "method, so the table covers ten seeds and their median." ] }, { "cell_type": "code", "execution_count": 5, "id": "permutation_based-7", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
| seed | \n", "0 | \n", "1 | \n", "2 | \n", "3 | \n", "4 | \n", "5 | \n", "6 | \n", "7 | \n", "8 | \n", "9 | \n", "median | \n", "
|---|---|---|---|---|---|---|---|---|---|---|---|
| Empty graph | \n", "15 | \n", "15 | \n", "15 | \n", "15 | \n", "15 | \n", "15 | \n", "15 | \n", "15 | \n", "15 | \n", "15 | \n", "15.0 | \n", "
| GES | \n", "21 | \n", "10 | \n", "0 | \n", "10 | \n", "17 | \n", "7 | \n", "11 | \n", "17 | \n", "0 | \n", "0 | \n", "10.0 | \n", "
| BOSS | \n", "6 | \n", "8 | \n", "0 | \n", "10 | \n", "1 | \n", "10 | \n", "13 | \n", "11 | \n", "0 | \n", "14 | \n", "9.0 | \n", "
| GRaSP | \n", "6 | \n", "0 | \n", "0 | \n", "1 | \n", "1 | \n", "0 | \n", "1 | \n", "6 | \n", "0 | \n", "0 | \n", "0.5 | \n", "