1. Import packages

First, we import the required packages and initialize the Nixtla client.

import pandas as pd
import os

from nixtla import NixtlaClient
from datasetsforecast.m5 import M5
nixtla_client = NixtlaClient(
    # defaults to os.environ.get("NIXTLA_API_KEY")
    api_key = 'my_api_key_provided_by_nixtla'
)

👍 Use an Azure AI endpoint

To use an Azure AI endpoint, remember to set also the base_url argument:

nixtla_client = NixtlaClient(base_url="you azure ai endpoint", api_key="your api_key")

2. Load M5 data

Let’s see an example on predicting sales of products of the M5 dataset. The M5 dataset contains daily product demand (sales) for 10 retail stores in the US.

First, we load the data using datasetsforecast. This returns:

  • Y_df, containing the sales (y column), for each unique product (unique_id column) at every timestamp (ds column).
  • X_df, containing additional relevant information for each unique product (unique_id column) at every timestamp (ds column).

Tip

You can find a tutorial on including exogenous variables in your forecast with TimeGPT here.

Y_df, X_df, S_df = M5.load(directory=os.getcwd())

Y_df.head(10)
100%|██████████| 50.2M/50.2M [00:00<00:00, 58.1MiB/s]
INFO:datasetsforecast.utils:Successfully downloaded m5.zip, 50219189, bytes.
INFO:datasetsforecast.utils:Decompressing zip file...
INFO:datasetsforecast.utils:Successfully decompressed c:\Users\ospra\OneDrive\Nixtla\Repositories\nixtla\m5\datasets\m5.zip
c:\Users\ospra\miniconda3\envs\nixtla\lib\site-packages\datasetsforecast\m5.py:143: FutureWarning: The default of observed=False is deprecated and will be changed to True in a future version of pandas. Pass observed=False to retain current behavior or observed=True to adopt the future default and silence this warning.
  keep_mask = long.groupby('id')['y'].transform(first_nz_mask, engine='numba')
unique_iddsy
0FOODS_1_001_CA_12011-01-293.0
1FOODS_1_001_CA_12011-01-300.0
2FOODS_1_001_CA_12011-01-310.0
3FOODS_1_001_CA_12011-02-011.0
4FOODS_1_001_CA_12011-02-024.0
5FOODS_1_001_CA_12011-02-032.0
6FOODS_1_001_CA_12011-02-040.0
7FOODS_1_001_CA_12011-02-052.0
8FOODS_1_001_CA_12011-02-060.0
9FOODS_1_001_CA_12011-02-070.0

For this example, we will only keep the additional relevant information from the column sell_price. This column shows the selling price of the product, and we expect demand to fluctuate given a different selling price.

X_df = X_df[['unique_id', 'ds', 'sell_price']]

X_df.head(10)
unique_iddssell_price
0FOODS_1_001_CA_12011-01-292.0
1FOODS_1_001_CA_12011-01-302.0
2FOODS_1_001_CA_12011-01-312.0
3FOODS_1_001_CA_12011-02-012.0
4FOODS_1_001_CA_12011-02-022.0
5FOODS_1_001_CA_12011-02-032.0
6FOODS_1_001_CA_12011-02-042.0
7FOODS_1_001_CA_12011-02-052.0
8FOODS_1_001_CA_12011-02-062.0
9FOODS_1_001_CA_12011-02-072.0

3. Forecasting demand using price as an exogenous variable

We will forecast the demand for a single product only, for all 10 retail stores in the dataset. We choose a food product with many price changes identified by FOODS_1_129_.

products = [
            'FOODS_1_129_CA_1', 
            'FOODS_1_129_CA_2', 
            'FOODS_1_129_CA_3', 
            'FOODS_1_129_CA_4', 
            'FOODS_1_129_TX_1', 
            'FOODS_1_129_TX_2', 
            'FOODS_1_129_TX_3',
            'FOODS_1_129_WI_1',
            'FOODS_1_129_WI_2',
            'FOODS_1_129_WI_3'
            ]
Y_df_product = Y_df.query('unique_id in @products')
X_df_product = X_df.query('unique_id in @products')

We merge our two dataframes to create the dataset to be used in TimeGPT.

df = Y_df_product.merge(X_df_product)

df.head(10)
unique_iddsysell_price
0FOODS_1_129_CA_12011-02-011.06.22
1FOODS_1_129_CA_12011-02-020.06.22
2FOODS_1_129_CA_12011-02-030.06.22
3FOODS_1_129_CA_12011-02-040.06.22
4FOODS_1_129_CA_12011-02-051.06.22
5FOODS_1_129_CA_12011-02-060.06.22
6FOODS_1_129_CA_12011-02-070.06.22
7FOODS_1_129_CA_12011-02-080.06.22
8FOODS_1_129_CA_12011-02-090.06.22
9FOODS_1_129_CA_12011-02-103.06.22

Let’s investigate how the demand - our target y - of these products has evolved in the last year of data. We see that in the California stores (with a CA_ suffix), the product has sold intermittently, whereas in the other regions (TX and WY) sales where less intermittent. Note that the plot only shows 8 (out of 10) stores.

nixtla_client.plot(df, 
                   unique_ids=products,
                   max_insample_length=365)

Next, we look at the sell_price of these products across the entire data available. We find that there have been relatively few price changes - about 20 in total - over the period 2011 - 2016. Note that the plot only shows 8 (out of 10) stores.

nixtla_client.plot(df, 
                   unique_ids=products,
                   target_col='sell_price')

Let’s turn to our forecasting task. We will forecast the last 28 days in the dataset.

To use the sell_price exogenous variable in TimeGPT, we have to add it as future values. Therefore, we create a future values dataframe, that contains the unique_id, the timestamp ds, and sell_price.

future_ex_vars_df = df.drop(columns = ['y'])
future_ex_vars_df = future_ex_vars_df.query("ds >= '2016-05-23'")

future_ex_vars_df.head(10)
unique_iddssell_price
1938FOODS_1_129_CA_12016-05-235.74
1939FOODS_1_129_CA_12016-05-245.74
1940FOODS_1_129_CA_12016-05-255.74
1941FOODS_1_129_CA_12016-05-265.74
1942FOODS_1_129_CA_12016-05-275.74
1943FOODS_1_129_CA_12016-05-285.74
1944FOODS_1_129_CA_12016-05-295.74
1945FOODS_1_129_CA_12016-05-305.74
1946FOODS_1_129_CA_12016-05-315.74
1947FOODS_1_129_CA_12016-06-015.74

Next, we limit our input dataframe to all but the 28 forecast days:

df_train = df.query("ds < '2016-05-23'")

df_train.tail(10)
unique_iddsysell_price
19640FOODS_1_129_WI_32016-05-133.07.23
19641FOODS_1_129_WI_32016-05-141.07.23
19642FOODS_1_129_WI_32016-05-152.07.23
19643FOODS_1_129_WI_32016-05-163.07.23
19644FOODS_1_129_WI_32016-05-171.07.23
19645FOODS_1_129_WI_32016-05-182.07.23
19646FOODS_1_129_WI_32016-05-193.07.23
19647FOODS_1_129_WI_32016-05-201.07.23
19648FOODS_1_129_WI_32016-05-210.07.23
19649FOODS_1_129_WI_32016-05-220.07.23

Let’s call the forecast method of TimeGPT:

timegpt_fcst_df = nixtla_client.forecast(df=df_train, X_df=future_ex_vars_df, h=28)
timegpt_fcst_df.head()
INFO:nixtla.nixtla_client:Validating inputs...
INFO:nixtla.nixtla_client:Preprocessing dataframes...
INFO:nixtla.nixtla_client:Inferred freq: D
WARNING:nixtla.nixtla_client:The specified horizon "h" exceeds the model horizon. This may lead to less accurate forecasts. Please consider using a smaller horizon.
INFO:nixtla.nixtla_client:Using the following exogenous variables: sell_price
INFO:nixtla.nixtla_client:Calling Forecast Endpoint...
unique_iddsTimeGPT
0FOODS_1_129_CA_12016-05-230.875594
1FOODS_1_129_CA_12016-05-240.777731
2FOODS_1_129_CA_12016-05-250.786871
3FOODS_1_129_CA_12016-05-260.828223
4FOODS_1_129_CA_12016-05-270.791228

📘 Available models in Azure AI

If you are using an Azure AI endpoint, please be sure to set model="azureai":

nixtla_client.forecast(..., model="azureai")

For the public API, we support two models: timegpt-1 and timegpt-1-long-horizon.

By default, timegpt-1 is used. Please see this tutorial on how and when to use timegpt-1-long-horizon.

We plot the forecast, the actuals and the last 28 days before the forecast period:

nixtla_client.plot(
    df[['unique_id', 'ds', 'y']], 
    timegpt_fcst_df, 
    max_insample_length=56, 
)

4. What if? Varying price when forecasting demand

What happens when we change the price of the products in our forecast period? Let’s see how our forecast changes when we increase and decrease the sell_price by 5%.

price_change = 0.05

# Plus 
future_ex_vars_df_plus= future_ex_vars_df.copy()
future_ex_vars_df_plus["sell_price"] = future_ex_vars_df_plus["sell_price"] * (1 + price_change)
# Minus
future_ex_vars_df_minus = future_ex_vars_df.copy()
future_ex_vars_df_minus["sell_price"] = future_ex_vars_df_minus["sell_price"] * (1 - price_change)

Let’s create a new set of forecasts with TimeGPT.

timegpt_fcst_df_plus = nixtla_client.forecast(df=df_train, X_df=future_ex_vars_df_plus, h=28)
timegpt_fcst_df_minus = nixtla_client.forecast(df=df_train, X_df=future_ex_vars_df_minus, h=28)
INFO:nixtla.nixtla_client:Validating inputs...
INFO:nixtla.nixtla_client:Preprocessing dataframes...
INFO:nixtla.nixtla_client:Inferred freq: D
WARNING:nixtla.nixtla_client:The specified horizon "h" exceeds the model horizon. This may lead to less accurate forecasts. Please consider using a smaller horizon.
INFO:nixtla.nixtla_client:Using the following exogenous variables: sell_price
INFO:nixtla.nixtla_client:Calling Forecast Endpoint...
INFO:nixtla.nixtla_client:Validating inputs...
INFO:nixtla.nixtla_client:Preprocessing dataframes...
INFO:nixtla.nixtla_client:Inferred freq: D
WARNING:nixtla.nixtla_client:The specified horizon "h" exceeds the model horizon. This may lead to less accurate forecasts. Please consider using a smaller horizon.
INFO:nixtla.nixtla_client:Using the following exogenous variables: sell_price
INFO:nixtla.nixtla_client:Calling Forecast Endpoint...

📘 Available models in Azure AI

If you are using an Azure AI endpoint, please be sure to set model="azureai":

nixtla_client.forecast(..., model="azureai")

For the public API, we support two models: timegpt-1 and timegpt-1-long-horizon.

By default, timegpt-1 is used. Please see this tutorial on how and when to use timegpt-1-long-horizon.

Let’s combine our three forecasts. We see that - as we expect - demand is expected to slightly increase (decrease) if we reduce (increase) the price. In other words, a cheaper product leads to higher sales and vice versa.

Note

Price elasticity is a measure of how sensitive the (product) demand is to a change in price. Read more about it here.

timegpt_fcst_df_plus = timegpt_fcst_df_plus.rename(columns={'TimeGPT':f'TimeGPT-sell_price_plus_{price_change * 100:.0f}%'})
timegpt_fcst_df_minus = timegpt_fcst_df_minus.rename(columns={'TimeGPT':f'TimeGPT-sell_price_minus_{price_change * 100:.0f}%'})

timegpt_fcst_df = pd.concat([timegpt_fcst_df, 
                             timegpt_fcst_df_plus[f'TimeGPT-sell_price_plus_{price_change * 100:.0f}%'], 
                             timegpt_fcst_df_minus[f'TimeGPT-sell_price_minus_{price_change * 100:.0f}%']], axis=1)

timegpt_fcst_df.head(10)
unique_iddsTimeGPTTimeGPT-sell_price_plus_5%TimeGPT-sell_price_minus_5%
0FOODS_1_129_CA_12016-05-230.8755940.8470061.370029
1FOODS_1_129_CA_12016-05-240.7777310.7491421.272166
2FOODS_1_129_CA_12016-05-250.7868710.7582831.281306
3FOODS_1_129_CA_12016-05-260.8282230.7996351.322658
4FOODS_1_129_CA_12016-05-270.7912280.7626401.285663
5FOODS_1_129_CA_12016-05-280.8191330.7905451.313568
6FOODS_1_129_CA_12016-05-290.8399920.8114041.334427
7FOODS_1_129_CA_12016-05-300.8430700.8144811.337505
8FOODS_1_129_CA_12016-05-310.8330890.8045001.327524
9FOODS_1_129_CA_12016-06-010.8550320.8264431.349467

Finally, let’s plot the forecasts for our different pricing scenarios, showing how TimeGPT forecasts a different demand when the price of a set of products is changed. In the graphs we can see that for specific products for certain periods the discount increases expected demand, while during other periods and for other products, price change has a smaller effect on total demand.

nixtla_client.plot(
    df[['unique_id', 'ds', 'y']], 
    timegpt_fcst_df, 
    max_insample_length=56, 
)

In this example, we have shown you: * How you can use TimeGPT to forecast product demand using price as an exogenous variable * How you can evaluate different pricing scenarios that affect product demand

Important

  • This method assumes that historical demand and price behaviour is predictive of future demand, and omits other factors affecting demand. To include these other factors, use additional exogenous variables that provide the model with more context about the factors influencing demand.
  • This method is sensitive to unmodelled events that affect the demand, such as sudden market shifts. To include those, use additional exogenous variables indicating such sudden shifts if they have been observed in the past too.