{
 "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": [
    "# Adjusting the Anomaly Detection Process\n",
    "\n",
    "This notebook explores methods to improve anomaly detection by refining the detection process. TimeGPT leverages its forecasting capabilities to identify anomalies based on forecast errors. By optimizing forecast parameters and accuracy, you can align anomaly detection with specific use cases and improve its accuracy."
   ]
  },
  {
   "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/02_adjusting_detection_process.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/02_adjusting_detection_process')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "from nixtla import NixtlaClient\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Utility function to plot anomalies\n",
    "def plot_anomaly(df, anomaly_df, time_col = 'ts', target_col = 'y'):\n",
    "    merged_df = pd.merge(df.tail(300), anomaly_df[[time_col, 'anomaly', 'TimeGPT']], on=time_col, how='left')\n",
    "    plt.figure(figsize=(12, 2))\n",
    "    plt.plot(merged_df[time_col], merged_df[target_col], label='y', color='navy', alpha=0.8)\n",
    "    plt.plot(merged_df[time_col], merged_df['TimeGPT'], label='TimeGPT', color='orchid', alpha=0.7)\n",
    "    plt.scatter(merged_df.loc[merged_df['anomaly'] == True, time_col], merged_df.loc[merged_df['anomaly'] == True, target_col], color='orchid', label='Anomalies Detected')\n",
    "    plt.legend()\n",
    "    plt.tight_layout()\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. Conduct anomaly detection\n",
    "After initializing an instance of `NixtlaClient`, let’s explore an example using the Peyton Manning dataset."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "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>unique_id</th>\n",
       "      <th>ds</th>\n",
       "      <th>y</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>2764</th>\n",
       "      <td>0</td>\n",
       "      <td>2015-07-05</td>\n",
       "      <td>6.499787</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2765</th>\n",
       "      <td>0</td>\n",
       "      <td>2015-07-06</td>\n",
       "      <td>6.859615</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2766</th>\n",
       "      <td>0</td>\n",
       "      <td>2015-07-07</td>\n",
       "      <td>6.881411</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2767</th>\n",
       "      <td>0</td>\n",
       "      <td>2015-07-08</td>\n",
       "      <td>6.997596</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2768</th>\n",
       "      <td>0</td>\n",
       "      <td>2015-07-09</td>\n",
       "      <td>7.152269</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "      unique_id         ds         y\n",
       "2764          0 2015-07-05  6.499787\n",
       "2765          0 2015-07-06  6.859615\n",
       "2766          0 2015-07-07  6.881411\n",
       "2767          0 2015-07-08  6.997596\n",
       "2768          0 2015-07-09  7.152269"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df = pd.read_csv('https://datasets-nixtla.s3.amazonaws.com/peyton-manning.csv',parse_dates = ['ds']).tail(200)\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First, let's set a baseline by using only the default parameters of the method."
   ]
  },
  {
   "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": [
    "# Base case for anomaly detection using detect_anomaly_online\n",
    "anomaly_df = nixtla_client.detect_anomalies_online(\n",
    "    df,\n",
    "    freq='D',\n",
    "    h=14,\n",
    "    level=80,\n",
    "    detection_size=150\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 65156 characters]",
      "text/plain": [
       "<Figure size 1200x200 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plot_anomaly(df, anomaly_df, time_col = 'ds', target_col = 'y')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Adjusting the Anomaly Detection Process\n",
    "This section explores two key approaches to enhancing anomaly detection: \n",
    "\n",
    "1. fine-tuning the model to boost forecast accuracy\n",
    "2. adjusting forecast horizon and step sizes to optimize time series segmentation and analysis. \n",
    "\n",
    "These strategies allow for a more tailored and effective anomaly detection process."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.1 Fine-tune TimeGPT\n",
    "TimeGPT uses forecast errors for anomaly detection, so improving forecast accuracy reduces noise in the errors, leading to better anomaly detection. You can fine-tune the model using the following parameters:\n",
    "\n",
    "* `finetune_steps`: Number of steps for finetuning TimeGPT on new data.\n",
    "* `finetune_depth`: Level of fine-tuning controlling the quantity of parameters being fine-tuned (see our [in-depth tutorial](https://docs.nixtla.io/docs/tutorials-controlling_the_level_of_fine_tuning))\n",
    "* `finetune_loss`: Loss function to be used during the fine-tuning process."
   ]
  },
  {
   "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_ft = nixtla_client.detect_anomalies_online(\n",
    "    df,\n",
    "    freq='D',\n",
    "    h=14,\n",
    "    level=80,\n",
    "    detection_size=150,\n",
    "    finetune_steps = 10,    # Number of steps for fine-tuning TimeGPT on new data\n",
    "    finetune_depth = 2,     # Intensity of finetuning\n",
    "    finetune_loss = 'mae'   # Loss function used during the finetuning process\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 64912 characters]",
      "text/plain": [
       "<Figure size 1200x200 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plot_anomaly(df, anomaly_online_ft, time_col = 'ds', target_col = 'y')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "From the plot above, we can see that fewer anomalies were detected by the model, since the fine-tuning process helps TimeGPT better forecast the series."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.2 Change forecast horizon and step\n",
    "Similar to cross-validation, the anomaly detection method generates forecasts for historical data by splitting the time series into multiple windows. The way these windows are defined can impact the anomaly detection results. Two key parameters control this process:\n",
    "\n",
    "* `h`: Specifies how many steps into the future the forecast is made for each window.\n",
    "* `step_size`: Determines the interval between the starting points of consecutive windows.\n",
    "\n",
    "Note that when `step_size` is smaller than `h`, then we get overlapping windows. This can make the detection process more robust, as TimeGPT will see the same time step more than once. However, this comes with a computational cost, since the same time step will be predicted more than once."
   ]
  },
  {
   "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_df_horizon = nixtla_client.detect_anomalies_online(\n",
    "    df,\n",
    "    time_col='ds',\n",
    "    target_col='y',\n",
    "    freq='D',\n",
    "    h=2,                 # Forecast horizon\n",
    "    step_size = 1,       # Step size for moving through the time series data\n",
    "    level=80,            \n",
    "    detection_size=150\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 64860 characters]",
      "text/plain": [
       "<Figure size 1200x200 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plot_anomaly(df, anomaly_df_horizon, time_col = 'ds', target_col = 'y')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> 📘 **Balancing h and step_size depends on your data:** For frequent, short-lived anomalies, use a smaller `h` to focus on short-term predictions and a smaller `step_size` to increase overlap and sensitivity. For smooth trends or long-term patterns, use a larger `h` to capture broader anomalies and a larger `step_size` to reduce noise and computational cost."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "python3",
   "language": "python",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
