{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| hide\n",
    "!pip install -Uqq nixtla utilsforecast neuralforecast"
   ]
  },
  {
   "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": [
    "# Forecasting Energy Demand"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This tutorial is based on an energy consumption forecasting scenario where we make a 4-day forecast of in-zone energy consumption.\n",
    "\n",
    "Here, we use a subset of the [PJM Hourly Energy Consumption dataset](https://www.pjm.com/), focusing on in-zone consumption, where electricity is both generated and consumed within the same transmission zone. The dataset consists of hourly data from October 1, 2023, to September 30, 2024, covering five representative areas to capture hourly energy demand patterns.\n",
    "\n",
    "In this experiment, we show that using TimeGPT delivers significant improvements over using a state-of-the-art deep learning model like N-HiTS in a just a few lines of code:\n",
    "\n",
    "- MAE of TimeGPT is **18.6% better** than N-HiTS\n",
    "- sMAPE of TimeGPT is **31.1% better** than N-HiTS\n",
    "- TimeGPT generated predictions in **4.3 seconds**, which is **90% faster** than training and predicting with N-HiTS.\n",
    "\n",
    "\n",
    "The following tutorial explore all the steps in detail to reproduce these results so that you can apply TimeGPT in your own project."
   ]
  },
  {
   "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/3_electricity_demand.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/3_electricity_demand')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Initial setup\n",
    "\n",
    "First, we load the required packages for this experiment."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import time\n",
    "import requests\n",
    "import pandas as pd\n",
    "\n",
    "from nixtla import NixtlaClient\n",
    "\n",
    "from utilsforecast.losses import mae, smape\n",
    "from utilsforecast.evaluation import evaluate"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Of course, we need an instance of `NixtlaClient` to use TimeGPT."
   ]
  },
  {
   "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": [
    "## Read the data\n",
    "\n",
    "Here, we load in the inbound energy transmission time series."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.read_csv('https://raw.githubusercontent.com/Nixtla/transfer-learning-time-series/refs/heads/main/datasets/pjm_in_zone.csv')\n",
    "df['ds'] = pd.to_datetime(df['ds'])"
   ]
  },
  {
   "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>0</th>\n",
       "      <td>AP-AP</td>\n",
       "      <td>2023-10-01 04:00:00+00:00</td>\n",
       "      <td>4042.513</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>AP-AP</td>\n",
       "      <td>2023-10-01 05:00:00+00:00</td>\n",
       "      <td>3850.067</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8784</th>\n",
       "      <td>DOM-DOM</td>\n",
       "      <td>2023-10-01 04:00:00+00:00</td>\n",
       "      <td>10732.435</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8785</th>\n",
       "      <td>DOM-DOM</td>\n",
       "      <td>2023-10-01 05:00:00+00:00</td>\n",
       "      <td>10314.211</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>17568</th>\n",
       "      <td>JC-JC</td>\n",
       "      <td>2023-10-01 04:00:00+00:00</td>\n",
       "      <td>1825.101</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>17569</th>\n",
       "      <td>JC-JC</td>\n",
       "      <td>2023-10-01 05:00:00+00:00</td>\n",
       "      <td>1729.590</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>26352</th>\n",
       "      <td>PN-PN</td>\n",
       "      <td>2023-10-01 04:00:00+00:00</td>\n",
       "      <td>1454.666</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>26353</th>\n",
       "      <td>PN-PN</td>\n",
       "      <td>2023-10-01 05:00:00+00:00</td>\n",
       "      <td>1416.688</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>35136</th>\n",
       "      <td>RTO-RTO</td>\n",
       "      <td>2023-10-01 04:00:00+00:00</td>\n",
       "      <td>69139.393</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>35137</th>\n",
       "      <td>RTO-RTO</td>\n",
       "      <td>2023-10-01 05:00:00+00:00</td>\n",
       "      <td>66207.416</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "      unique_id                        ds          y\n",
       "0         AP-AP 2023-10-01 04:00:00+00:00   4042.513\n",
       "1         AP-AP 2023-10-01 05:00:00+00:00   3850.067\n",
       "8784    DOM-DOM 2023-10-01 04:00:00+00:00  10732.435\n",
       "8785    DOM-DOM 2023-10-01 05:00:00+00:00  10314.211\n",
       "17568     JC-JC 2023-10-01 04:00:00+00:00   1825.101\n",
       "17569     JC-JC 2023-10-01 05:00:00+00:00   1729.590\n",
       "26352     PN-PN 2023-10-01 04:00:00+00:00   1454.666\n",
       "26353     PN-PN 2023-10-01 05:00:00+00:00   1416.688\n",
       "35136   RTO-RTO 2023-10-01 04:00:00+00:00  69139.393\n",
       "35137   RTO-RTO 2023-10-01 05:00:00+00:00  66207.416"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df.groupby('unique_id').head(2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's plot our series to see what it looks like."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 359668 characters]",
      "text/plain": [
       "<Figure size 1600x1050 with 6 Axes>"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "nixtla_client.plot(\n",
    "    df, \n",
    "    max_insample_length=365, \n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can see clear sesaonal pattern in all of our series. It will be interesting to see how TimeGPT handles this type of data."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Forecasting with TimeGPT\n",
    "### Splitting the data\n",
    "\n",
    "The first step is to split our data. Here, we define an input DataFrame to feed to the model. We also reserve the last 96 time steps for the test set, so that we can evaluate the performance of TimeGPT against actual values.\n",
    "\n",
    "For this situation, we use a forecast horizon of 96, which represents four days, and we use an input sequence of 362 days, which is 8688 time steps."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_df = df.groupby('unique_id').tail(96)                                                             # 96 = 4 days (96 *  1 day/24h )\n",
    "\n",
    "input_df = df.groupby('unique_id').apply(lambda group: group.iloc[-1104:-96]).reset_index(drop=True)   # 1008 = 42 days (1008 * 1 day/24h)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Forecasting\n",
    "\n",
    "Then, we simply call the `forecast` method. Here, we use fine-tuning and specify the mean absolute error (MAE) as the fine-tuning loss. Also, we use the `timegpt-1-long-horizon` since we are forecasting the next two days, and the seasoanl period is one day."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "start = time.time()\n",
    "\n",
    "fcst_df = nixtla_client.forecast(\n",
    "    df=input_df,\n",
    "    h=96,                            \n",
    "    level=[90],                        # Generate a 90% confidence interval\n",
    "    finetune_steps=10,                 # Specify the number of steps for fine-tuning\n",
    "    finetune_loss='mae',               # Use the MAE as the loss function for fine-tuning\n",
    "    model='timegpt-1-long-horizon',    # Use the model for long-horizon forecasting\n",
    "    time_col='ds',\n",
    "    target_col='y',\n",
    "    id_col='unique_id'\n",
    ")\n",
    "\n",
    "end = time.time()\n",
    "\n",
    "timegpt_duration = end - start\n",
    "\n",
    "print(f\"Time (TimeGPT): {timegpt_duration}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> 📘 Available models in Azure AI\n",
    ">\n",
    "> If you are using an Azure AI endpoint, please be sure to set `model=\"azureai\"`:\n",
    ">\n",
    "> `nixtla_client.forecast(..., model=\"azureai\")`\n",
    "> \n",
    "> For the public API, we support two models: `timegpt-1` and `timegpt-1-long-horizon`. \n",
    "> \n",
    "> By default, `timegpt-1` is used. Please see [this tutorial](https://docs.nixtla.io/docs/tutorials-long_horizon_forecasting) on how and when to use `timegpt-1-long-horizon`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "TimeGPT was done in 4.3 seconds! We can now plot the predictions against the actual values of the test set."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 425684 characters]",
      "text/plain": [
       "<Figure size 1600x1050 with 6 Axes>"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "nixtla_client.plot(test_df, fcst_df, models=['TimeGPT'], level=[90], time_col='ds', target_col='y')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Evaluation\n",
    "\n",
    "Now that we have predictions, let's evaluate the model's performance."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fcst_df['ds'] = pd.to_datetime(fcst_df['ds'])\n",
    "\n",
    "test_df = pd.merge(test_df, fcst_df, 'left', ['unique_id', 'ds'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "metric\n",
       "mae      882.693979\n",
       "smape      0.019974\n",
       "Name: TimeGPT, dtype: float64"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "evaluation = evaluate(\n",
    "    test_df,\n",
    "    metrics=[mae, smape],\n",
    "    models=[\"TimeGPT\"],\n",
    "    target_col=\"y\",\n",
    "    id_col='unique_id'\n",
    ")\n",
    "\n",
    "average_metrics = evaluation.groupby('metric')['TimeGPT'].mean()\n",
    "average_metrics"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can see that TimeGPT achieves a MAE of 882.6 and a sMAPE of 2%.\n",
    "\n",
    "Great! Now, let's see if a data-specific model can do better."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Forecasting with N-HiTS\n",
    "\n",
    "Here, we use the N-HiTS model, as it is very fast to train and performs well on long-horizon forecasting tasks. To reproduce these results, make sure to install the library `neuralforecast`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from neuralforecast.core import NeuralForecast\n",
    "from neuralforecast.models import NHITS"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Define the training set\n",
    "\n",
    "The training set is different from the input DataFrame for TimeGPT, as we need more data to train a data-specific model. \n",
    "\n",
    "Note that the dataset is very large, so we use the last 362 days of the training set to fit our model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_df = df.groupby('unique_id').apply(lambda group: group.iloc[:-96]).reset_index(drop=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Forecasting with N-HiTS\n",
    "\n",
    "We can now fit the model on training set and make predictions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| eval: false\n",
    "horizon = 96\n",
    "\n",
    "models = [NHITS(h=horizon, input_size = 5*horizon, scaler_type='robust', batch_size=16, valid_batch_size=8)]\n",
    "\n",
    "nf = NeuralForecast(models=models, freq='H')\n",
    "\n",
    "start = time.time()\n",
    "\n",
    "nf.fit(df=train_df)\n",
    "nhits_preds = nf.predict()\n",
    "\n",
    "end = time.time()\n",
    "\n",
    "nhits_duration = end - start\n",
    "\n",
    "print(f\"Time (N-HiTS): {nhits_duration}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Great! Note that N-HiTS took 44 seconds to carry out the training and forecasting procedures. Now, let's evaluate the performance of this model."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Evaluation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| eval: false\n",
    "preds_df = pd.merge(test_df, nhits_preds, 'left', ['unique_id', 'ds'])\n",
    "\n",
    "evaluation = evaluate(\n",
    "    preds_df,\n",
    "    metrics=[mae, smape],\n",
    "    models=[\"NHITS\"],\n",
    "    target_col=\"y\",\n",
    "    id_col='unique_id'\n",
    ")\n",
    "\n",
    "\n",
    "average_metrics = evaluation.groupby('metric')['NHITS'].mean()\n",
    "print(average_metrics)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Conclusion\n",
    "\n",
    "TimeGPT achieves a MAE of 882.6 while N-HiTS achieves a MAE of 1084.7, meaning there is a **18.6% improvement** in using TimeGPT versus our data-specific N-HiTS model. TimeGPT also improved the sMAPE by 31.1%.\n",
    "\n",
    "Plus, TimeGPT took 4.3 seconds to generate forecasts, while N-HiTS took 44 seconds to fit and predict. TimeGPT is thus **90% faster** than using N-HiTS in this scenario."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| hide\n",
    "# This is a test for the above last two code cells that runs faster (we train NHITS for fewer steps). It is merely here to check that the above code runs correctly.\n",
    "if not IN_COLAB:\n",
    "    horizon = 96\n",
    "\n",
    "    models = [NHITS(h=horizon, input_size = 5*horizon, scaler_type='robust', batch_size=16, valid_batch_size=8, max_steps=5)]\n",
    "\n",
    "    nf = NeuralForecast(models=models, freq='H')\n",
    "\n",
    "    start = time.time()\n",
    "\n",
    "    nf.fit(df=train_df)\n",
    "    nhits_preds = nf.predict()\n",
    "\n",
    "    end = time.time()\n",
    "\n",
    "    nhits_duration = end - start\n",
    "\n",
    "    print(f\"Time (N-HiTS): {nhits_duration}\")    \n",
    "\n",
    "    preds_df = pd.merge(test_df, nhits_preds, 'left', ['unique_id', 'ds'])\n",
    "\n",
    "    evaluation = evaluate(\n",
    "        preds_df,\n",
    "        metrics=[mae, smape],\n",
    "        models=[\"NHITS\"],\n",
    "        target_col=\"y\",\n",
    "        id_col='unique_id'\n",
    "    )\n",
    "\n",
    "    average_metrics = evaluation.groupby('metric')['NHITS'].mean()\n",
    "    print(average_metrics)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "python3",
   "language": "python",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
