{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Requirement already satisfied: shap in c:\\users\\ospra\\miniconda3\\envs\\nixtla\\lib\\site-packages (0.46.0)\n",
      "Requirement already satisfied: numpy in c:\\users\\ospra\\miniconda3\\envs\\nixtla\\lib\\site-packages (from shap) (1.26.4)\n",
      "Requirement already satisfied: scipy in c:\\users\\ospra\\miniconda3\\envs\\nixtla\\lib\\site-packages (from shap) (1.14.1)\n",
      "Requirement already satisfied: scikit-learn in c:\\users\\ospra\\miniconda3\\envs\\nixtla\\lib\\site-packages (from shap) (1.5.2)\n",
      "Requirement already satisfied: pandas in c:\\users\\ospra\\miniconda3\\envs\\nixtla\\lib\\site-packages (from shap) (2.2.3)\n",
      "Requirement already satisfied: tqdm>=4.27.0 in c:\\users\\ospra\\miniconda3\\envs\\nixtla\\lib\\site-packages (from shap) (4.67.1)\n",
      "Requirement already satisfied: packaging>20.9 in c:\\users\\ospra\\miniconda3\\envs\\nixtla\\lib\\site-packages (from shap) (24.2)\n",
      "Requirement already satisfied: slicer==0.0.8 in c:\\users\\ospra\\miniconda3\\envs\\nixtla\\lib\\site-packages (from shap) (0.0.8)\n",
      "Requirement already satisfied: numba in c:\\users\\ospra\\miniconda3\\envs\\nixtla\\lib\\site-packages (from shap) (0.60.0)\n",
      "Requirement already satisfied: cloudpickle in c:\\users\\ospra\\miniconda3\\envs\\nixtla\\lib\\site-packages (from shap) (3.1.0)\n",
      "Requirement already satisfied: colorama in c:\\users\\ospra\\miniconda3\\envs\\nixtla\\lib\\site-packages (from tqdm>=4.27.0->shap) (0.4.6)\n",
      "Requirement already satisfied: llvmlite<0.44,>=0.43.0dev0 in c:\\users\\ospra\\miniconda3\\envs\\nixtla\\lib\\site-packages (from numba->shap) (0.43.0)\n",
      "Requirement already satisfied: python-dateutil>=2.8.2 in c:\\users\\ospra\\miniconda3\\envs\\nixtla\\lib\\site-packages (from pandas->shap) (2.9.0.post0)\n",
      "Requirement already satisfied: pytz>=2020.1 in c:\\users\\ospra\\miniconda3\\envs\\nixtla\\lib\\site-packages (from pandas->shap) (2024.2)\n",
      "Requirement already satisfied: tzdata>=2022.7 in c:\\users\\ospra\\miniconda3\\envs\\nixtla\\lib\\site-packages (from pandas->shap) (2024.2)\n",
      "Requirement already satisfied: joblib>=1.2.0 in c:\\users\\ospra\\miniconda3\\envs\\nixtla\\lib\\site-packages (from scikit-learn->shap) (1.4.2)\n",
      "Requirement already satisfied: threadpoolctl>=3.1.0 in c:\\users\\ospra\\miniconda3\\envs\\nixtla\\lib\\site-packages (from scikit-learn->shap) (3.5.0)\n",
      "Requirement already satisfied: six>=1.5 in c:\\users\\ospra\\miniconda3\\envs\\nixtla\\lib\\site-packages (from python-dateutil>=2.8.2->pandas->shap) (1.16.0)\n"
     ]
    }
   ],
   "source": [
    "#| hide\n",
    "!pip install -Uqq nixtla\n",
    "!pip install shap"
   ]
  },
  {
   "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": [
    "# SHAP Values for TimeGPT and TimeGEN"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "SHAP (SHapley Additive exPlanation) values use game theory to explain the output of any machine learning model. It allows us to explore in detail how exogenous features impact the final forecast, both at a single forecast step or over the entire horizon. \n",
    "\n",
    "When you forecast with exogenous features, you can access the SHAP values for all series at each prediction step, and use the popular [shap](https://shap.readthedocs.io/en/latest/) Python package to make different plots and explain the impact of the features.\n",
    "\n",
    "This tutorial assumes knowledge on forecasting with exogenous features, so make sure to read our tutorial on [exogenous variables](https://docs.nixtla.io/docs/tutorials-exogenous_variables). Also, the `shap` package must be installed separately as it is not a dependency of `nixtla`.\n",
    "\n",
    "`shap` can be installed from either [PyPI](https://pypi.org/project/shap/) or [conda-forge](https://anaconda.org/conda-forge/shap):\n",
    "```\n",
    "pip install shap\n",
    "\n",
    "or\n",
    "\n",
    "conda install -c conda-forge shap\n",
    "```\n",
    "For the official documentation of SHAP, visit: https://shap.readthedocs.io/en/latest/"
   ]
  },
  {
   "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/tutorials/21_shap_values.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/tutorials/21_shap_values')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Import packages\n",
    "First, we import the required packages and initialize the Nixtla client."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "from nixtla import NixtlaClient"
   ]
  },
  {
   "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, remember to set also 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": [
    "## 2. Load data\n",
    "\n",
    "In this example on SHAP values, we will use exogenous variables (also known as covariates) to improve the accuracy of electricity market forecasts. We'll work with a well-known dataset called `EPF`, which is publicly accessible [here](https://zenodo.org/records/4624805). \n",
    "\n",
    "This dataset includes data from five different electricity markets, each with unique price dynamics, such as varying frequencies and occurrences of negative prices, zeros, and price spikes. Since electricity prices are influenced by exogenous factors, each dataset also contains two additional time series: day-ahead forecasts of two significant exogenous factors specific to each market.\n",
    "\n",
    "For simplicity, we will focus on the Belgian electricity market (BE). This dataset includes hourly prices (`y`), day-ahead forecasts of load (`Exogenous1`), and electricity generation (`Exogenous2`). It also includes one-hot encoding to indicate whether a specific date is a specific day of the week. Eg.: Monday (`day_0 = 1`), a Tuesday (`day_1 = 1`), and so on.\n",
    "\n",
    "If your data depends on exogenous factors or covariates such as prices, discounts, special holidays, weather, etc., you can follow a similar structure."
   ]
  },
  {
   "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",
       "      <th>Exogenous1</th>\n",
       "      <th>Exogenous2</th>\n",
       "      <th>day_0</th>\n",
       "      <th>day_1</th>\n",
       "      <th>day_2</th>\n",
       "      <th>day_3</th>\n",
       "      <th>day_4</th>\n",
       "      <th>day_5</th>\n",
       "      <th>day_6</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>BE</td>\n",
       "      <td>2016-10-22 00:00:00</td>\n",
       "      <td>70.00</td>\n",
       "      <td>57253.0</td>\n",
       "      <td>49593.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>BE</td>\n",
       "      <td>2016-10-22 01:00:00</td>\n",
       "      <td>37.10</td>\n",
       "      <td>51887.0</td>\n",
       "      <td>46073.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>BE</td>\n",
       "      <td>2016-10-22 02:00:00</td>\n",
       "      <td>37.10</td>\n",
       "      <td>51896.0</td>\n",
       "      <td>44927.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>BE</td>\n",
       "      <td>2016-10-22 03:00:00</td>\n",
       "      <td>44.75</td>\n",
       "      <td>48428.0</td>\n",
       "      <td>44483.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>BE</td>\n",
       "      <td>2016-10-22 04:00:00</td>\n",
       "      <td>37.10</td>\n",
       "      <td>46721.0</td>\n",
       "      <td>44338.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "  unique_id                   ds      y  Exogenous1  Exogenous2  day_0  day_1  \\\n",
       "0        BE  2016-10-22 00:00:00  70.00     57253.0     49593.0    0.0    0.0   \n",
       "1        BE  2016-10-22 01:00:00  37.10     51887.0     46073.0    0.0    0.0   \n",
       "2        BE  2016-10-22 02:00:00  37.10     51896.0     44927.0    0.0    0.0   \n",
       "3        BE  2016-10-22 03:00:00  44.75     48428.0     44483.0    0.0    0.0   \n",
       "4        BE  2016-10-22 04:00:00  37.10     46721.0     44338.0    0.0    0.0   \n",
       "\n",
       "   day_2  day_3  day_4  day_5  day_6  \n",
       "0    0.0    0.0    0.0    1.0    0.0  \n",
       "1    0.0    0.0    0.0    1.0    0.0  \n",
       "2    0.0    0.0    0.0    1.0    0.0  \n",
       "3    0.0    0.0    0.0    1.0    0.0  \n",
       "4    0.0    0.0    0.0    1.0    0.0  "
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "market = \"BE\"\n",
    "df = pd.read_csv('https://raw.githubusercontent.com/Nixtla/transfer-learning-time-series/main/datasets/electricity-short-with-ex-vars.csv')\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Forecasting electricity prices using exogenous variables\n",
    "\n",
    "To produce forecasts we also have to add the future values of the exogenous variables. \n",
    "\n",
    "If your forecast depends on other variables, it is important to ensure that those variables are available at the time of forecasting. In this example, we know that the price of electricity depends on the demand (`Exogenous1`) and the quantity produced (`Exogenous2`). Thus, we need to have those future values available at the time of forecasting. If those values were not available, we can always [use TimeGPT to forecast them](https://docs.nixtla.io/docs/tutorials-exogenous_variables).\n",
    "\n",
    "Here, we read a dataset that contains the future values of our features. In this case, we want to predict 24 steps ahead, therefore each `unique_id` will have 24 observations.\n",
    "\n",
    "::: {.callout-important}\n",
    "If you want to use exogenous variables when forecasting with TimeGPT, you need to have the future values of those exogenous variables too.\n",
    "::: "
   ]
  },
  {
   "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>Exogenous1</th>\n",
       "      <th>Exogenous2</th>\n",
       "      <th>day_0</th>\n",
       "      <th>day_1</th>\n",
       "      <th>day_2</th>\n",
       "      <th>day_3</th>\n",
       "      <th>day_4</th>\n",
       "      <th>day_5</th>\n",
       "      <th>day_6</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>BE</td>\n",
       "      <td>2016-12-31 00:00:00</td>\n",
       "      <td>70318.0</td>\n",
       "      <td>64108.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>BE</td>\n",
       "      <td>2016-12-31 01:00:00</td>\n",
       "      <td>67898.0</td>\n",
       "      <td>62492.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>BE</td>\n",
       "      <td>2016-12-31 02:00:00</td>\n",
       "      <td>68379.0</td>\n",
       "      <td>61571.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>BE</td>\n",
       "      <td>2016-12-31 03:00:00</td>\n",
       "      <td>64972.0</td>\n",
       "      <td>60381.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>BE</td>\n",
       "      <td>2016-12-31 04:00:00</td>\n",
       "      <td>62900.0</td>\n",
       "      <td>60298.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "  unique_id                   ds  Exogenous1  Exogenous2  day_0  day_1  day_2  \\\n",
       "0        BE  2016-12-31 00:00:00     70318.0     64108.0    0.0    0.0    0.0   \n",
       "1        BE  2016-12-31 01:00:00     67898.0     62492.0    0.0    0.0    0.0   \n",
       "2        BE  2016-12-31 02:00:00     68379.0     61571.0    0.0    0.0    0.0   \n",
       "3        BE  2016-12-31 03:00:00     64972.0     60381.0    0.0    0.0    0.0   \n",
       "4        BE  2016-12-31 04:00:00     62900.0     60298.0    0.0    0.0    0.0   \n",
       "\n",
       "   day_3  day_4  day_5  day_6  \n",
       "0    0.0    0.0    1.0    0.0  \n",
       "1    0.0    0.0    1.0    0.0  \n",
       "2    0.0    0.0    1.0    0.0  \n",
       "3    0.0    0.0    1.0    0.0  \n",
       "4    0.0    0.0    1.0    0.0  "
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "future_ex_vars_df = pd.read_csv('https://raw.githubusercontent.com/Nixtla/transfer-learning-time-series/main/datasets/electricity-short-future-ex-vars.csv')\n",
    "future_ex_vars_df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's call the `forecast` method, adding this information. To access the SHAP values, we also need to specify `feature_contributions=True` in the `forecast` 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:Inferred freq: h\n",
      "INFO:nixtla.nixtla_client:Querying model metadata...\n",
      "INFO:nixtla.nixtla_client:Preprocessing dataframes...\n",
      "INFO:nixtla.nixtla_client:Using future exogenous features: ['Exogenous1', 'Exogenous2', 'day_0', 'day_1', 'day_2', 'day_3', 'day_4', 'day_5', 'day_6']\n",
      "INFO:nixtla.nixtla_client:Calling Forecast Endpoint...\n"
     ]
    },
    {
     "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>TimeGPT</th>\n",
       "      <th>TimeGPT-hi-80</th>\n",
       "      <th>TimeGPT-hi-90</th>\n",
       "      <th>TimeGPT-lo-80</th>\n",
       "      <th>TimeGPT-lo-90</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>BE</td>\n",
       "      <td>2016-12-31 00:00:00</td>\n",
       "      <td>51.632830</td>\n",
       "      <td>61.598820</td>\n",
       "      <td>66.088295</td>\n",
       "      <td>41.666843</td>\n",
       "      <td>37.177372</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>BE</td>\n",
       "      <td>2016-12-31 01:00:00</td>\n",
       "      <td>45.750877</td>\n",
       "      <td>54.611988</td>\n",
       "      <td>60.176445</td>\n",
       "      <td>36.889767</td>\n",
       "      <td>31.325312</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>BE</td>\n",
       "      <td>2016-12-31 02:00:00</td>\n",
       "      <td>39.650543</td>\n",
       "      <td>46.256210</td>\n",
       "      <td>52.842808</td>\n",
       "      <td>33.044876</td>\n",
       "      <td>26.458277</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>BE</td>\n",
       "      <td>2016-12-31 03:00:00</td>\n",
       "      <td>34.000072</td>\n",
       "      <td>44.015310</td>\n",
       "      <td>47.429000</td>\n",
       "      <td>23.984835</td>\n",
       "      <td>20.571144</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>BE</td>\n",
       "      <td>2016-12-31 04:00:00</td>\n",
       "      <td>33.785370</td>\n",
       "      <td>43.140503</td>\n",
       "      <td>48.581240</td>\n",
       "      <td>24.430239</td>\n",
       "      <td>18.989498</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "  unique_id                  ds    TimeGPT  TimeGPT-hi-80  TimeGPT-hi-90  \\\n",
       "0        BE 2016-12-31 00:00:00  51.632830      61.598820      66.088295   \n",
       "1        BE 2016-12-31 01:00:00  45.750877      54.611988      60.176445   \n",
       "2        BE 2016-12-31 02:00:00  39.650543      46.256210      52.842808   \n",
       "3        BE 2016-12-31 03:00:00  34.000072      44.015310      47.429000   \n",
       "4        BE 2016-12-31 04:00:00  33.785370      43.140503      48.581240   \n",
       "\n",
       "   TimeGPT-lo-80  TimeGPT-lo-90  \n",
       "0      41.666843      37.177372  \n",
       "1      36.889767      31.325312  \n",
       "2      33.044876      26.458277  \n",
       "3      23.984835      20.571144  \n",
       "4      24.430239      18.989498  "
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "timegpt_fcst_ex_vars_df = nixtla_client.forecast(df=df, \n",
    "                                                 X_df=future_ex_vars_df, \n",
    "                                                 h=24, \n",
    "                                                 level=[80, 90],\n",
    "                                                 feature_contributions=True)\n",
    "timegpt_fcst_ex_vars_df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Extract SHAP values\n",
    "\n",
    "Now that we have made predictions using exogenous features, we can then extract the SHAP values to understand their relevance using the `feature_contributions` attribute of the client. This returns a DataFrame containing the SHAP values and base values for each series, at each step in the horizon."
   ]
  },
  {
   "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>TimeGPT</th>\n",
       "      <th>Exogenous1</th>\n",
       "      <th>Exogenous2</th>\n",
       "      <th>day_0</th>\n",
       "      <th>day_1</th>\n",
       "      <th>day_2</th>\n",
       "      <th>day_3</th>\n",
       "      <th>day_4</th>\n",
       "      <th>day_5</th>\n",
       "      <th>day_6</th>\n",
       "      <th>base_value</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>BE</td>\n",
       "      <td>2016-12-31 00:00:00</td>\n",
       "      <td>51.632830</td>\n",
       "      <td>27.929638</td>\n",
       "      <td>-16.363607</td>\n",
       "      <td>0.081917</td>\n",
       "      <td>-1.883555</td>\n",
       "      <td>0.346484</td>\n",
       "      <td>-0.228611</td>\n",
       "      <td>0.424167</td>\n",
       "      <td>-3.411662</td>\n",
       "      <td>1.113910</td>\n",
       "      <td>43.624146</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>BE</td>\n",
       "      <td>2016-12-31 01:00:00</td>\n",
       "      <td>45.750877</td>\n",
       "      <td>17.678530</td>\n",
       "      <td>-12.240089</td>\n",
       "      <td>-0.758545</td>\n",
       "      <td>-0.077536</td>\n",
       "      <td>-0.160390</td>\n",
       "      <td>-0.309567</td>\n",
       "      <td>0.871469</td>\n",
       "      <td>-3.927268</td>\n",
       "      <td>1.218714</td>\n",
       "      <td>43.455560</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>BE</td>\n",
       "      <td>2016-12-31 02:00:00</td>\n",
       "      <td>39.650543</td>\n",
       "      <td>21.632694</td>\n",
       "      <td>-21.400244</td>\n",
       "      <td>-0.926842</td>\n",
       "      <td>-0.470276</td>\n",
       "      <td>-0.022417</td>\n",
       "      <td>-0.225389</td>\n",
       "      <td>0.220258</td>\n",
       "      <td>-3.927268</td>\n",
       "      <td>1.145736</td>\n",
       "      <td>43.624290</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>BE</td>\n",
       "      <td>2016-12-31 03:00:00</td>\n",
       "      <td>34.000072</td>\n",
       "      <td>13.879354</td>\n",
       "      <td>-20.681124</td>\n",
       "      <td>-0.114050</td>\n",
       "      <td>-0.488141</td>\n",
       "      <td>0.048164</td>\n",
       "      <td>-0.126627</td>\n",
       "      <td>0.200692</td>\n",
       "      <td>-3.400485</td>\n",
       "      <td>1.144959</td>\n",
       "      <td>43.537330</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>BE</td>\n",
       "      <td>2016-12-31 04:00:00</td>\n",
       "      <td>33.785370</td>\n",
       "      <td>13.465129</td>\n",
       "      <td>-20.619830</td>\n",
       "      <td>-0.036112</td>\n",
       "      <td>-0.470496</td>\n",
       "      <td>0.048375</td>\n",
       "      <td>-0.126627</td>\n",
       "      <td>0.200692</td>\n",
       "      <td>-3.400485</td>\n",
       "      <td>1.144959</td>\n",
       "      <td>43.579760</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "  unique_id                  ds    TimeGPT  Exogenous1  Exogenous2     day_0  \\\n",
       "0        BE 2016-12-31 00:00:00  51.632830   27.929638  -16.363607  0.081917   \n",
       "1        BE 2016-12-31 01:00:00  45.750877   17.678530  -12.240089 -0.758545   \n",
       "2        BE 2016-12-31 02:00:00  39.650543   21.632694  -21.400244 -0.926842   \n",
       "3        BE 2016-12-31 03:00:00  34.000072   13.879354  -20.681124 -0.114050   \n",
       "4        BE 2016-12-31 04:00:00  33.785370   13.465129  -20.619830 -0.036112   \n",
       "\n",
       "      day_1     day_2     day_3     day_4     day_5     day_6  base_value  \n",
       "0 -1.883555  0.346484 -0.228611  0.424167 -3.411662  1.113910   43.624146  \n",
       "1 -0.077536 -0.160390 -0.309567  0.871469 -3.927268  1.218714   43.455560  \n",
       "2 -0.470276 -0.022417 -0.225389  0.220258 -3.927268  1.145736   43.624290  \n",
       "3 -0.488141  0.048164 -0.126627  0.200692 -3.400485  1.144959   43.537330  \n",
       "4 -0.470496  0.048375 -0.126627  0.200692 -3.400485  1.144959   43.579760  "
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "shap_df = nixtla_client.feature_contributions\n",
    "shap_df = shap_df.query(\"unique_id == @market\")\n",
    "shap_df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the Dataframe above, we can see that we have the SHAP values at every forecasting step, as well as the prediction from TimeGPT and the base value. Note that the base value is the prediction of the model if exogenous features were unknown. \n",
    "\n",
    "Therefore, the forecast from TimeGPT is equal to the sum of the base value and the SHAP values of each exogenous feature in a given row."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Make plots using `shap`\n",
    "\n",
    "Now that we have access to SHAP values we can use the `shap` package to make any plots that we want.\n",
    "\n",
    "### 5.1 Bar plot\n",
    "Here, let's make bar plots for each series and their features, so we can see which features impacts the predictions the most."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 44564 characters]",
      "text/plain": [
       "<Figure size 800x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import shap\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "shap_columns = shap_df.columns.difference(['unique_id', 'ds', 'TimeGPT', 'base_value'])\n",
    "shap_values = shap_df[shap_columns].values  # SHAP values matrix\n",
    "base_values = shap_df['base_value'].values  # Extract base values\n",
    "features = shap_columns  # Feature names\n",
    "\n",
    "# Create a SHAP values object\n",
    "shap_obj = shap.Explanation(values=shap_values, base_values=base_values, feature_names=features)\n",
    "\n",
    "# Plot the bar plot for SHAP values\n",
    "shap.plots.bar(shap_obj, max_display=len(features), show=False)\n",
    "plt.title(f'SHAP values for {market}')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The plot above shows the average SHAP values for each feature across the entire horizon.\n",
    "\n",
    "Here, we see that `Exogenous1` is the most important feature, as it has the largest average contribution. Remember that it designates the expected energy demand, so we can see that this variable has a large impact on the final prediction. On the other hand, `day_2` is the least important feature, since it has the lowest value."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.2 Waterfall plot\n",
    "\n",
    "Now, let's see how we can make a waterfall plot to explore the the impact of features at a single prediction step. The code below selects a specific date. Of course, this can be modified for any series or date."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 62928 characters]",
      "text/plain": [
       "<Figure size 800x600 with 3 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#| eval: false\n",
    "selected_ds = shap_df['ds'].min()\n",
    "\n",
    "filtered_df = shap_df[shap_df['ds'] == selected_ds]\n",
    "\n",
    "shap_values = filtered_df[shap_columns].values.flatten()\n",
    "base_value = filtered_df['base_value'].values[0]\n",
    "features = shap_columns\n",
    "\n",
    "shap_obj = shap.Explanation(values=shap_values, base_values=base_value, feature_names=features)\n",
    "\n",
    "shap.plots.waterfall(shap_obj, show=False)\n",
    "plt.title(f'Waterfall Plot: {market}, date: {selected_ds}')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the waterfall plot above, we can explore in more detail a single prediction. Here, we study the final prediction for the start of December 31th, 2016.\n",
    "\n",
    "The x-axis represents the value of our series. At the bottom, we see `E[f(X)]` which represents the baseline value (the predicted value if exogenous features were unknown). \n",
    "\n",
    "Then, we see how each feature has impacted the final forecast. Features like `day_3`, `day_1`, `day_5`, `Exogenous2` all push the forecast to the left (smaller value). On the other hand, `day_0`, `day_2`, `day_4`, `day_6` and `Exogenous1` push it to the right (larger value). \n",
    "\n",
    "Let’s think about this for a moment. In the introduction, we stated that `Exogenous1` represents electricity load, whereas `Exogenous2` represents electricity generation. \n",
    "* `Exogenous1`, the electricity load, adds positively to the overall prediction. This seems reasonable: if we expect a higher demand, we might expect the price to go up.\n",
    "* `Exogenous2`, on the other hand, adds negatively to the overall prediction. This seems reasonable too: if there’s a higher electricity generation, we expect the price to be lower. Hence, a negative contribution to the forecast for `Exogenous2`.\n",
    "\n",
    "At the top right, we see f(x) which is the final output of the model after considering the impact of the exogenous features. Notice that this value corresponds to the final prediction from TimeGPT. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.3 Heatmap\n",
    "We can also do a heatmap plot to see how each feature impacts the final prediction. Here, we only need to select a specific series."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 55436 characters]",
      "text/plain": [
       "<Figure size 800x700 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "shap_columns = shap_df.columns.difference(['unique_id', 'ds', 'TimeGPT', 'base_value'])\n",
    "shap_values = shap_df[shap_columns].values  \n",
    "feature_names = shap_columns.tolist()\n",
    "\n",
    "shap_obj = shap.Explanation(values=shap_values, feature_names=feature_names)\n",
    "\n",
    "shap.plots.heatmap(shap_obj, show=False)\n",
    "plt.title(f'SHAP Heatmap (Unique ID: NP)')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "With the heatmap, we basically see a breakdown of each each feature impacts the final predciton at each timestep.\n",
    "\n",
    "On the x-axis, we have the number of instances, which corresponds to the number of prediction steps (24 in this case, since our horizon is set to 24h). On the y-axis, we have the name of the exogenous features.\n",
    "\n",
    "First, notice that the ordering is the same as in the bar plot, where `Exogenous1` is the most important, and `day_6` is the least important.\n",
    "\n",
    "Then, the color of the heatmap indiciates if the feature tends to increase of decrease the final prediction at each forecasting step. For example, `Exogenous1` always increases predictions across all 24 hours in the forecast horizon.\n",
    "\n",
    "We also see that all days except `day_5` do not have a very large impact at any forecasting step, indicating that they barely impacting the final prediction."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ultimately, the `feature_contributions` attribute gives you access to all the necessary information to explain the impact of exogenous features using the `shap` package."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "python3",
   "language": "python",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
