{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| hide\n",
    "!pip install -Uqq nixtla hierarchicalforecast"
   ]
  },
  {
   "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"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Temporal Hierarchical Forecasting with TimeGPT"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this notebook, we demonstrate how to use TimeGPT for temporal hierarchical forecasting. We will use a dataset that has an hourly frequency, and we create forecasts with TimeGPT for both the hourly and the 2-hourly frequency level. The latter constitutes the timeseries when it is aggregated across 2-hour windows. Subsequently, we can use temporal reconciliation techniques to improve the forecasting performance of TimeGPT."
   ]
  },
  {
   "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/23_temporalhierarchical.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/23_temporalhierarchical')"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Load and Process Data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "from utilsforecast.evaluation import evaluate\n",
    "from utilsforecast.plotting import plot_series\n",
    "from utilsforecast.losses import mae, rmse\n",
    "from nixtla import NixtlaClient"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "nixtla_client = NixtlaClient(\n",
    "    # api_key = 'my_api_key_provided_by_nixtla'\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.read_csv('https://raw.githubusercontent.com/Nixtla/transfer-learning-time-series/main/datasets/electricity-short-with-ex-vars.csv')\n",
    "df['ds'] = pd.to_datetime(df['ds'])\n",
    "df_sub = df.query('unique_id == \"DE\"')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "((1632, 12), (48, 12))"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df_train = df_sub.query('ds < \"2017-12-29\"')\n",
    "df_test = df_sub.query('ds >= \"2017-12-29\"')\n",
    "df_train.shape, df_test.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 71404 characters]",
      "text/plain": [
       "<Figure size 1600x350 with 1 Axes>"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "plot_series(df_train[['unique_id','ds','y']][-200:], forecasts_df= df_test[['unique_id','ds','y']].rename(columns={'y': 'test'}))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Temporal aggregation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We are interested in generating forecasts for the hourly and 2-hourly windows. We can generate these forecasts using TimeGPT. After generating these forecasts, we make use of hierarchical forecasting techniques to improve the accuracy of each forecast. \n",
    "\n",
    "We first define the temporal aggregation spec. The spec is a dictionary in which the keys are the name of the aggregation and the value is the amount of bottom-level timesteps that should be aggregated in that aggregation. \n",
    "\n",
    "In this example, we choose a temporal aggregation of a 2-hour period and a 1-hour period (the bottom level)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "spec_temporal = { \"2-hour-period\": 2, \"1-hour-period\": 1}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We next compute the temporally aggregated train- and test sets using the `aggregate_temporal` function from `hierarchicalforecast`. Note that we have different aggregation matrices `S` for the train- and test set, as the test set contains temporal hierarchies that are not included in the train set."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from hierarchicalforecast.utils import aggregate_temporal"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Y_train, S_train, tags_train = aggregate_temporal(df=df_train[['unique_id','ds','y']], spec=spec_temporal)\n",
    "Y_test, S_test, tags_test = aggregate_temporal(df=df_test[['unique_id','ds','y']],  spec=spec_temporal)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`Y_train` contains our training data, for both 1-hour and 2-hour periods. For example, if we look at the first two timestamps of the training data, we have a 2-hour period ending at 2017-10-22 01:00, and two 1-hour periods, the first ending at 2017-10-22 00:00, and the second at 2017-10-22 01:00, the latter corresponding to when the first 2-hour period ends. \n",
    "\n",
    "Also, the ground truth value `y` of the first 2-hour period is 38.13, which is equal to the sum of the first two 1-hour periods (19.10 + 19.03). This showcases how the higher frequency `1-hour-period` has been aggregated into the `2-hour-period` frequency."
   ]
  },
  {
   "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>temporal_id</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>2-hour-period-1</td>\n",
       "      <td>DE</td>\n",
       "      <td>2017-10-22 01:00:00</td>\n",
       "      <td>38.13</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>816</th>\n",
       "      <td>1-hour-period-1</td>\n",
       "      <td>DE</td>\n",
       "      <td>2017-10-22 00:00:00</td>\n",
       "      <td>19.10</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>817</th>\n",
       "      <td>1-hour-period-2</td>\n",
       "      <td>DE</td>\n",
       "      <td>2017-10-22 01:00:00</td>\n",
       "      <td>19.03</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "         temporal_id unique_id                  ds      y\n",
       "0    2-hour-period-1        DE 2017-10-22 01:00:00  38.13\n",
       "816  1-hour-period-1        DE 2017-10-22 00:00:00  19.10\n",
       "817  1-hour-period-2        DE 2017-10-22 01:00:00  19.03"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Y_train.query(\"ds <= '2017-10-22 01:00:00'\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The aggregation matrices `S_train` and `S_test` detail how the lowest temporal granularity (hour) can be aggregated into the 2-hour periods. For example, the first 2-hour period, named `2-hour-period-1`, can be constructed by summing the first two hour-periods, `1-hour-period-1` and `1-hour-period-2` - which we also verified above in our inspection of `Y_train`."
   ]
  },
  {
   "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>temporal_id</th>\n",
       "      <th>1-hour-period-1</th>\n",
       "      <th>1-hour-period-2</th>\n",
       "      <th>1-hour-period-3</th>\n",
       "      <th>1-hour-period-4</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>2-hour-period-1</td>\n",
       "      <td>1.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>2-hour-period-2</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>1.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>2-hour-period-3</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>2-hour-period-4</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>2-hour-period-5</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "       temporal_id  1-hour-period-1  1-hour-period-2  1-hour-period-3  \\\n",
       "0  2-hour-period-1              1.0              1.0              0.0   \n",
       "1  2-hour-period-2              0.0              0.0              1.0   \n",
       "2  2-hour-period-3              0.0              0.0              0.0   \n",
       "3  2-hour-period-4              0.0              0.0              0.0   \n",
       "4  2-hour-period-5              0.0              0.0              0.0   \n",
       "\n",
       "   1-hour-period-4  \n",
       "0              0.0  \n",
       "1              1.0  \n",
       "2              0.0  \n",
       "3              0.0  \n",
       "4              0.0  "
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "S_train.iloc[:5, :5]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3b. Computing base forecasts\n",
    "\n",
    "Now, we need to compute base forecasts for each temporal aggregation. The following cell computes the **base forecasts** for each temporal aggregation in `Y_train` using TimeGPT. \n",
    "\n",
    "Note that both frequency and horizon are different for each temporal aggregation. In this example, the lowest level has a hourly frequency, and a horizon of `48`. The `2-hourly-period` aggregation thus has a 2-hourly frequency with a horizon of `24`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Y_hats = []\n",
    "id_cols = [\"unique_id\", \"temporal_id\", \"ds\", \"y\"]\n",
    "# We will train a model for each temporal level\n",
    "for level, temporal_ids_train in tags_train.items():\n",
    "    # Filter the data for the level\n",
    "    Y_level_train = Y_train.query(\"temporal_id in @temporal_ids_train\")\n",
    "    temporal_ids_test = tags_test[level]\n",
    "    Y_level_test = Y_test.query(\"temporal_id in @temporal_ids_test\")\n",
    "    # For each temporal level we have a different frequency and forecast horizon\n",
    "    freq_level = pd.infer_freq(Y_level_train[\"ds\"].unique())\n",
    "    horizon_level = Y_level_test[\"ds\"].nunique()\n",
    "    # Train a model and create forecasts\n",
    "    Y_hat_level = nixtla_client.forecast(df=Y_level_train[[\"ds\", \"unique_id\", \"y\"]], h=horizon_level)\n",
    "    # Add the test set to the forecast\n",
    "    Y_hat_level = Y_hat_level.merge(Y_level_test, on=[\"ds\", \"unique_id\"], how=\"left\")\n",
    "    # Put cols in the right order (for readability)\n",
    "    Y_hat_cols = id_cols + [col for col in Y_hat_level.columns if col not in id_cols]\n",
    "    Y_hat_level = Y_hat_level[Y_hat_cols]\n",
    "    # Append the forecast to the list\n",
    "    Y_hats.append(Y_hat_level)\n",
    "\n",
    "Y_hat = pd.concat(Y_hats, ignore_index=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Observe that `Y_hat` contains all the forecasts but they are not coherent with each other. For example, consider the forecasts for the first time period of both frequencies."
   ]
  },
  {
   "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>temporal_id</th>\n",
       "      <th>ds</th>\n",
       "      <th>y</th>\n",
       "      <th>TimeGPT</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>DE</td>\n",
       "      <td>2-hour-period-1</td>\n",
       "      <td>2017-12-29 01:00:00</td>\n",
       "      <td>10.45</td>\n",
       "      <td>16.949448</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>24</th>\n",
       "      <td>DE</td>\n",
       "      <td>1-hour-period-1</td>\n",
       "      <td>2017-12-29 00:00:00</td>\n",
       "      <td>9.73</td>\n",
       "      <td>-0.241489</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>25</th>\n",
       "      <td>DE</td>\n",
       "      <td>1-hour-period-2</td>\n",
       "      <td>2017-12-29 01:00:00</td>\n",
       "      <td>0.72</td>\n",
       "      <td>-3.456482</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   unique_id      temporal_id                  ds      y    TimeGPT\n",
       "0         DE  2-hour-period-1 2017-12-29 01:00:00  10.45  16.949448\n",
       "24        DE  1-hour-period-1 2017-12-29 00:00:00   9.73  -0.241489\n",
       "25        DE  1-hour-period-2 2017-12-29 01:00:00   0.72  -3.456482"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Y_hat.query(\"temporal_id in ['2-hour-period-1', '1-hour-period-1', '1-hour-period-2']\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ground truth value `y` for the first 2-hour period is 10.45, and the sum of the ground truth values for the first two 1-hour periods is (9.73 + 0.72) = 10.45. Hence, these values are coherent with each other.\n",
    "\n",
    "However, the forecast for the first 2-hour period is 16.95, but the sum of the forecasts for the first two 1-hour periods is -3.69. Hence, these forecasts are clearly not coherent with each other. \n",
    "\n",
    "We will use reconciliation techniques to make these forecasts better coherent with each other and improve their accuracy."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3c. Reconcile forecasts\n",
    "\n",
    "We can use the `HierarchicalReconciliation` class to reconcile the forecasts. In this example we use `MinTrace`. Note that we have to set `temporal=True` in the `reconcile` function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from hierarchicalforecast.methods import MinTrace\n",
    "from hierarchicalforecast.core import HierarchicalReconciliation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "reconcilers = [\n",
    "    MinTrace(method=\"wls_struct\"),\n",
    "]\n",
    "hrec = HierarchicalReconciliation(reconcilers=reconcilers)\n",
    "Y_rec = hrec.reconcile(Y_hat_df=Y_hat, S=S_test, tags=tags_test, temporal=True)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Evaluation "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `HierarchicalForecast` package includes the `evaluate` function to evaluate the different hierarchies."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We evaluate the temporally aggregated forecasts _across all temporal aggregations_."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import hierarchicalforecast.evaluation as hfe\n",
    "from utilsforecast.losses import mae"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "evaluation = hfe.evaluate(df = Y_rec.drop(columns = 'unique_id'),\n",
    "                      tags = tags_test,\n",
    "                      metrics = [mae],\n",
    "                      id_col='temporal_id')\n",
    "\n",
    "numeric_cols = evaluation.select_dtypes(include=\"number\").columns\n",
    "evaluation[numeric_cols] = evaluation[numeric_cols].map('{:.3}'.format).astype(np.float64)"
   ]
  },
  {
   "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>level</th>\n",
       "      <th>metric</th>\n",
       "      <th>TimeGPT</th>\n",
       "      <th>TimeGPT/MinTrace_method-wls_struct</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>2-hour-period</td>\n",
       "      <td>mae</td>\n",
       "      <td>25.2</td>\n",
       "      <td>12.00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>1-hour-period</td>\n",
       "      <td>mae</td>\n",
       "      <td>18.5</td>\n",
       "      <td>6.16</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>Overall</td>\n",
       "      <td>mae</td>\n",
       "      <td>20.8</td>\n",
       "      <td>8.12</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "           level metric  TimeGPT  TimeGPT/MinTrace_method-wls_struct\n",
       "0  2-hour-period    mae     25.2                               12.00\n",
       "1  1-hour-period    mae     18.5                                6.16\n",
       "2        Overall    mae     20.8                                8.12"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "evaluation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As we can see, we improved performance of TimeGPT's predictions both for the 2-hour period and for the 1-hour period, as both levels see a significant reduction in MAE and RMSE."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Visually, we can also verify the forecast is better after using reconciliation techniques. For the 1-hour-period forecasts:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 82552 characters]",
      "text/plain": [
       "<Figure size 1600x350 with 1 Axes>"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "plot_series(Y_train.query(\"temporal_id in @tags_train['1-hour-period']\")[[\"y\", \"ds\", \"unique_id\"]].iloc[-100:], forecasts_df=Y_rec.query(\"temporal_id in @tags_test['1-hour-period']\").drop(columns=[\"temporal_id\"]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "and for the 2-hour period forecasts:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 80428 characters]",
      "text/plain": [
       "<Figure size 1600x350 with 1 Axes>"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "plot_series(Y_train.query(\"temporal_id in @tags_train['2-hour-period']\")[[\"y\", \"ds\", \"unique_id\"]].iloc[-50:], forecasts_df=Y_rec.query(\"temporal_id in @tags_test['2-hour-period']\").drop(columns=[\"temporal_id\"]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Also, we can now verify that the forecasts are better coherent with each other. For the first 2-hour period, our forecast after reconciliation is 6.63, and the sum of the forecasts for the first two 1-hour periods is 1.7 + 4.92 = 6.63. Hence, we now have more accurate and coherent forecasts across frequencies."
   ]
  },
  {
   "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>temporal_id</th>\n",
       "      <th>ds</th>\n",
       "      <th>y</th>\n",
       "      <th>TimeGPT</th>\n",
       "      <th>TimeGPT/MinTrace_method-wls_struct</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>DE</td>\n",
       "      <td>2-hour-period-1</td>\n",
       "      <td>2017-12-29 01:00:00</td>\n",
       "      <td>10.45</td>\n",
       "      <td>16.949448</td>\n",
       "      <td>6.625738</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>24</th>\n",
       "      <td>DE</td>\n",
       "      <td>1-hour-period-1</td>\n",
       "      <td>2017-12-29 00:00:00</td>\n",
       "      <td>9.73</td>\n",
       "      <td>-0.241489</td>\n",
       "      <td>4.920365</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>25</th>\n",
       "      <td>DE</td>\n",
       "      <td>1-hour-period-2</td>\n",
       "      <td>2017-12-29 01:00:00</td>\n",
       "      <td>0.72</td>\n",
       "      <td>-3.456482</td>\n",
       "      <td>1.705373</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   unique_id      temporal_id                  ds      y    TimeGPT  \\\n",
       "0         DE  2-hour-period-1 2017-12-29 01:00:00  10.45  16.949448   \n",
       "24        DE  1-hour-period-1 2017-12-29 00:00:00   9.73  -0.241489   \n",
       "25        DE  1-hour-period-2 2017-12-29 01:00:00   0.72  -3.456482   \n",
       "\n",
       "    TimeGPT/MinTrace_method-wls_struct  \n",
       "0                             6.625738  \n",
       "24                            4.920365  \n",
       "25                            1.705373  "
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Y_rec.query(\"temporal_id in ['2-hour-period-1', '1-hour-period-1', '1-hour-period-2']\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Conclusion"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this notebook we have shown:\n",
    "- How to create forecasts for multiple frequencies for the same dataset with TimeGPT\n",
    "- How to improve the accuracy of these forecasts using temporal reconciliation techniques\n",
    "\n",
    "Note that even though we created forecasts for two different frequencie, there is no 'need' to use the forecast of the 2-hour-period. One can use this technique also simply to improve the forecast of the 1-hour-period.\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "python3",
   "language": "python",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
