{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| hide\n",
    "!pip install -Uqq nixtla"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| hide \n",
    "from nixtla.utils import in_colab"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| hide \n",
    "IN_COLAB = in_colab()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| hide\n",
    "if not IN_COLAB:\n",
    "    from nixtla.utils import colab_badge\n",
    "    from dotenv import load_dotenv"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Univariate vs. Multivariate Anomaly Detection"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this notebook, we show how to detect anomalies across multiple time series using the multivariate method. This method is great for situations where you have several sensors or related time series. We also explain how it works differently from the univariate method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "[![](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Nixtla/nixtla/blob/main/nbs/docs/capabilities/online-anomaly-detection/03_univariate_vs_multivariate_anomaly_detection.ipynb)"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#| echo: false\n",
    "if not IN_COLAB:\n",
    "    load_dotenv()\n",
    "    colab_badge('docs/capabilities/online-anomaly-detection/03_univariate_vs_multivariate_anomaly_detection')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "from nixtla import NixtlaClient"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Utility function to plot anomalies\n",
    "def plot_anomalies(df, unique_ids, rows, cols):\n",
    "    fig, axes = plt.subplots(rows, cols, figsize=(12, rows * 2))\n",
    "    for i, (ax, uid) in enumerate(zip(axes.flatten(), unique_ids)):\n",
    "        filtered_df = df[df['unique_id'] == uid]\n",
    "        ax.plot(filtered_df['ts'], filtered_df['y'], color='navy', alpha=0.8, label='y')\n",
    "        ax.plot(filtered_df['ts'], filtered_df['TimeGPT'], color='orchid', alpha=0.7, label='TimeGPT')\n",
    "        ax.scatter(filtered_df.loc[filtered_df['anomaly'] == 1, 'ts'], filtered_df.loc[filtered_df['anomaly'] == 1, 'y'], color='orchid', label='Anomalies Detected')\n",
    "        ax.set_title(f\"Unique_id: {uid}\", fontsize=8); ax.tick_params(axis='x', labelsize=6)\n",
    "    fig.legend(loc='upper center', ncol=3, fontsize=8, labels=['y', 'TimeGPT', 'Anomaly'])\n",
    "    plt.tight_layout(rect=[0, 0, 1, 0.95])\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "nixtla_client = NixtlaClient(\n",
    "    # defaults to os.environ.get(\"NIXTLA_API_KEY\")\n",
    "    api_key = 'my_api_key_provided_by_nixtla'\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> 👍 Use an Azure AI endpoint\n",
    "> \n",
    "> To use an Azure AI endpoint, set the `base_url` argument:\n",
    "> \n",
    "> `nixtla_client = NixtlaClient(base_url=\"you azure ai endpoint\", api_key=\"your api_key\")`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| hide\n",
    "if not IN_COLAB:\n",
    "    nixtla_client = NixtlaClient()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Dataset\n",
    "In this notebook example from SMD dataset. SMD (Server Machine Dataset) is a benchmark dataset for anomaly detection with multiple time series. It monitors abnormal patterns in server machine data. \n",
    "\n",
    "Here, we use a set of monitoring data from a single server machine(machine-1-1) that has 38 time series. Each time series represents a different metric being monitored, such as CPU usage, memory usage, disk I/O, and network I/O. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "38"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df = pd.read_csv('https://datasets-nixtla.s3.us-east-1.amazonaws.com/SMD_test.csv', parse_dates=['ts'])\n",
    "df.unique_id.nunique()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Univariate vs. Multivariate Method"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.1 Univariate Method\n",
    "Univariate anomaly detection analyzes each time series independently, flagging anomalies based on deviations from its historical patterns. This method is effective for detecting issues within a single metric but ignores dependencies across multiple series. As a result, it may miss collective anomalies or flag irrelevant ones in scenarios where anomalies arise from patterns across multiple series, such as system-wide failures, correlated financial metrics, or interconnected processes. That’s when multivariate anomaly detection comes into play."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:nixtla.nixtla_client:Validating inputs...\n",
      "INFO:nixtla.nixtla_client:Preprocessing dataframes...\n",
      "WARNING:nixtla.nixtla_client:Detection size is large. Using the entire series to compute the anomaly threshold...\n",
      "INFO:nixtla.nixtla_client:Calling Online Anomaly Detector Endpoint...\n"
     ]
    }
   ],
   "source": [
    "anomaly_online = nixtla_client.detect_anomalies_online(\n",
    "    df[['ts', 'y', 'unique_id']],\n",
    "    time_col='ts',\n",
    "    target_col='y',\n",
    "    freq='h',                        \n",
    "    h=24,                            \n",
    "    level=95,                        \n",
    "    detection_size=475,              \n",
    "    threshold_method = 'univariate'  # Specify the threshold_method as 'univariate'\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 131344 characters]",
      "text/plain": [
       "<Figure size 1200x400 with 4 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display_ids = ['machine-1-1_y_0', 'machine-1-1_y_1', 'machine-1-1_y_6', 'machine-1-1_y_29']\n",
    "plot_anomalies(anomaly_online, display_ids, rows=2, cols=2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.2 Multivariate Method\n",
    "The multivariate anomaly detection method considers multiple time series simultaneously. Instead of treating each series in isolation, it accumulates the anomaly scores for the same time step across all series and determines whether the step is anomalous based on the combined score. This method is particularly useful in scenarios where anomalies are only significant when multiple series collectively indicate an issue. To apply multivariate detection, simply set the `threshold_method` parameter to `multivariate`.\n",
    "\n",
    "We can see that the anomalies detected for each time series occur at the same time step since they rely on the accumulated error across all series."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:nixtla.nixtla_client:Validating inputs...\n",
      "INFO:nixtla.nixtla_client:Preprocessing dataframes...\n",
      "WARNING:nixtla.nixtla_client:Detection size is large. Using the entire series to compute the anomaly threshold...\n",
      "INFO:nixtla.nixtla_client:Calling Online Anomaly Detector Endpoint...\n"
     ]
    }
   ],
   "source": [
    "anomaly_online_multi = nixtla_client.detect_anomalies_online(\n",
    "    df[['ts', 'y', 'unique_id']],\n",
    "    time_col='ts',\n",
    "    target_col='y',\n",
    "    freq='h',\n",
    "    h=24,\n",
    "    level=95,\n",
    "    detection_size=475,\n",
    "    threshold_method = 'multivariate'   # Specify the threshold_method as 'multivariate'\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 131496 characters]",
      "text/plain": [
       "<Figure size 1200x400 with 4 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plot_anomalies(anomaly_online_multi, display_ids, rows=2, cols=2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> 📘 In multiseries anomaly detection, error scores from all time series are aggregated at each time step, and a threshold is applied to identify significant deviations. If the aggregated error exceeds the threshold, the time step is flagged as anomalous across all series, capturing system-wide patterns."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "python3",
   "language": "python",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
