{ "cells": [ { "cell_type": "markdown", "id": "time_series-0", "metadata": {}, "source": "# Time series (PCMCI, PCMCI+, LPCMCI, Granger, VARMA-LiNGAM, longitudinal LiNGAM)\n\n[](https://colab.research.google.com/github/Abel-ai-lab/andrey/blob/main/examples/time_series.ipynb) {download}`Download this notebook <../../../examples/time_series.ipynb>`\n\nSix methods find causes in data measured over time. PCMCI and Granger find the links from earlier\nsteps; PCMCI+ also finds links within one step, and LPCMCI allows series that are not measured.\nVARMA-LiNGAM estimates the weights of the links within one step and from the step before.\nLongitudinal LiNGAM takes a panel instead: the same units measured at a few time points. Each\nreturns a `TemporalStructure`, one graph per lag." }, { "cell_type": "markdown", "id": "time_series-1", "metadata": {}, "source": [ "## Start\n", "\n", "Choose a CPU runtime and run this cell first. In Colab it installs `andrey-core==0.9.0` from PyPI;\n", "local kernels use the installed environment." ] }, { "cell_type": "code", "execution_count": 1, "id": "time_series-2", "metadata": { "tags": [ "colab-setup" ] }, "outputs": [], "source": [ "import os\n", "import sys\n", "\n", "IN_COLAB = \"google.colab\" in sys.modules\n", "\n", "if IN_COLAB:\n", " os.environ[\"ANDREY_DEVICE\"] = \"cpu\"\n", " !pip install --quiet \"andrey-core[viz]==0.9.0\" pandas" ] }, { "cell_type": "code", "execution_count": 2, "id": "time_series-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" ] }, { "cell_type": "markdown", "id": "time_series-4", "metadata": {}, "source": [ "## Data\n", "\n", "`sample_scm` draws one series of 1,000 time steps over 4 variables: a vector autoregression with\n", "one lag, links within each step from a random DAG, and uniform noise. The drawing merges the lags\n", "into one graph; a loop is a variable's dependence on its own past." ] }, { "cell_type": "code", "execution_count": 3, "id": "time_series-5", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1000 time steps of 4 variables\n", "temporal | 4 nodes | 2 lags\n", "\n", " lag 0 | 2 edges (2 directed)\n", " 0 -> 1\n", " 0 -> 3\n", "\n", " lag 1 | 10 edges (10 directed)\n", " 0 -> 0\n", " 0 -> 1\n", " 1 -> 0\n", " 0 -> 3\n", " 1 -> 1\n", " 2 -> 1\n", " 1 -> 3\n", " 2 -> 2\n", " 2 -> 3\n", " 3 -> 3\n" ] }, { "data": { "text/html": [ "" ], "text/plain": [ "''" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "series = sample_scm(\n", " graph=\"erdos_renyi\",\n", " functional=\"var\",\n", " noise=\"uniform\",\n", " d=4,\n", " n=1000,\n", " seed=1,\n", " density=1.0,\n", ")\n", "X, truth = series.data, series.graph\n", "print(f\"{X.shape[0]} time steps of {X.shape[1]} variables\")\n", "print(truth)\n", "display(andrey.viz.draw(truth.summary_graph()))" ] }, { "cell_type": "markdown", "id": "time_series-6", "metadata": {}, "source": [ "## Links from the past: PCMCI and Granger\n", "\n", "PCMCI tests each lagged link with partial correlations, given the strongest earlier causes it\n", "finds first; `tau_max` is the largest lag tested. Granger regresses each variable on the past\n", "`maxlag` steps of every variable with a cross-validated lasso. Neither tests links within one step." ] }, { "cell_type": "code", "execution_count": 4, "id": "time_series-7", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "pcmci temporal | 4 nodes | 1 lags\n", "\n", " lag 1 | 10 edges (10 directed)\n", " 0 -> 0\n", " 0 -> 1\n", " 1 -> 0\n", " 0 -> 3\n", " 1 -> 1\n", " 2 -> 1\n", " 1 -> 3\n", " 2 -> 2\n", " 2 -> 3\n", " 3 -> 3\n", "\n", " p_values: a list of 1, too long to print; read metadata['p_values']\n", " statistics: a list of 1, too long to print; read metadata['statistics']\n", " parents: [[[1, 1], [0, 1]], [[1, 1], [0, 1], [2, 1]], [[2, 1], [3, 1]], [[1, 1], [2, 1], [0, 1]]]\n", "\n", "granger temporal | 4 nodes | 1 lags\n", "\n", " lag 1 | 13 edges (13 directed)\n", " 0 -> 0\n", " 0 -> 1\n", " 1 -> 0\n", " 2 -> 0\n", " 0 -> 3\n", " 1 -> 1\n", " 2 -> 1\n", " 1 -> 3\n", " 3 -> 1\n", " 2 -> 2\n", " 2 -> 3\n", " 3 -> 2\n", " 3 -> 3\n" ] } ], "source": [ "print(andrey.pcmci(X, tau_max=1), end=\"\\n\\n\")\n", "print(andrey.granger(X, maxlag=1))" ] }, { "cell_type": "markdown", "id": "time_series-8", "metadata": {}, "source": [ "## Links within a step: PCMCI+ and LPCMCI\n", "\n", "PCMCI+ adds the same-step links and orients them where the data allow; lag 0 is a CPDAG. LPCMCI\n", "allows hidden series, so it returns a partial ancestral graph (PAG): a circle marks an end the data\n", "leave open." ] }, { "cell_type": "code", "execution_count": 5, "id": "time_series-9", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "pcmciplus temporal | 4 nodes | 2 lags\n", "\n", " lag 0 | 3 edges (3 directed)\n", " 0 -> 1\n", " 3 -> 0\n", " 1 -> 3\n", "\n", " lag 1 | 7 edges (7 directed)\n", " 0 -> 0\n", " 0 -> 1\n", " 1 -> 0\n", " 1 -> 1\n", " 2 -> 1\n", " 1 -> 3\n", " 2 -> 2\n", "\n", " p_values: a list of 2, too long to print; read metadata['p_values']\n", " statistics: a list of 2, too long to print; read metadata['statistics']\n", " parents: [[[1, 1], [0, 1]], [[1, 1], [0, 1], [2, 1]], [[2, 1]], [[1, 1], [2, 1]]]\n", " conflicts: []\n", " ambiguous_triples: [[0, 1, 1, 3]]\n", "\n", "lpcmci temporal | 4 nodes | 2 lags\n", "\n", " lag 0 | 2 edges (1 partially_directed, 1 directed)\n", " 0 o-> 1\n", " 0 -> 3\n", "\n", " lag 1 | 0 edges (no edges)\n", "\n", " links: a list of 2, too long to print; read metadata['links']\n", " p_values: a list of 2, too long to print; read metadata['p_values']\n", " statistics: a list of 2, too long to print; read metadata['statistics']\n", " parents: [[], [], [], [[0, 0]]]\n", " conflicts: []\n" ] } ], "source": [ "print(andrey.pcmciplus(X, tau_max=1), end=\"\\n\\n\")\n", "print(andrey.lpcmci(X, tau_max=1))" ] }, { "cell_type": "markdown", "id": "time_series-10", "metadata": {}, "source": [ "## Weights: VARMA-LiNGAM\n", "\n", "VARMA-LiNGAM uses the non-Gaussian noise to order the variables within a step, and estimates the\n", "weight of every link. `prune=True` sets small weights to zero." ] }, { "cell_type": "code", "execution_count": 6, "id": "time_series-11", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "varmalingam temporal | 4 nodes | 2 lags\n", "\n", " lag 0 | 2 edges (2 directed)\n", " 0 -> 1\n", " 0 -> 3\n", "\n", " lag 1 | 7 edges (7 directed)\n", " 0 -> 0\n", " 1 -> 0\n", " 1 -> 1\n", " 2 -> 1\n", " 1 -> 3\n", " 2 -> 3\n", " 3 -> 3\n", "\n", " order: 0 -> 3 -> 2 -> 1\n", "\n", "lag 0 weights, row = cause:\n", "[[ 0. 1.19 0. -1.38]\n", " [ 0. 0. 0. 0. ]\n", " [ 0. 0. 0. 0. ]\n", " [ 0. 0. 0. 0. ]]\n" ] } ], "source": [ "out = andrey.varma_lingam(X, order=(1, 1), prune=True)\n", "print(out, end=\"\\n\\n\")\n", "print(\"lag 0 weights, row = cause:\")\n", "print(np.round(out.structure.lag_weights[0], 2))" ] }, { "cell_type": "markdown", "id": "time_series-12", "metadata": {}, "source": [ "## Compare the links\n", "\n", "Each method's links at lags 0 and 1 beside the true ones, a variable's dependence on its own past\n", "included. A same-step link counts once, whatever its direction; a lagged link counts with its\n", "direction. LPCMCI is left out: its lagged graphs keep only the links it orients as causes, so its\n", "PAG above is the answer to read." ] }, { "cell_type": "code", "execution_count": 7, "id": "time_series-13", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
| \n", " | true links found | \n", "extra links | \n", "
|---|---|---|
| PCMCI | \n", "10 of 12 | \n", "0 | \n", "
| Granger | \n", "10 of 12 | \n", "3 | \n", "
| PCMCI+ | \n", "9 of 12 | \n", "1 | \n", "
| VARMA-LiNGAM | \n", "9 of 12 | \n", "0 | \n", "