Using the watsonx.ai Time Series Forecasting API to predict energy demand¶
Authors: Aleksandra Kłeczek and Meredith Syed
In this tutorial, you will discover how to perform timeseries forecasting using the watsonx.ai Timeseries Forecasting API and SDK to predict energy demand. This notebook demonstrates the usage of a pre-trained time series foundation model for multivariate forecasting tasks and demonstrates a variety of features available using Time Series Foundation Models.
watsonx.ai Timeseries Forecasting API and SDK¶
We are pleased to announce that the watsonx.ai Timeseries Forecasting API and SDK is available to the public in GA from February 2025. Built on the Granite Timeseries models, this new API/SDK offers unparalleled accuracy and ease of use for tackling real-world forecasting challenges. At the core of this capability is IBM’s Granite timeseries models (TinyTimeMixers), a family of open source pre-trained, lightweight models based on a novel architecture.
Granite timeseries models are designed to:
-Adapt to diverse datasets: Whether it’s IoT sensor data, stock market prices, or energy demand, Granite dynamically adjusts to data irregularities, seasonality, and trends, allowing for zero-shot forecasting.
-Deliver state-of-the-art accuracy: Granite timeseries models outperform many larger timeseries models like TimesFM, Moirai, Chronos, and more that are 10x the size on various industry benchmarks, while being computationally efficient.
-Support scalability: From small-scale business needs to enterprise-grade implementations, the models handle forecasting at any scale.
These models are trained on a collection of publicly available datasets which permit commercial use. A total of 700 Million timepoints were used from selected datasets from the Monash Forecasting Repository and the LOTSA collection. Granite timeseries models currently support multiple input context lengths (512, 1024, and 1536 data points), and are capable of multivariate predictions across a number of channels and IDs. For more information about use cases and training details, see the model card and the IBM Research paper.
By launching the watsonx.ai Timeseries Forecasting API, IBM reaffirms its commitment to providing developers with the tools they need to build impactful AI solutions. This new capability enhances IBM watsonx.ai, an enterprise-grade AI developer studio, enabling clients to develop AI and ML solutions with tools for agent development, fine-tuning, RAG, guardrails and more.
Steps¶
Step 1. Set up your environment¶
While you can choose from several tools, this tutorial walks you through how to set up an IBM account to use a Jupyter Notebook.
Log in to watsonx.ai using your IBM Cloud® account.
Create a watsonx.ai project.
You can get your project ID from within your project. Click the Manage tab. Then, copy the project ID from the Details section of the General page. You need this ID for this tutorial.
Create a Jupyter Notebook.
This step opens a Jupyter Notebook environment where you can copy the code from this tutorial. Alternatively, you can download this notebook to your local system and upload it to your watsonx.ai project as an asset. To view more Granite tutorials, check out the IBM Granite Community. This tutorial is also available on GitHub.
Step 2. Set up a watsonx.ai Runtime instance and API key.¶
Create a watsonx.ai Runtime service instance (select your appropriate region and choose the Lite plan, which is a free instance).
Generate an application programming interface (API) key.
Associate the watsonx.ai Runtime service instance to the project that you created in watsonx.ai.
Step 3. Install and import relevant libraries and set up your credentials¶
We need a few libraries and modules for this tutorial. Make sure to import the following ones and if they're not installed, a quick pip installation resolves the problem.
We'll be using os
and getpass
to set up our credentials. To load the dataset, we'll use wget
and pandas
. For data visualization, we'll use numpy
and matplotlib
.
In addition to common Python libraries, we'll be using ibm-watsonx-ai
which allows you to work with IBM watsonx.ai services. You can train, store, and deploy your models, score them using APIs, and finally integrate them with your application development. We'll use the Credentials
and APIClient
functionality to connect to watsonx.ai services.
For timeseries capabilities, we're using new functionality from TSModelInference
and TSForecastParameters
to define the model and set parameters for forecasting. In order to evaluate the performance of the model, we'll use the Mean Absolute Percentage Error (MAPE) metric from sklearn
.
Note, this tutorial was built using Python 3.11
#installations
%pip install wget | tail -n 1
%pip install -U matplotlib | tail -n 1
%pip install -U ibm-watsonx-ai | tail -n 1
[notice] A new release of pip is available: 24.3.1 -> 25.0.1 [notice] To update, run: python3.11 -m pip install --upgrade pip Requirement already satisfied: wget in /usr/local/lib/python3.11/site-packages (3.2) Note: you may need to restart the kernel to use updated packages. [notice] A new release of pip is available: 24.3.1 -> 25.0.1 [notice] To update, run: python3.11 -m pip install --upgrade pip Requirement already satisfied: six>=1.5 in /Users/meredithsyed/Library/Python/3.11/lib/python/site-packages (from python-dateutil>=2.7->matplotlib) (1.16.0) Note: you may need to restart the kernel to use updated packages. [notice] A new release of pip is available: 24.3.1 -> 25.0.1 [notice] To update, run: python3.11 -m pip install --upgrade pip Requirement already satisfied: six>=1.10.0 in /Users/meredithsyed/Library/Python/3.11/lib/python/site-packages (from lomond->ibm-watsonx-ai) (1.16.0) Note: you may need to restart the kernel to use updated packages.
#imports
import os
import getpass
import wget
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.metrics import mean_absolute_percentage_error
from ibm_watsonx_ai import Credentials
from ibm_watsonx_ai import APIClient
from ibm_watsonx_ai.foundation_models import TSModelInference
from ibm_watsonx_ai.foundation_models.schema import TSForecastParameters
To set our credentials, we need the WATSONX_APIKEY
and WATSONX_PROJECT_ID
you generated in step 1. We will also set the URL serving as the API endpoint.
If you're not sure of the URL, you can use IBM Cloud CLI to retrieve the instance location
.
ibmcloud login --apikey API_KEY -a https://cloud.ibm.com
ibmcloud resource service-instance INSTANCE_NAME
WATSONX_APIKEY = getpass.getpass("Please enter your watsonx.ai Runtime API key (hit enter): ")
WATSONX_PROJECT_ID = getpass.getpass("Please enter your project ID (hit enter): ")
URL = "https://us-south.ml.cloud.ibm.com"
We can use the Credentials
class to encapsulate our passed credentials.
credentials = Credentials(
url=URL,
api_key=WATSONX_APIKEY
)
To be able to interact with all resources available in watsonx.ai Runtime, you need to set the project which you will be using via the WATSONX_PROJECT_ID
.
client = APIClient(credentials)
client.set.default_project(WATSONX_PROJECT_ID)
'SUCCESS'
Step 4. Load and prepare the dataset¶
This tutorial uses the Hourly energy demand dataset. This dataset contains 4 years of electrical consumption and energy generation data gathered in Spain from 2015 to 2018 aggregated by hour. It is a modified version of the Hourly energy demand generation and weather dataset. You can find more details about the dataset, including metadata in the preceding links.
For simplicity, the dataset was prepared to have no missing values and to remove irrelevant columns.
filename = 'energy_dataset.csv'
base_url = 'https://github.com/IBM/watson-machine-learning-samples/raw/refs/heads/master/cloud/data/energy/'
if not os.path.isfile(filename): wget.download(base_url + filename)
Let's examine the last few rows of the dataset. We can see the time
column showing a timestamp for each hour. Other columns show numeric data types for energy generation from different sources, weather forecast details and actual energy usage, termed as total_load_actual
. This will be our target column, the column for which we are trying to predict values. Since our model is performing multivariate forecasting, we'll use all of the other columns as input to our model to help inform it's predictions. These columns provide details about energy generation and weather forecasts for each hour, enabling us to predict actual energy demand on an hourly basis.
df = pd.read_csv(filename)
df.tail()
time | generation biomass | generation fossil brown coal/lignite | generation fossil gas | generation fossil hard coal | generation fossil oil | generation hydro pumped storage consumption | generation hydro run-of-river and poundage | generation hydro water reservoir | generation nuclear | generation other | generation other renewable | generation solar | generation waste | generation wind onshore | forecast solar day ahead | forecast wind onshore day ahead | total load forecast | total load actual | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
35059 | 2018-12-31 19:00:00 | 297.0 | 0.0 | 7634.0 | 2628.0 | 178.0 | 1.0 | 1135.0 | 4836.0 | 6073.0 | 63.0 | 95.0 | 85.0 | 277.0 | 3113.0 | 96.0 | 3253.0 | 30619.0 | 30653.0 |
35060 | 2018-12-31 20:00:00 | 296.0 | 0.0 | 7241.0 | 2566.0 | 174.0 | 1.0 | 1172.0 | 3931.0 | 6074.0 | 62.0 | 95.0 | 33.0 | 280.0 | 3288.0 | 51.0 | 3353.0 | 29932.0 | 29735.0 |
35061 | 2018-12-31 21:00:00 | 292.0 | 0.0 | 7025.0 | 2422.0 | 168.0 | 50.0 | 1148.0 | 2831.0 | 6076.0 | 61.0 | 94.0 | 31.0 | 286.0 | 3503.0 | 36.0 | 3404.0 | 27903.0 | 28071.0 |
35062 | 2018-12-31 22:00:00 | 293.0 | 0.0 | 6562.0 | 2293.0 | 163.0 | 108.0 | 1128.0 | 2068.0 | 6075.0 | 61.0 | 93.0 | 31.0 | 287.0 | 3586.0 | 29.0 | 3273.0 | 25450.0 | 25801.0 |
35063 | 2018-12-31 23:00:00 | 290.0 | 0.0 | 6926.0 | 2166.0 | 163.0 | 108.0 | 1069.0 | 1686.0 | 6075.0 | 61.0 | 92.0 | 31.0 | 287.0 | 3651.0 | 26.0 | 3117.0 | 24424.0 | 24455.0 |
df.drop("total load forecast", axis=1, inplace=True)
df.head()
time | generation biomass | generation fossil brown coal/lignite | generation fossil gas | generation fossil hard coal | generation fossil oil | generation hydro pumped storage consumption | generation hydro run-of-river and poundage | generation hydro water reservoir | generation nuclear | generation other | generation other renewable | generation solar | generation waste | generation wind onshore | forecast solar day ahead | forecast wind onshore day ahead | total load actual | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2015-01-01 00:00:00 | 447.0 | 329.0 | 4844.0 | 4821.0 | 162.0 | 863.0 | 1051.0 | 1899.0 | 7096.0 | 43.0 | 73.0 | 49.0 | 196.0 | 6378.0 | 17.0 | 6436.0 | 25385.0 |
1 | 2015-01-01 01:00:00 | 449.0 | 328.0 | 5196.0 | 4755.0 | 158.0 | 920.0 | 1009.0 | 1658.0 | 7096.0 | 43.0 | 71.0 | 50.0 | 195.0 | 5890.0 | 16.0 | 5856.0 | 24382.0 |
2 | 2015-01-01 02:00:00 | 448.0 | 323.0 | 4857.0 | 4581.0 | 157.0 | 1164.0 | 973.0 | 1371.0 | 7099.0 | 43.0 | 73.0 | 50.0 | 196.0 | 5461.0 | 8.0 | 5454.0 | 22734.0 |
3 | 2015-01-01 03:00:00 | 438.0 | 254.0 | 4314.0 | 4131.0 | 160.0 | 1503.0 | 949.0 | 779.0 | 7098.0 | 43.0 | 75.0 | 50.0 | 191.0 | 5238.0 | 2.0 | 5151.0 | 21286.0 |
4 | 2015-01-01 04:00:00 | 428.0 | 187.0 | 4130.0 | 3840.0 | 156.0 | 1826.0 | 953.0 | 720.0 | 7097.0 | 43.0 | 74.0 | 42.0 | 189.0 | 4935.0 | 9.0 | 4861.0 | 20264.0 |
Split the data¶
For our forecasting problem, we'll need to split the data into 2 sets, the first which will be used as historical data. We'll provide the historical data to the model and ask it to predict future values. In order to test the accuracy of our predictions, we'll also need to compare these predictions against ground truth values. For our experiment, we'll use a second subset of our dataset as the ground truth and we'll compare the predicted values to the actual values in this ground truth subset.
Granite timeseries models come in different context lengths of 512, 1024 and 1536 tokens. The context length describes the amount of information the model can consider when making a single prediction. For the Granite timeseries models, each row in a dataset counts as one token towards the context length. We'll be using the 512 token context length timeseries model, ibm/granite-ttm-512-96-r2
, in our experiment. In order to do this, we need a dataset of 512 rows to provide as input to the model, our historical data. We'll term this input dataset as data
. We have many more rows in our dataset than are needed for this prediction problem. In this case, to subset the data, we'll simply take the most recent timestamps or the last rows of the dataset.
The second dataset we need is our evaluation or ground truth dataset. We'll use the last 96 rows of data in our dataset for this purpose. We'll call this future_context
and we'll use this data to compare against our predictions.
Here, we also specify the columns to be used for prediction. The identifiers timestamp_column
and target_column
set these values for the model.
# how many rows and columns
df.shape
(35064, 18)
timestamp_column = "time"
target_column = "total load actual"
context_length = 512
future_context = 96
# use the last `context_length` rows for prediction.
future_data = df.iloc[-future_context:,]
data = df.iloc[-(context_length + future_context):-future_context,]
Let's examine the data further with this data visualization, which plots the hourly timestamps against our target column, total load actual
.
plt.figure(figsize=(10,2))
plt.plot(np.asarray(data[timestamp_column], 'datetime64[s]'), data[target_column])
plt.title("Actual Total Load")
plt.show()
In preparing data for timeseries forecasting, models can have different requirements for preprocessing the data. The Granite TTM model card recommends that data be scaled and a preprocessing script is provided as an example. For the purposes of this tutorial, we'll use our dataset 'as-is.'
Step 5. Select a Granite Timeseries Foundation Model from watsonx.ai¶
Using the get_time_series_model_specs
function from the watsonx.ai SDK, we can list the models available from the Timeseries Forecasting API. For this experiment, we'll use the 512 context length model, but we see that the larger context models are also available.
for model in client.foundation_models.get_time_series_model_specs()["resources"]:
print('--------------------------------------------------')
print(f'model_id: {model["model_id"]}')
print(f'functions: {model["functions"]}')
print(f'long_description: {model["long_description"]}')
print(f'label: {model["label"]}')
-------------------------------------------------- model_id: ibm/granite-ttm-1024-96-r2 functions: [{'id': 'time_series_forecast'}] long_description: TinyTimeMixers (TTMs) are compact pre-trained models for Multivariate Time-Series Forecasting, open-sourced by IBM Research. Given the last 1024 time-points (i.e. context length), this model can forecast up to next 96 time-points (i.e. forecast length) in future. This model is targeted towards a forecasting setting of context length 1024 and forecast length 96 and recommended for hourly and minutely resolutions (Ex. 10 min, 15 min, 1 hour, etc) label: granite-ttm-1024-96-r2 -------------------------------------------------- model_id: ibm/granite-ttm-1536-96-r2 functions: [{'id': 'time_series_forecast'}] long_description: TinyTimeMixers (TTMs) are compact pre-trained models for Multivariate Time-Series Forecasting, open-sourced by IBM Research. Given the last 1536 time-points (i.e. context length), this model can forecast up to next 96 time-points (i.e. forecast length) in future. This model is targeted towards a forecasting setting of context length 1536 and forecast length 96 and recommended for hourly and minutely resolutions (Ex. 10 min, 15 min, 1 hour, etc) label: granite-ttm-1536-96-r2 -------------------------------------------------- model_id: ibm/granite-ttm-512-96-r2 functions: [{'id': 'time_series_forecast'}] long_description: TinyTimeMixers (TTMs) are compact pre-trained models for Multivariate Time-Series Forecasting, open-sourced by IBM Research. Given the last 512 time-points (i.e. context length), this model can forecast up to next 96 time-points (i.e. forecast length) in future. This model is targeted towards a forecasting setting of context length 512 and forecast length 96 and recommended for hourly and minutely resolutions (Ex. 10 min, 15 min, 1 hour, etc) label: granite-ttm-512-96-r2
We need to specify the model_id
that will be used for inferencing.
ts_model_id = client.foundation_models.TimeSeriesModels.GRANITE_TTM_512_96_R2
Next, we need to intialize an object of the TSModelInference
class. TSModelInference
is a wrapper around watsonx.ai models that provides integration around the models. This ts_model
object will be able to make an API call to the model hosted by watsonx.ai.
ts_model = TSModelInference(
model_id=ts_model_id,
api_client=client
)
Here, we provide a set of model parameters as a specification that will influence our predictions. In this case, we set the frequency to a time range of 1 hour. TSForecastParameters
documentation provides details about the parameters to the model. You can also see more details in our API documentation.
forecasting_params = TSForecastParameters(
timestamp_column=timestamp_column,
freq="1h",
target_columns=[target_column],
)
Step 6. Forecasting using your model¶
In order to create predictions, we'll call the forecast()
method to compute values for our target variable total_load_actual
, predicting electricity usage for the future time range. Using the Granite TTM (TinyTimeMixer) model via the API, we get a fast response time, even for a multivariate forecasting problem.
results = ts_model.forecast(data=data, params=forecasting_params)['results'][0]
For an initial evaluation of our model's performance, we'll use a data visualization. Here, we plot the predictions (shown in gold), along with the historical data (shown in blue) for past values. We can also see the green dotted line, our future_data
or the ground truth against which to compare our predictions.
This gold line predicts hourly energy demand based on the patterns in the historical data. In evaluating the green dotted line against the gold line, we can see instances of overlap where the model's predictions are accurate.
plt.figure(figsize=(10,2))
plt.plot(np.asarray(data[timestamp_column], dtype='datetime64[s]'), data[target_column], label="Historical data")
plt.plot(np.asarray(results[timestamp_column], dtype='datetime64[s]'), results[target_column], label="Predicted")
plt.plot(np.asarray(future_data[timestamp_column], dtype='datetime64[s]'), future_data[target_column], label="True", linestyle='dashed')
plt.legend(loc='center left', bbox_to_anchor=(1, 0.5))
plt.show()
We can also calculate evaluation metrics to quantify the accuracy of the model. We'll use the Mean Absolute Percentage Error (MAPE) as our metric. A lower percentage is better and our result, at less than 10%, is a good indication of the performance of the model for this use case.
Using the watsonx.ai Timeseries Forecasting API, the Granite model's accuracy and response time for a multivariate forecasting problem is noteworthy.
mape = mean_absolute_percentage_error(future_data[target_column], results[target_column]) * 100
mape
6.081524040679701
Summary¶
In this tutorial, you used the watsonx.ai Timeseries Forecasting API and SDK to make predictions for energy demand.
Whether you’re improving and streamlining operations, predicting share prices, navigating emissions for sustainability or exploring entirely new use cases, this Forecasting API delivers the foundation for smarter, data-driven decisions, complementing watsonx.ai’s existing foundation model library.
Get started today for free at our IBM watsonx Developer Hub.
For more use cases using watsonx.ai and IBM's open source Granite models, try out our other tutorials.