Quickstart

In this notebook, we go over the main functionalities of the library:

We will only show some minimal “get started” examples here. For more in depth information, you can refer to our user guide and example notebooks.

Installing Darts

We recommend using some virtual environment. Then there are mainly two ways.

With pip:

pip install darts

With conda

conda install -c conda-forge -c pytorch u8darts-all

Consult the detailed install guide if you run into issues or want to install a different flavour (avoiding certain dependencies).

First, let’s import a few things:

[1]:
%matplotlib inline

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

from darts import TimeSeries
from darts.datasets import AirPassengersDataset

Building and manipulating TimeSeries

TimeSeries is the main data class in Darts. A TimeSeries represents a univariate or multivariate time series, with a proper time index. The time index can either be of type pandas.DatetimeIndex (containing datetimes), or of type pandas.RangeIndex (containing integers; useful for representing sequential data without specific timestamps). In some cases, TimeSeries can even represent probabilistic series, in order for instance to obtain confidence intervals. All models in Darts consume TimeSeries and produce TimeSeries.

Read data and build a TimeSeries

TimeSeries can be built easily using a few factory methods:

  • From an entire Pandas DataFrame, using TimeSeries.from_dataframe() (docs).

  • From a time index and an array of corresponding values, using TimeSeries.from_times_and_values() (docs).

  • From a NumPy array of values, using TimeSeries.from_values() (docs).

  • From a Pandas Series, using TimeSeries.from_series() (docs).

  • From an xarray.DataArray, using TimeSeries.from_xarray() (docs).

  • From a CSV file, using TimeSeries.from_csv() (docs).

Below, we get a TimeSeries by directly loading the air passengers series from one of the datasets available in Darts:

[2]:
series = AirPassengersDataset().load()
series.plot()
[2]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_5_1.png

Some TimeSeries Operations

TimeSeries support different kinds of operations - here are a few examples.

splitting

We can also split at a fraction of the series, at a pandas Timestamp or at an integer index value.

[3]:
series1, series2 = series.split_after(0.75)
series1.plot()
series2.plot()
[3]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_7_1.png

slicing:

[4]:
series1, series2 = series[:-36], series[-36:]
series1.plot()
series2.plot()
[4]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_9_1.png

arithmetic operations:

[5]:
series_noise = TimeSeries.from_times_and_values(
    series.time_index, np.random.randn(len(series))
)
(series / 2 + 20 * series_noise - 10).plot()
[5]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_11_1.png

stacking

Concatenating a new dimension to produce a new single multivariate series.

[6]:
(series / 50).stack(series_noise).plot()
[6]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_13_1.png

mapping:

[7]:
series.map(np.log).plot()
[7]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_15_1.png

mapping on both timestamps and values:

[8]:
series.map(lambda ts, x: x / ts.days_in_month).plot()
[8]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_17_1.png

Adding some datetime attribute as an extra dimension (yielding a multivariate series):

[9]:
(series / 20).add_datetime_attribute("month").plot()
[9]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_19_1.png

Adding some binary holidays component:

[10]:
(series / 200).add_holidays("US").plot()
[10]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_21_1.png

differencing:

[11]:
series.diff().plot()
[11]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_23_1.png

Filling missing values (using a ``utils`` function).

Missing values are represented by np.nan.

[12]:
from darts.utils.missing_values import fill_missing_values

values = np.arange(50, step=0.5)
values[10:30] = np.nan
values[60:95] = np.nan
series_ = TimeSeries.from_values(values)

(series_ - 10).plot(label="with missing values (shifted below)")
fill_missing_values(series_).plot(label="without missing values")
[12]:
<Axes: xlabel='time'>
../_images/quickstart_00-quickstart_25_1.png

Creating a training and validation series

For what follows, we will split our TimeSeries into a training and a validation series. Note: in general, it is also a good practice to keep a test series aside and never touch it until the end of the process. Here, we just build a training and a validation series for simplicity.

The training series will be a TimeSeries containing values until January 1958 (excluded), and the validation series a TimeSeries containing the rest:

[13]:
train, val = series.split_before(pd.Timestamp("19580101"))
train.plot(label="training")
val.plot(label="validation")
[13]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_27_1.png

Training forecasting models and making predictions

Playing with toy models

There is a collection of “naive” baseline models in Darts, which can be very useful to get an idea of the bare minimum accuracy that one could expect. For example, the NaiveSeasonal(K) model always “repeats” the value that occured K time steps ago.

In its most naive form, when K=1, this model simply always repeats the last value of the training series:

[14]:
from darts.models import NaiveSeasonal

naive_model = NaiveSeasonal(K=1)
naive_model.fit(train)
naive_forecast = naive_model.predict(36)

series.plot(label="actual")
naive_forecast.plot(label="naive forecast (K=1)")
/Users/dennisbader/miniconda3/envs/darts310/lib/python3.10/site-packages/statsforecast/utils.py:237: FutureWarning: 'M' is deprecated and will be removed in a future version, please use 'ME' instead.
  "ds": pd.date_range(start="1949-01-01", periods=len(AirPassengers), freq="M"),
[14]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_29_2.png

It’s very easy to fit models and produce predictions on TimeSeries. All the models have a fit() and a predict() function. This is similar to Scikit-learn, except that it is specific to time series. The fit() function takes in argument the training time series on which to fit the model, and the predict() function takes in argument the number of time steps (after the end of the training series) over which to forecast.

Inspect Seasonality

Our model above is perhaps a bit too naive. We can already improve by exploiting the seasonality in the data. It seems quite obvious that the data has a yearly seasonality, which we can confirm by looking at the auto-correlation function (ACF), and highlighting the lag m=12:

[15]:
from darts.utils.statistics import plot_acf, check_seasonality

plot_acf(train, m=12, alpha=0.05)
../_images/quickstart_00-quickstart_31_0.png

The ACF presents a spike at x = 12, which suggests a yearly seasonality trend (highlighted in red). The blue zone determines the significance of the statistics for a confidence level of \(\alpha = 5\%\). We can also run a statistical check of seasonality for each candidate period m:

[16]:
for m in range(2, 25):
    is_seasonal, period = check_seasonality(train, m=m, alpha=0.05)
    if is_seasonal:
        print("There is seasonality of order {}.".format(period))
There is seasonality of order 12.

A less naive model

Let’s try the NaiveSeasonal model again with a seasonality of 12:

[17]:
seasonal_model = NaiveSeasonal(K=12)
seasonal_model.fit(train)
seasonal_forecast = seasonal_model.predict(36)

series.plot(label="actual")
seasonal_forecast.plot(label="naive forecast (K=12)")
[17]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_35_1.png

This is better, but we are still missing the trend. Fortunately, there is also another naive baseline model capturing the trend, which is called NaiveDrift. This model simply produces linear predictions, with a slope that is determined by the first and last values of the training set:

[18]:
from darts.models import NaiveDrift

drift_model = NaiveDrift()
drift_model.fit(train)
drift_forecast = drift_model.predict(36)

combined_forecast = drift_forecast + seasonal_forecast - train.last_value()

series.plot()
combined_forecast.plot(label="combined")
drift_forecast.plot(label="drift")
[18]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_37_1.png

What happened there? We simply fit a naive drift model, and add its forecast to the seasonal forecast we had previously. We also subtract the last value of the training set from the result, so that the resulting combined forecast starts off with the right offset.

Computing error metrics

This looks already like a fairly decent forecast, and we did not use any non-naive model yet. In fact - any model should be able to beat this.

So what’s the error we will have to beat? We will use the Mean Absolute Percentage Error (MAPE) (note that in practice there are often good reasons not to use the MAPE - we use it here as it is quite convenient and scale independent). In Darts it is a simple function call:

[19]:
from darts.metrics import mape

print(
    "Mean absolute percentage error for the combined naive drift + seasonal: {:.2f}%.".format(
        mape(series, combined_forecast)
    )
)
Mean absolute percentage error for the combined naive drift + seasonal: 5.66%.

darts.metrics contains many more metrics to compare time series. The metrics will compare only common slices of series when the two series are not aligned, and parallelize computation over a large number of pairs of series - but let’s not get ahead of ourselves.

Quickly try out several models

Darts is built to make it easy to train and validate several models in a unified way. Let’s train a few more and compute their respective MAPE on the validation set:

[20]:
from darts.models import ExponentialSmoothing, TBATS, AutoARIMA, Theta


def eval_model(model):
    model.fit(train)
    forecast = model.predict(len(val))
    print("model {} obtains MAPE: {:.2f}%".format(model, mape(val, forecast)))


eval_model(ExponentialSmoothing())
eval_model(TBATS())
eval_model(AutoARIMA())
eval_model(Theta())
model ExponentialSmoothing() obtains MAPE: 5.11%
model TBATS() obtains MAPE: 5.87%
model AutoARIMA() obtains MAPE: 11.65%
model Theta() obtains MAPE: 8.15%

Here, we did only built these models with their default parameters. We can probably do better if we fine-tune to our problem. Let’s try with the Theta method.

Searching for hyper-parameters with the Theta method

The model Theta contains an implementation of Assimakopoulos and Nikolopoulos’ Theta method. This method has had some success, particularly in the M3-competition.

Though the value of the Theta parameter is often set to 0 in applications, our implementation supports a variable value for parameter tuning purposes. Let’s try to find a good value for Theta:

[21]:
# Search for the best theta parameter, by trying 50 different values
thetas = 2 - np.linspace(-10, 10, 50)

best_mape = float("inf")
best_theta = 0

for theta in thetas:
    model = Theta(theta)
    model.fit(train)
    pred_theta = model.predict(len(val))
    res = mape(val, pred_theta)

    if res < best_mape:
        best_mape = res
        best_theta = theta
[22]:
best_theta_model = Theta(best_theta)
best_theta_model.fit(train)
pred_best_theta = best_theta_model.predict(len(val))

print(
    "The MAPE is: {:.2f}, with theta = {}.".format(
        mape(val, pred_best_theta), best_theta
    )
)
The MAPE is: 4.40, with theta = -3.5102040816326543.
[23]:
train.plot(label="train")
val.plot(label="true")
pred_best_theta.plot(label="prediction")
[23]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_48_1.png

We can observe that the model with best_theta is so far the best we have, in terms of MAPE.

Backtesting: simulate historical forecasting

So at this point we have a model that performs well on our validation set, and that’s good. But, how can we know the performance we would have obtained if we had been using this model historically?

Backtesting simulates predictions that would have been obtained historically with a given model. It can take a while to produce, since the model is (by default) re-trained every time the simulated prediction time advances.

Such simulated forecasts are always defined with respect to a forecast horizon, which is the number of time steps that separate the prediction time from the forecast time. In the example below, we simulate forecasts done for 3 months in the future (compared to prediction time). The result of calling historical_forecasts() is (by default) a TimeSeries that contains those 3-months ahead forecasts:

[24]:
historical_fcast_theta = best_theta_model.historical_forecasts(
    series, start=0.6, forecast_horizon=3, verbose=True
)

series.plot(label="data")
historical_fcast_theta.plot(label="backtest 3-months ahead forecast (Theta)")
print("MAPE = {:.2f}%".format(mape(historical_fcast_theta, series)))
MAPE = 7.70%
../_images/quickstart_00-quickstart_51_2.png

So it seems that our best model on validation set is not doing so great anymore when we backtest it (did I hear overfitting :D)

To have a closer look at the errors, we can also use the backtest() method to obtain all the raw errors (say, MAPE errors) that would have been obtained by our model:

[25]:
best_theta_model = Theta(best_theta)

raw_errors = best_theta_model.backtest(
    series, start=0.6, forecast_horizon=3, metric=mape, reduction=None, verbose=True
)

from darts.utils.statistics import plot_hist

plot_hist(
    raw_errors,
    bins=np.arange(0, max(raw_errors), 1),
    title="Individual backtest error scores (histogram)",
)
../_images/quickstart_00-quickstart_53_1.png

Finally, using backtest() we can also get a simpler view of the average error over the historical forecasts:

[26]:
average_error = best_theta_model.backtest(
    series,
    start=0.6,
    forecast_horizon=3,
    metric=mape,
    reduction=np.mean,  # this is actually the default
    verbose=True,
)

print("Average error (MAPE) over all historical forecasts: %.2f" % average_error)
Average error (MAPE) over all historical forecasts: 6.36

We could also for instance have specified the argument reduction=np.median to get the median MAPE instead.

Let’s look at the fitted value residuals of our current Theta model, i.e. the difference between the 1-step forecasts at every point in time obtained by fitting the model on all previous points, and the actual observed values:

[27]:
from darts.utils.statistics import plot_residuals_analysis

plot_residuals_analysis(best_theta_model.residuals(series))
../_images/quickstart_00-quickstart_58_0.png

We can see that the distribution is not centered at 0, which means that our Theta model is biased. We can also make out a large ACF value at lag equal to 12, which indicates that the residuals contain information that was not used by the model.

Could we maybe do better with a simple ExponentialSmoothing model?

[28]:
model_es = ExponentialSmoothing(seasonal_periods=12)
historical_fcast_es = model_es.historical_forecasts(
    series, start=0.6, forecast_horizon=3, verbose=True
)

series.plot(label="data")
historical_fcast_es.plot(label="backtest 3-months ahead forecast (Exp. Smoothing)")
print("MAPE = {:.2f}%".format(mape(historical_fcast_es, series)))
MAPE = 4.45%
../_images/quickstart_00-quickstart_60_2.png

This much better! We get a mean absolute percentage error of about 4-5% when backtesting with a 3-months forecast horizon in this case.

[29]:
plot_residuals_analysis(model_es.residuals(series))
../_images/quickstart_00-quickstart_62_0.png

The residual analysis also reflects an improved performance in that we now have a distribution of the residuals centred at value 0, and the ACF values, although not insignificant, have lower magnitudes.

Machine learning and global models

Darts has a rich support for machine learning and deep learning forecasting models; for instance:

  • RegressionModel can wrap around any sklearn-compatible regression model to produce forecasts (it has its own section below).

  • RNNModel is a flexible RNN implementation, which can be used like DeepAR.

  • NBEATSModel implements the N-BEATS model.

  • TFTModel implements the Temporal Fusion Transformer model.

  • TCNModel implements temporal convolutional networks.

In addition to supporting the same basic fit()/predict() interface as the other models, these models are also global models, as they support being trained on multiple time series (sometimes referred to as meta learning).

This is a key point of using ML-based models for forecasting: more often than not, ML models (especially deep learning models) need to be trained on large amounts of data, which often means a large amount of separate yet related time series.

In Darts, the basic way to specify multiple TimeSeries is using a Sequence of TimeSeries (for instance, a simple list of TimeSeries).

A toy example with two series

These models can be trained on thousands of series. Here, for the sake of illustration, we will load two distinct series - the air traffic passenger count and another series containing the number of pounds of milk produced per cow monthly. We also cast our series to np.float32 as that will slightly speedup the training:

[30]:
from darts.datasets import AirPassengersDataset, MonthlyMilkDataset

series_air = AirPassengersDataset().load().astype(np.float32)
series_milk = MonthlyMilkDataset().load().astype(np.float32)

# set aside last 36 months of each series as validation set:
train_air, val_air = series_air[:-36], series_air[-36:]
train_milk, val_milk = series_milk[:-36], series_milk[-36:]

train_air.plot()
val_air.plot()
train_milk.plot()
val_milk.plot()
[30]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_65_1.png

First, let’s scale these two series between 0 and 1, as that will benefit most ML models. We will use a Scaler for this:

[31]:
from darts.dataprocessing.transformers import Scaler

scaler = Scaler()
train_air_scaled, train_milk_scaled = scaler.fit_transform([train_air, train_milk])

train_air_scaled.plot()
train_milk_scaled.plot()
[31]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_67_1.png

Note how we can scale several series in one go. We can also parallelize this sort of operations over multiple processors by specifying n_jobs.

Using deep learning: example with N-BEATS

Next, we will build an N-BEATS model. This model can be tuned with many hyper-parameters (such as number of stacks, layers, etc). Here, for simplicity, we will use it with default hyper-parameters. The only two hyper-parameters that we have to provide are:

  • input_chunk_length: this is the “lookback window” of the model - i.e., how many time steps of history the neural network takes as input to produce its output in a forward pass.

  • output_chunk_length: this is the “forward window” of the model - i.e., how many time steps of future values the neural network outputs in a forward pass.

The random_state parameter is just here to get reproducible results.

Most neural networks in Darts require these two parameters. Here, we will use multiples of the seasonality. We are now ready to fit our model on our two series (by giving a list containing the two series to fit()):

[32]:
from darts.models import NBEATSModel

model = NBEATSModel(input_chunk_length=24, output_chunk_length=12, random_state=42)

model.fit([train_air_scaled, train_milk_scaled], epochs=50, verbose=True);
GPU available: True (mps), used: True
TPU available: False, using: 0 TPU cores
IPU available: False, using: 0 IPUs
HPU available: False, using: 0 HPUs

  | Name          | Type             | Params
---------------------------------------------------
0 | criterion     | MSELoss          | 0
1 | train_metrics | MetricCollection | 0
2 | val_metrics   | MetricCollection | 0
3 | stacks        | ModuleList       | 6.2 M
---------------------------------------------------
6.2 M     Trainable params
1.4 K     Non-trainable params
6.2 M     Total params
24.787    Total estimated model params size (MB)
`Trainer.fit` stopped: `max_epochs=50` reached.

Let’s now get some forecasts 36 months ahead, for our two series. We can just use the series argument of the fit() function to tell the model which series to forecast. Importantly, the output_chunk_length does not directly constrain the forecast horizon n that can be used with predict(). Here, we trained the model with output_chunk_length=12 and produce forecasts for n=36 months ahead; this is simply done in an auto-regressive way behind the scenes (where the network recursively consumes its previous outputs).

[33]:
pred_air = model.predict(series=train_air_scaled, n=36)
pred_milk = model.predict(series=train_milk_scaled, n=36)

# scale back:
pred_air, pred_milk = scaler.inverse_transform([pred_air, pred_milk])

plt.figure(figsize=(10, 6))
series_air.plot(label="actual (air)")
series_milk.plot(label="actual (milk)")
pred_air.plot(label="forecast (air)")
pred_milk.plot(label="forecast (milk)")
GPU available: True (mps), used: True
TPU available: False, using: 0 TPU cores
IPU available: False, using: 0 IPUs
HPU available: False, using: 0 HPUs
GPU available: True (mps), used: True
TPU available: False, using: 0 TPU cores
IPU available: False, using: 0 IPUs
HPU available: False, using: 0 HPUs
[33]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_72_5.png

Our forecasts are actually not so terrible, considering that we use one model with default hyper-parameters to capture both air passengers and milk production!

The model seems quite OK at capturing the yearly seasonality, but misses the trend for the air series. In the next section, we will try to solve this issue using external data (covariates).

Covariates: using external data

In addition to the target series (the series we are interested to forecast), many models in Darts also accept covariates series in input. Covariates are series that we do not want to forecast, but which can provide helpful additional information to the models. Both the targets and covariates can be multivariate or univariate.

There are two kinds of covariate time series in Darts:

  • past_covariates are series not necessarily known ahead of the forecast time. Those can for instance represent things that have to be measured and are not known upfront. Models do not use the future values of past_covariates when making forecasts.

  • future_covariates are series which are known in advance, up to the forecast horizon. This can represent things such as calendar information, holidays, weather forecasts, etc. Models that accept future_covariates will look at the future values (up to the forecast horizon) when making forecasts.

covariates

Each covariate can potentially be multivariate. If you have several covariate series (such as month and year values), you should stack() or concatenate() them to obtain a multivariate series.

The covariates you provide can be longer than necessary. Darts will try to be smart and slice them in the right way for forecasting the target, based on the time indexes of the different series. You will receive an error if your covariates do not have a sufficient time span, though.

Let’s now build some external covariates containing both monthly and yearly values for our air and milk series. In the cell below, we use the darts.utils.timeseries_generation.datetime_attribute_timeseries() function to generate series containing the month and year values, and we concatenate() these series along the "component" axis in order to obtain one covariate series with two components (month and year), per target series. For simplicity, we directly scale the month and year values to have them between (roughly) 0 and 1:

[34]:
from darts import concatenate
from darts.utils.timeseries_generation import datetime_attribute_timeseries as dt_attr

air_covs = concatenate(
    [
        dt_attr(series_air.time_index, "month", dtype=np.float32) / 12,
        (dt_attr(series_air.time_index, "year", dtype=np.float32) - 1948) / 12,
    ],
    axis="component",
)

milk_covs = concatenate(
    [
        dt_attr(series_milk.time_index, "month", dtype=np.float32) / 12,
        (dt_attr(series_milk.time_index, "year", dtype=np.float32) - 1962) / 13,
    ],
    axis="component",
)

air_covs.plot()
plt.title(
    "one multivariate time series of 2 dimensions, containing covariates for the air series:"
);
../_images/quickstart_00-quickstart_75_0.png

Not all models support all types of covariates. NBEATSModel supports only past_covariates. Therefore, even though our covariates represent calendar information and are known in advance, we will use them as past_covariates with N-BEATS. To train, all we have to do is give them as past_covariates to the fit() function, in the same order as the targets:

[35]:
model = NBEATSModel(input_chunk_length=24, output_chunk_length=12, random_state=42)

model.fit(
    [train_air_scaled, train_milk_scaled],
    past_covariates=[air_covs, milk_covs],
    epochs=50,
    verbose=True,
);
GPU available: True (mps), used: True
TPU available: False, using: 0 TPU cores
IPU available: False, using: 0 IPUs
HPU available: False, using: 0 HPUs

  | Name          | Type             | Params
---------------------------------------------------
0 | criterion     | MSELoss          | 0
1 | train_metrics | MetricCollection | 0
2 | val_metrics   | MetricCollection | 0
3 | stacks        | ModuleList       | 6.6 M
---------------------------------------------------
6.6 M     Trainable params
1.7 K     Non-trainable params
6.6 M     Total params
26.314    Total estimated model params size (MB)
`Trainer.fit` stopped: `max_epochs=50` reached.

Then to produce forecasts, we again have to provide our covariates as past_covariates to the predict() function. Even though the covariates time series also contains “future” values of the covariates up to the forecast horizon, the model will not consume those future values, because it uses them as past covariates (and not future covariates).

[36]:
pred_air = model.predict(series=train_air_scaled, past_covariates=air_covs, n=36)
pred_milk = model.predict(series=train_milk_scaled, past_covariates=milk_covs, n=36)

# scale back:
pred_air, pred_milk = scaler.inverse_transform([pred_air, pred_milk])

plt.figure(figsize=(10, 6))
series_air.plot(label="actual (air)")
series_milk.plot(label="actual (milk)")
pred_air.plot(label="forecast (air)")
pred_milk.plot(label="forecast (milk)")
`predict()` was called with `n > output_chunk_length`: using auto-regression to forecast the values after `output_chunk_length` points. The model will access `(n - output_chunk_length)` future values of your `past_covariates` (relative to the first predicted time step). To hide this warning, set `show_warnings=False`.
GPU available: True (mps), used: True
TPU available: False, using: 0 TPU cores
IPU available: False, using: 0 IPUs
HPU available: False, using: 0 HPUs
`predict()` was called with `n > output_chunk_length`: using auto-regression to forecast the values after `output_chunk_length` points. The model will access `(n - output_chunk_length)` future values of your `past_covariates` (relative to the first predicted time step). To hide this warning, set `show_warnings=False`.
GPU available: True (mps), used: True
TPU available: False, using: 0 TPU cores
IPU available: False, using: 0 IPUs
HPU available: False, using: 0 HPUs
[36]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_79_5.png

It seems that now the model captures better the trend of the air series (which also perturbs a bit the forecasts of the milk series).

Encoders: using covariates for free

Using covariates related to the calendar or time axis (such as months and years as in our example above) is so frequent that deep learning models in Darts have a built-in functionality to use such covariates out of the box.

To easily integrate such covariates to your model, you can simply specify the add_encoders parameter at model creation. This parameter has to be a dictionary containing informations about what should be encoded as extra covariates. Here is an example of what such a dictionary could look like, for a model supporting both past and future covariates:

[37]:
def extract_year(idx):
    """Extract the year each time index entry and normalized it."""
    return (idx.year - 1950) / 50


encoders = {
    "cyclic": {"future": ["month"]},
    "datetime_attribute": {"future": ["hour", "dayofweek"]},
    "position": {"past": ["absolute"], "future": ["relative"]},
    "custom": {"past": [extract_year]},
    "transformer": Scaler(),
}

In the above dictionary, the following things are specified:

  • The month should be used as a future covariate, with a cyclic (sin/cos) encoding.

  • The hour and day-of-the-week should be used as future covariates.

  • The absolute position (time step in the series) should be used as past covariates.

  • The relative position (w.r.t the forecasting time) should be used as future covariates.

  • An additional custom function of the year should be used as past covariates.

  • All the above covariates should be scaled using a Scaler, which will be fit upon calling the model fit() function and used afterwards to transform the covariates.

We refer to the API doc for more informations about how to use encoders. Note that lambda functions cannot be used as they are not pickable.

To replicate our example with month and year used as past covariates with N-BEATS, we can use some encoders as follows:

[38]:
encoders = {"datetime_attribute": {"past": ["month", "year"]}, "transformer": Scaler()}

Now, the whole training of the N-BEATS model with these covariates looks as follows:

[39]:
model = NBEATSModel(
    input_chunk_length=24,
    output_chunk_length=12,
    add_encoders=encoders,
    random_state=42,
)

model.fit([train_air_scaled, train_milk_scaled], epochs=50, verbose=True);
GPU available: True (mps), used: True
TPU available: False, using: 0 TPU cores
IPU available: False, using: 0 IPUs
HPU available: False, using: 0 HPUs

  | Name          | Type             | Params
---------------------------------------------------
0 | criterion     | MSELoss          | 0
1 | train_metrics | MetricCollection | 0
2 | val_metrics   | MetricCollection | 0
3 | stacks        | ModuleList       | 6.6 M
---------------------------------------------------
6.6 M     Trainable params
1.7 K     Non-trainable params
6.6 M     Total params
26.314    Total estimated model params size (MB)
`Trainer.fit` stopped: `max_epochs=50` reached.

And get some forecasts for the air passengers series:

[40]:
pred_air = model.predict(series=train_air_scaled, n=36)

# scale back:
pred_air = scaler.inverse_transform(pred_air)

plt.figure(figsize=(10, 6))
series_air.plot(label="actual (air)")
pred_air.plot(label="forecast (air)")
`predict()` was called with `n > output_chunk_length`: using auto-regression to forecast the values after `output_chunk_length` points. The model will access `(n - output_chunk_length)` future values of your `past_covariates` (relative to the first predicted time step). To hide this warning, set `show_warnings=False`.
GPU available: True (mps), used: True
TPU available: False, using: 0 TPU cores
IPU available: False, using: 0 IPUs
HPU available: False, using: 0 HPUs
[40]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_88_3.png

Regression forecasting models

RegressionModel’s are forecasting models which wrap around sklearn-compatible regression models. The inner regression model is used to predict future values of the target series, as a function of certain lags of the target, past and future covariates. Behind the scenes, the time series are tabularized in order to build a training dataset in the right format.

By default, the RegressionModel will do a linear regression. It is very easy to use any desired sklearn-compatible regression model by specifying the model parameter, but for convenience Darts also provides a couple of ready-made models out of the box:

For example, this is what fitting a Bayesian ridge regression to our toy two-series problem looks like:

[41]:
from darts.models import RegressionModel
from sklearn.linear_model import BayesianRidge

model = RegressionModel(lags=72, lags_future_covariates=[-6, 0], model=BayesianRidge())

model.fit(
    [train_air_scaled, train_milk_scaled], future_covariates=[air_covs, milk_covs]
);

Several things happened above:

  • lags=72 is telling the RegressionModel to look at the past 72 lags of the target.

  • In addition, lags_future_covariates=[-6, 0] means that the model will also look at lags of the future_covariates we provide. Here we enumerate the precise lags we want the models to take into account; the “-6th” and the “0th” lags. The “0th” lag means the “current” lag (i.e., at the time step being forecasted); obviously, knowning this lag requires knowing the data in advance (hence the fact we are using future_covariates). Similarly, -6 means we also look at the value of the covariates 6 months before the forecasted time step (which also requires to know the covariates in advance if we are forecasting at a horizon more than 6 steps ahead).

  • model=BayesianRidge() provides the actual inner regression model.

Now let’s get some forecasts:

[42]:
pred_air, pred_milk = model.predict(
    series=[train_air_scaled, train_milk_scaled],
    future_covariates=[air_covs, milk_covs],
    n=36,
)

# scale back:
pred_air, pred_milk = scaler.inverse_transform([pred_air, pred_milk])

plt.figure(figsize=(10, 6))
series_air.plot(label="actual (air)")
series_milk.plot(label="actual (milk)")
pred_air.plot(label="forecast (air)")
pred_milk.plot(label="forecast (milk)")
[42]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_92_1.png

Note how we obtained the forecasts for the two time series at once above. Similarly, we can also get some metrics over sequences of series:

[43]:
mape([series_air, series_milk], [pred_air, pred_milk])
[43]:
[3.4170475, 5.2831783]

or the average metric over “all” series:

[44]:
mape([series_air, series_milk], [pred_air, pred_milk], component_reduction=np.mean)
[44]:
[3.4170475, 5.2831783]

By the way: similarly to transformers such as Scaler, computing metrics can be parallelized over N processors when executed over many series pairs by specifying n_jobs=N.

It seems that this model performs well on the Air traffic series, how does it do when we backtest it on this one series?

[45]:
bayes_ridge_model = RegressionModel(
    lags=72, lags_future_covariates=[0], model=BayesianRidge()
)

backtest = bayes_ridge_model.historical_forecasts(
    series_air, future_covariates=air_covs, start=0.6, forecast_horizon=3, verbose=True
)

print("MAPE = %.2f" % (mape(backtest, series_air)))
series_air.plot()
backtest.plot()
`enable_optimization=True` is ignored because `retrain` is not `False` or `0`.To hide this warning, set `show_warnings=False` or `enable_optimization=False`.
`enable_optimization=True` is ignored because `forecast_horizon > model.output_chunk_length`.To hide this warning, set `show_warnings=False` or `enable_optimization=False`.
MAPE = 3.66
[45]:
<Axes: xlabel='time'>
../_images/quickstart_00-quickstart_98_4.png

Our best model so far!

Probabilistic forecasts

Some models can produce probabilistic forecasts. This is the case for all deep learning models (such as RNNModel, NBEATSModel, etc …), as well as for ARIMA and ExponentialSmoothing. The full list is available on the Darts README page.

For ARIMA and ExponentialSmoothing, one can simply specify a num_samples parameter to the predict() function. The returned TimeSeries will then be composed of num_samples Monte Carlo samples describing the distribution of the time series’ values. The advantage of relying on Monte Carlo samples (in contrast to, say, explicit confidence intervals) is that they can be used to describe any parametric or non-parametric joint distribution over components, and compute arbitrary quantiles.

[46]:
model_es = ExponentialSmoothing()
model_es.fit(train)
probabilistic_forecast = model_es.predict(len(val), num_samples=500)

series.plot(label="actual")
probabilistic_forecast.plot(label="probabilistic forecast")
plt.legend()
plt.show()
../_images/quickstart_00-quickstart_101_0.png

With neural networks

With neural networks, one has to give a Likelihood object to the model. The likelihoods specify which distribution the model will try to fit, along with potential prior values for the distributions’ parameters. The full list of available likelihoods is available in the docs.

Using likelihoods is easy. For instance, here is what training an TCNModel to fit a Laplace likelihood looks like:

[47]:
from darts.models import TCNModel
from darts.utils.likelihood_models import LaplaceLikelihood

model = TCNModel(
    input_chunk_length=24,
    output_chunk_length=12,
    random_state=42,
    likelihood=LaplaceLikelihood(),
)

model.fit(train_air_scaled, epochs=400, verbose=True);
GPU available: True (mps), used: True
TPU available: False, using: 0 TPU cores
IPU available: False, using: 0 IPUs
HPU available: False, using: 0 HPUs

  | Name          | Type              | Params
----------------------------------------------------
0 | criterion     | MSELoss           | 0
1 | train_metrics | MetricCollection  | 0
2 | val_metrics   | MetricCollection  | 0
3 | dropout       | MonteCarloDropout | 0
4 | res_blocks    | ModuleList        | 166
----------------------------------------------------
166       Trainable params
0         Non-trainable params
166       Total params
0.001     Total estimated model params size (MB)
`Trainer.fit` stopped: `max_epochs=400` reached.

Then to get probabilistic forecasts, we again only need to specify some num_samples >> 1:

[48]:
pred = model.predict(n=36, num_samples=500)

# scale back:
pred = scaler.inverse_transform(pred)

series_air.plot()
pred.plot()
GPU available: True (mps), used: True
TPU available: False, using: 0 TPU cores
IPU available: False, using: 0 IPUs
HPU available: False, using: 0 HPUs
/Users/dennisbader/miniconda3/envs/darts310/lib/python3.10/site-packages/torch/_tensor_str.py:137: UserWarning: MPS: nonzero op is supported natively starting from macOS 13.0. Falling back on CPU. This may have performance implications. (Triggered internally at /Users/runner/work/pytorch/pytorch/pytorch/aten/src/ATen/native/mps/operations/Indexing.mm:283.)
  nonzero_finite_vals = torch.masked_select(
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[48], line 1
----> 1 pred = model.predict(n=36, num_samples=500)
      3 # scale back:
      4 pred = scaler.inverse_transform(pred)

File ~/miniconda3/envs/darts310/lib/python3.10/site-packages/darts/utils/torch.py:112, in random_method.<locals>.decorator(self, *args, **kwargs)
    110 with fork_rng():
    111     manual_seed(self._random_instance.randint(0, high=MAX_TORCH_SEED_VALUE))
--> 112     return decorated(self, *args, **kwargs)

File ~/miniconda3/envs/darts310/lib/python3.10/site-packages/darts/models/forecasting/torch_forecasting_model.py:1401, in TorchForecastingModel.predict(self, n, series, past_covariates, future_covariates, trainer, batch_size, verbose, n_jobs, roll_size, num_samples, num_loader_workers, mc_dropout, predict_likelihood_parameters, show_warnings)
   1382 super().predict(
   1383     n,
   1384     series,
   (...)
   1389     show_warnings=show_warnings,
   1390 )
   1392 dataset = self._build_inference_dataset(
   1393     target=series,
   1394     n=n,
   (...)
   1398     bounds=None,
   1399 )
-> 1401 predictions = self.predict_from_dataset(
   1402     n,
   1403     dataset,
   1404     trainer=trainer,
   1405     verbose=verbose,
   1406     batch_size=batch_size,
   1407     n_jobs=n_jobs,
   1408     roll_size=roll_size,
   1409     num_samples=num_samples,
   1410     num_loader_workers=num_loader_workers,
   1411     mc_dropout=mc_dropout,
   1412     predict_likelihood_parameters=predict_likelihood_parameters,
   1413 )
   1415 return predictions[0] if called_with_single_series else predictions

File ~/miniconda3/envs/darts310/lib/python3.10/site-packages/darts/utils/torch.py:112, in random_method.<locals>.decorator(self, *args, **kwargs)
    110 with fork_rng():
    111     manual_seed(self._random_instance.randint(0, high=MAX_TORCH_SEED_VALUE))
--> 112     return decorated(self, *args, **kwargs)

File ~/miniconda3/envs/darts310/lib/python3.10/site-packages/darts/models/forecasting/torch_forecasting_model.py:1546, in TorchForecastingModel.predict_from_dataset(self, n, input_series_dataset, trainer, batch_size, verbose, n_jobs, roll_size, num_samples, num_loader_workers, mc_dropout, predict_likelihood_parameters)
   1541 self.trainer = self._setup_trainer(
   1542     trainer=trainer, model=self.model, verbose=verbose, epochs=self.n_epochs
   1543 )
   1545 # prediction output comes as nested list: list of predicted `TimeSeries` for each batch.
-> 1546 predictions = self.trainer.predict(self.model, pred_loader)
   1547 # flatten and return
   1548 return [ts for batch in predictions for ts in batch]

File ~/miniconda3/envs/darts310/lib/python3.10/site-packages/pytorch_lightning/trainer/trainer.py:863, in Trainer.predict(self, model, dataloaders, datamodule, return_predictions, ckpt_path)
    861 self.state.status = TrainerStatus.RUNNING
    862 self.predicting = True
--> 863 return call._call_and_handle_interrupt(
    864     self, self._predict_impl, model, dataloaders, datamodule, return_predictions, ckpt_path
    865 )

File ~/miniconda3/envs/darts310/lib/python3.10/site-packages/pytorch_lightning/trainer/call.py:44, in _call_and_handle_interrupt(trainer, trainer_fn, *args, **kwargs)
     42     if trainer.strategy.launcher is not None:
     43         return trainer.strategy.launcher.launch(trainer_fn, *args, trainer=trainer, **kwargs)
---> 44     return trainer_fn(*args, **kwargs)
     46 except _TunerExitException:
     47     _call_teardown_hook(trainer)

File ~/miniconda3/envs/darts310/lib/python3.10/site-packages/pytorch_lightning/trainer/trainer.py:902, in Trainer._predict_impl(self, model, dataloaders, datamodule, return_predictions, ckpt_path)
    898 assert self.state.fn is not None
    899 ckpt_path = self._checkpoint_connector._select_ckpt_path(
    900     self.state.fn, ckpt_path, model_provided=model_provided, model_connected=self.lightning_module is not None
    901 )
--> 902 results = self._run(model, ckpt_path=ckpt_path)
    904 assert self.state.stopped
    905 self.predicting = False

File ~/miniconda3/envs/darts310/lib/python3.10/site-packages/pytorch_lightning/trainer/trainer.py:986, in Trainer._run(self, model, ckpt_path)
    981 self._signal_connector.register_signal_handlers()
    983 # ----------------------------
    984 # RUN THE TRAINER
    985 # ----------------------------
--> 986 results = self._run_stage()
    988 # ----------------------------
    989 # POST-Training CLEAN UP
    990 # ----------------------------
    991 log.debug(f"{self.__class__.__name__}: trainer tearing down")

File ~/miniconda3/envs/darts310/lib/python3.10/site-packages/pytorch_lightning/trainer/trainer.py:1027, in Trainer._run_stage(self)
   1025     return self._evaluation_loop.run()
   1026 if self.predicting:
-> 1027     return self.predict_loop.run()
   1028 if self.training:
   1029     with isolate_rng():

File ~/miniconda3/envs/darts310/lib/python3.10/site-packages/pytorch_lightning/loops/utilities.py:182, in _no_grad_context.<locals>._decorator(self, *args, **kwargs)
    180     context_manager = torch.no_grad
    181 with context_manager():
--> 182     return loop_run(self, *args, **kwargs)

File ~/miniconda3/envs/darts310/lib/python3.10/site-packages/pytorch_lightning/loops/prediction_loop.py:124, in _PredictionLoop.run(self)
    122     self.batch_progress.is_last_batch = data_fetcher.done
    123     # run step hooks
--> 124     self._predict_step(batch, batch_idx, dataloader_idx, dataloader_iter)
    125 except StopIteration:
    126     # this needs to wrap the `*_step` call too (not just `next`) for `dataloader_iter` support
    127     break

File ~/miniconda3/envs/darts310/lib/python3.10/site-packages/pytorch_lightning/loops/prediction_loop.py:253, in _PredictionLoop._predict_step(self, batch, batch_idx, dataloader_idx, dataloader_iter)
    247 # configure step_kwargs
    248 step_args = (
    249     self._build_step_args_from_hook_kwargs(hook_kwargs, "predict_step")
    250     if not using_dataloader_iter
    251     else (dataloader_iter,)
    252 )
--> 253 predictions = call._call_strategy_hook(trainer, "predict_step", *step_args)
    254 if predictions is None:
    255     self._warning_cache.warn("predict returned None if it was on purpose, ignore this warning...")

File ~/miniconda3/envs/darts310/lib/python3.10/site-packages/pytorch_lightning/trainer/call.py:309, in _call_strategy_hook(trainer, hook_name, *args, **kwargs)
    306     return None
    308 with trainer.profiler.profile(f"[Strategy]{trainer.strategy.__class__.__name__}.{hook_name}"):
--> 309     output = fn(*args, **kwargs)
    311 # restore current_fx when nested context
    312 pl_module._current_fx_name = prev_fx_name

File ~/miniconda3/envs/darts310/lib/python3.10/site-packages/pytorch_lightning/strategies/strategy.py:438, in Strategy.predict_step(self, *args, **kwargs)
    436 if self.model != self.lightning_module:
    437     return self._forward_redirection(self.model, self.lightning_module, "predict_step", *args, **kwargs)
--> 438 return self.lightning_module.predict_step(*args, **kwargs)

File ~/miniconda3/envs/darts310/lib/python3.10/site-packages/darts/models/forecasting/pl_forecasting_module.py:292, in PLForecastingModule.predict_step(self, batch, batch_idx, dataloader_idx)
    286 input_data_tuple_samples = self._sample_tiling(
    287     input_data_tuple, batch_sample_size
    288 )
    290 # get predictions for 1 whole batch (can include predictions of multiple series
    291 # and for multiple samples if a probabilistic forecast is produced)
--> 292 batch_prediction = self._get_batch_prediction(
    293     self.pred_n, input_data_tuple_samples, self.pred_roll_size
    294 )
    296 # reshape from 3d tensor (num_series x batch_sample_size, ...)
    297 # into 4d tensor (batch_sample_size, num_series, ...), where dim 0 represents the samples
    298 out_shape = batch_prediction.shape

File ~/miniconda3/envs/darts310/lib/python3.10/site-packages/darts/models/forecasting/pl_forecasting_module.py:678, in PLPastCovariatesModule._get_batch_prediction(self, n, input_batch, roll_size)
    673     input_past[:, :, n_targets : n_targets + n_past_covs] = (
    674         future_past_covariates[:, left_past:right_past, :]
    675     )
    677 # take only last part of the output sequence where needed
--> 678 out = self._produce_predict_output(x=(input_past, static_covariates))[
    679     :, self.first_prediction_index :, :
    680 ]
    682 batch_prediction.append(out)
    683 prediction_length += self.output_chunk_length

File ~/miniconda3/envs/darts310/lib/python3.10/site-packages/darts/models/forecasting/pl_forecasting_module.py:480, in PLForecastingModule._produce_predict_output(self, x)
    478         return self.likelihood.predict_likelihood_parameters(output)
    479     else:
--> 480         return self.likelihood.sample(output)
    481 else:
    482     return self(x).squeeze(dim=-1)

File ~/miniconda3/envs/darts310/lib/python3.10/site-packages/darts/utils/likelihood_models.py:1051, in LaplaceLikelihood.sample(self, model_output)
   1049 def sample(self, model_output) -> torch.Tensor:
   1050     mu, b = self._params_from_output(model_output)
-> 1051     distr = _Laplace(mu, b)
   1052     return distr.sample()

File ~/miniconda3/envs/darts310/lib/python3.10/site-packages/torch/distributions/laplace.py:52, in Laplace.__init__(self, loc, scale, validate_args)
     50 else:
     51     batch_shape = self.loc.size()
---> 52 super().__init__(batch_shape, validate_args=validate_args)

File ~/miniconda3/envs/darts310/lib/python3.10/site-packages/torch/distributions/distribution.py:68, in Distribution.__init__(self, batch_shape, event_shape, validate_args)
     66         valid = constraint.check(value)
     67         if not valid.all():
---> 68             raise ValueError(
     69                 f"Expected parameter {param} "
     70                 f"({type(value).__name__} of shape {tuple(value.shape)}) "
     71                 f"of distribution {repr(self)} "
     72                 f"to satisfy the constraint {repr(constraint)}, "
     73                 f"but found invalid values:\n{value}"
     74             )
     75 super().__init__()

ValueError: Expected parameter scale (Tensor of shape (32, 24, 1)) of distribution Laplace(loc: torch.Size([32, 24, 1]), scale: torch.Size([32, 24, 1])) to satisfy the constraint GreaterThan(lower_bound=0.0), but found invalid values:
tensor([[[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [3.5466e+02],
         [0.0000e+00],
         [4.6437e+02],
         [7.5609e+02],
         [3.2690e+02],
         [0.0000e+00],
         [3.2321e+02],
         [1.1282e+03],
         [2.8581e+03],
         [3.9684e+03],
         [8.2819e+03],
         [2.1521e+04]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [4.2196e+02],
         [2.9085e+02],
         [1.4671e+02],
         [4.2493e+02],
         [1.8432e+03],
         [5.2108e+02],
         [1.8184e+02],
         [2.8255e+02],
         [4.0458e+03],
         [1.3649e+03],
         [5.2962e+03],
         [3.4843e+03]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [2.6621e+02],
         [9.4351e+01],
         [5.9659e+02],
         [0.0000e+00],
         [0.0000e+00],
         [3.0769e+02],
         [0.0000e+00],
         [2.4108e+02],
         [0.0000e+00],
         [3.4435e+02],
         [0.0000e+00],
         [9.6189e+03]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [6.2573e+02],
         [1.9478e+01],
         [0.0000e+00],
         [2.6027e+02],
         [7.2585e+03],
         [8.6589e+03],
         [1.2641e+04],
         [1.2864e+04],
         [2.2555e+04],
         [2.1138e+04],
         [2.4303e+04],
         [1.9105e+04]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [4.0673e+02],
         [0.0000e+00],
         [4.7850e+02],
         [1.6997e+02],
         [2.0591e+03],
         [6.3764e+01],
         [0.0000e+00],
         [0.0000e+00],
         [1.4656e+04],
         [3.9371e+04],
         [4.8098e+04],
         [8.9642e+04]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [5.1685e+02],
         [6.7294e+01],
         [1.8114e+02],
         [7.7117e+01],
         [4.1540e+03],
         [1.0585e+03],
         [4.6140e+03],
         [2.0682e+02],
         [9.2396e+03],
         [1.0781e+03],
         [6.3496e+03],
         [0.0000e+00]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [4.2792e+02],
         [3.4131e+02],
         [2.9428e+02],
         [2.8990e+02],
         [9.5690e+02],
         [2.9681e+03],
         [3.5354e+03],
         [2.1584e+03],
         [1.3484e+03],
         [7.0866e+03],
         [4.9625e+03],
         [4.5635e+03]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [4.9794e+02],
         [6.3204e+02],
         [0.0000e+00],
         [0.0000e+00],
         [3.6342e+03],
         [1.5193e+04],
         [1.7998e+04],
         [3.3042e+04],
         [3.7157e+04],
         [5.3951e+04],
         [5.3066e+04],
         [6.3337e+04]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [3.8738e+02],
         [5.9231e+02],
         [1.2937e+02],
         [0.0000e+00],
         [8.7417e+02],
         [4.4924e+03],
         [7.7686e+03],
         [5.2749e+03],
         [1.1464e+04],
         [7.3656e+03],
         [1.5769e+04],
         [5.8549e+03]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [5.2412e+02],
         [5.9308e+01],
         [0.0000e+00],
         [7.0189e+02],
         [4.2594e+03],
         [1.7311e+03],
         [3.6755e+03],
         [5.0991e+03],
         [9.1981e+03],
         [4.6576e+03],
         [7.5442e+03],
         [1.0303e+04]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [0.0000e+00],
         [2.2418e+02],
         [7.0614e+02],
         [3.5272e+02],
         [0.0000e+00],
         [0.0000e+00],
         [1.7541e+02],
         [7.4891e+01],
         [5.0964e+03],
         [0.0000e+00],
         [1.0632e+04],
         [1.6849e+02]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [2.4644e+02],
         [6.0210e+02],
         [2.9503e+02],
         [6.2108e+02],
         [3.4425e+02],
         [2.4112e+03],
         [1.8081e+02],
         [5.3398e+03],
         [3.0440e+03],
         [5.6225e+03],
         [7.2210e+03],
         [7.2862e+03]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [5.1805e+02],
         [4.2366e+01],
         [5.6576e+02],
         [5.2030e+02],
         [4.2446e+03],
         [0.0000e+00],
         [8.7383e+03],
         [2.5814e+03],
         [2.0124e+04],
         [1.0774e+04],
         [4.8236e+04],
         [3.5690e+04]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [2.8773e+02],
         [2.1789e+02],
         [4.3082e+02],
         [3.2359e+02],
         [4.0239e+02],
         [2.4006e+02],
         [8.2602e+01],
         [0.0000e+00],
         [0.0000e+00],
         [0.0000e+00],
         [1.8908e+03],
         [8.6257e+02]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [3.5584e+02],
         [1.1363e+03],
         [0.0000e+00],
         [0.0000e+00],
         [0.0000e+00],
         [1.8747e+04],
         [4.2485e+03],
         [2.7998e+04],
         [3.0196e+03],
         [4.8885e+04],
         [5.8029e+03],
         [4.8085e+04]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [4.0141e+02],
         [2.3429e+02],
         [8.7342e+02],
         [0.0000e+00],
         [8.6362e+02],
         [7.0546e+02],
         [7.8284e+03],
         [2.4537e+03],
         [1.5170e+04],
         [1.7069e+04],
         [1.8793e+04],
         [2.3392e+04]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [4.7611e+02],
         [3.5129e+02],
         [1.8447e+02],
         [0.0000e+00],
         [1.5156e+03],
         [6.0635e+03],
         [1.2944e+04],
         [2.0738e+04],
         [1.6236e+04],
         [2.7564e+04],
         [2.1402e+04],
         [3.6097e+04]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [0.0000e+00],
         [4.1882e+00],
         [6.6243e+00],
         [0.0000e+00],
         [4.2323e-04],
         [0.0000e+00],
         [0.0000e+00],
         [3.9317e+01],
         [0.0000e+00],
         [0.0000e+00],
         [0.0000e+00],
         [0.0000e+00]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [2.8740e+02],
         [5.7409e+02],
         [3.1118e+02],
         [4.6254e+02],
         [4.6531e+02],
         [2.5792e+03],
         [5.5729e+02],
         [4.7599e+03],
         [6.3608e+03],
         [6.3185e+03],
         [9.6536e+03],
         [6.8044e+03]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [3.8756e+02],
         [4.2308e+02],
         [2.3249e+02],
         [2.0505e+02],
         [9.1562e+02],
         [1.4755e+03],
         [5.3977e+02],
         [4.2784e+02],
         [1.1924e+03],
         [1.4245e+03],
         [3.7625e+03],
         [6.6584e+02]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [2.8670e+02],
         [1.3137e+02],
         [5.9928e+02],
         [3.8858e+02],
         [5.2856e+02],
         [0.0000e+00],
         [2.4692e+02],
         [1.6345e+02],
         [6.7857e+03],
         [0.0000e+00],
         [4.9448e+03],
         [1.6755e+02]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [5.1253e+02],
         [2.0230e+02],
         [8.7402e+00],
         [0.0000e+00],
         [4.2190e+03],
         [4.5460e+03],
         [6.9655e+03],
         [4.3069e+03],
         [1.0163e+04],
         [5.1115e+03],
         [1.3262e+04],
         [2.0912e+04]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [5.1422e+02],
         [2.6167e+02],
         [0.0000e+00],
         [5.9992e+02],
         [3.1619e+03],
         [7.3641e+03],
         [8.3763e+03],
         [1.9238e+04],
         [1.0471e+04],
         [2.9842e+04],
         [1.2521e+04],
         [3.7745e+04]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [3.4059e+02],
         [5.0451e+02],
         [0.0000e+00],
         [1.0669e+02],
         [4.7835e+02],
         [1.7370e+03],
         [0.0000e+00],
         [2.0056e+02],
         [1.6877e+02],
         [1.5789e+03],
         [8.3318e+01],
         [0.0000e+00]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [3.7563e+02],
         [3.7193e+02],
         [4.0011e+02],
         [5.6188e+02],
         [6.5350e+02],
         [0.0000e+00],
         [5.6529e+02],
         [1.0223e+03],
         [1.6954e+03],
         [0.0000e+00],
         [6.2738e+03],
         [6.0970e+03]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [5.7755e+02],
         [9.4972e+01],
         [1.3695e+00],
         [1.9749e+02],
         [4.7334e+03],
         [8.4419e+03],
         [1.4042e+04],
         [1.6364e+04],
         [1.7950e+04],
         [3.9612e+04],
         [2.3361e+04],
         [4.9177e+04]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [5.3864e+02],
         [1.3122e-04],
         [1.5656e+02],
         [4.4812e+02],
         [4.3967e+03],
         [2.1135e+02],
         [4.8541e+03],
         [0.0000e+00],
         [7.9736e+03],
         [5.3526e+02],
         [5.9979e+03],
         [0.0000e+00]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [2.8944e+02],
         [7.1204e+02],
         [1.1715e+02],
         [0.0000e+00],
         [0.0000e+00],
         [7.1381e+03],
         [6.9366e+03],
         [1.4242e+04],
         [6.5499e+03],
         [2.5540e+04],
         [1.8810e+04],
         [3.7684e+04]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [2.3328e+02],
         [2.7684e+02],
         [5.5600e+02],
         [5.2213e+02],
         [6.6743e+01],
         [1.7771e+01],
         [1.4544e+02],
         [1.0106e+03],
         [3.4816e+03],
         [2.7806e+03],
         [4.9556e+03],
         [1.0809e+04]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [2.9472e+02],
         [0.0000e+00],
         [4.8687e+02],
         [3.7346e+02],
         [4.9578e-07],
         [2.0248e+02],
         [5.1683e+02],
         [4.9718e+02],
         [0.0000e+00],
         [0.0000e+00],
         [0.0000e+00],
         [9.4098e+03]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [2.1485e+02],
         [3.6214e+02],
         [5.5538e+02],
         [4.7417e+02],
         [1.2258e+02],
         [0.0000e+00],
         [1.2590e+03],
         [1.3526e+02],
         [2.0405e+03],
         [0.0000e+00],
         [1.7554e+04],
         [1.7065e+04]],

        [[1.0175e+00],
         [3.7658e-01],
         [2.3018e-01],
         [2.9205e-01],
         [2.3816e+01],
         [3.4126e+01],
         [7.2803e+01],
         [8.8747e+01],
         [1.5841e+02],
         [1.8664e+02],
         [2.5827e+02],
         [2.8993e+02],
         [5.2569e+02],
         [1.1581e+02],
         [5.3897e+02],
         [0.0000e+00],
         [3.6477e+03],
         [3.4733e+03],
         [1.8417e+04],
         [1.3479e+04],
         [2.8909e+04],
         [1.9235e+04],
         [5.3059e+04],
         [3.4125e+04]]], device='mps:0')

Furthermore, we could also for instance specify that we have some prior belief that the scale of the distribution is about \(0.1\) (in the transformed domain), while still capturing some time dependency of the distribution, by specifying prior_b=.1.

Behind the scenes this will regularize the training loss with a Kullback-Leibler divergence term.

[ ]:
model = TCNModel(
    input_chunk_length=24,
    output_chunk_length=12,
    random_state=42,
    likelihood=LaplaceLikelihood(prior_b=0.1),
)

model.fit(train_air_scaled, epochs=400, verbose=True);
[ ]:
pred = model.predict(n=36, num_samples=500)

# scale back:
pred = scaler.inverse_transform(pred)

series_air.plot()
pred.plot()

By default TimeSeries.plot() shows the median as well as the 5th and 95th percentiles (of the marginal distributions, if the TimeSeries is multivariate). It is possible to control this:

[ ]:
pred.plot(low_quantile=0.01, high_quantile=0.99, label="1-99th percentiles")
pred.plot(low_quantile=0.2, high_quantile=0.8, label="20-80th percentiles")

Types of distributions

The likelihood has to be compatible with the domain of your time series’ values. For instance PoissonLikelihood can be used on discrete positive values, ExponentialLikelihood can be used on real positive values, and BetaLikelihood on real values in \((0,1)\).

It is also possible to use QuantileRegression to apply a quantile loss and fit some desired quantiles directly.

Evaluating Probabilistic Forecasts

How can we evaluate the quality of probabilistic forecasts? By default, most metrics functions (such as mape()) will keep working but look only at the median forecast. It is also possible to use the Mean Quantile Loss metric mql(), which quantifies the error for each predicted quantiles. For quantile=0.5 (the median), it is identical to the Mean Absolute Error (MAE):

[52]:
from darts.metrics import mql, mae

print("MAPE of median forecast: %.2f" % mape(series_air, pred))
print("MAE of median forecast: %.2f" % mae(series_air, pred))
for q in [0.05, 0.1, 0.5, 0.9, 0.95]:
    q_loss = mql(series_air, pred, q=q)
    print("quantile loss at quantile %.2f: %.2f" % (q, q_loss))
MAPE of median forecast: 11.78
MAE of median forecast: 50.12
quantile loss at quantile 0.05: 5.01
quantile loss at quantile 0.10: 11.73
quantile loss at quantile 0.50: 50.12
quantile loss at quantile 0.90: 20.56
quantile loss at quantile 0.95: 12.13

Using Quantile Loss

Could we do better by fitting these quantiles directly? We can just use a QuantileRegression likelihood:

[ ]:
from darts.utils.likelihood_models import QuantileRegression

model = TCNModel(
    input_chunk_length=24,
    output_chunk_length=12,
    random_state=42,
    likelihood=QuantileRegression([0.05, 0.1, 0.5, 0.9, 0.95]),
)

model.fit(train_air_scaled, epochs=400, verbose=True);
[54]:
pred = model.predict(n=36, num_samples=500)

# scale back:
pred = scaler.inverse_transform(pred)

series_air.plot()
pred.plot()

print("MAPE of median forecast: %.2f" % mape(series_air, pred))
for q in [0.05, 0.1, 0.5, 0.9, 0.95]:
    q_loss = mql(series_air, pred, q=q)
    print("quantile loss at quantile %.2f: %.2f" % (q, q_loss))
GPU available: False, used: False
TPU available: False, using: 0 TPU cores
IPU available: False, using: 0 IPUs
HPU available: False, using: 0 HPUs
MAPE of median forecast: 4.90
quantile loss at quantile 0.05: 8.27
quantile loss at quantile 0.10: 12.76
quantile loss at quantile 0.50: 19.86
quantile loss at quantile 0.90: 12.17
quantile loss at quantile 0.95: 7.57
../_images/quickstart_00-quickstart_115_3.png

Ensembling models

Ensembling is about combining the forecasts produced by several models, in order to obtain a final - and hopefully better forecast.

For instance, in our example of a less naive model above, we manually combined a naive seasonal model with a naive drift model. Here, we will show how models forecasts can be automatically combined, naively using a NaiveEnsembleModel, or learned using RegressionEnsembleModel.

It is of course also possible to use past and/or future_covariates with ensemble model but they will be passed only to the models supporting them when calling fit() and predict().

Naive Ensembling

Naive ensembling just takes the average of the forecasts of several models. Darts provides a NaiveEnsembleModel, which allows to do this while still manipulating only one forecasting model (which, for instance, allows for easier backtesting):

[ ]:
from darts.models import NaiveEnsembleModel

models = [NaiveDrift(), NaiveSeasonal(12)]

ensemble_model = NaiveEnsembleModel(forecasting_models=models)

backtest = ensemble_model.historical_forecasts(
    series_air, start=0.6, forecast_horizon=3, verbose=True
)

print("MAPE = %.2f" % (mape(backtest, series_air)))
series_air.plot()
backtest.plot()

Learned Ensembling

As expected in this case, the naive ensemble doesn’t give great results (although in some cases it could!)

We can sometimes do better if we see the ensembling as a supervised regression problem: given a set of forecasts (features), find a model that combines them in order to minimise errors on the target. This is what the RegressionEnsembleModel does. It accepts three parameters:

  • forecasting_models is a list of forecasting models whose predictions we want to ensemble.

  • regression_train_n_points is the number of time steps to use for fitting the “ensemble regression” model (i.e., the inner model that combines the forecasts).

  • regression_model is, optionally, a sklearn-compatible regression model or a Darts RegressionModel to be used for the ensemble regression. If not specified, a linear regression is used. Using a sklearn model is easy out-of-the-box, but using a RegressionModel allows to potentially take arbitrary lags of the individual forecasts as inputs of the regression model.

Once these elements are in place, a RegressionEnsembleModel can be used like a regular forecasting model:

[ ]:
from darts.models import RegressionEnsembleModel

models = [NaiveDrift(), NaiveSeasonal(12)]

ensemble_model = RegressionEnsembleModel(
    forecasting_models=models, regression_train_n_points=12
)

backtest = ensemble_model.historical_forecasts(
    series_air, start=0.6, forecast_horizon=3, verbose=True
)

print("MAPE = %.2f" % (mape(backtest, series_air)))
series_air.plot()
backtest.plot()

We can also inspect the coefficients used to weigh the two inner models in the linear combination:

[ ]:
ensemble_model.fit(series_air)
ensemble_model.regression_model.model.coef_

By using a probabilistic regression model, the RegressionEnsembleModel can also generate probabilistic forecasts:

[ ]:
from darts.models import LinearRegressionModel

quantiles = [0.25, 0.5, 0.75]

models = [NaiveDrift(), NaiveSeasonal(12)]

regression_model = LinearRegressionModel(
    quantiles=quantiles,
    lags_future_covariates=[0],
    likelihood="quantile",
    fit_intercept=False,
)

ensemble_model = RegressionEnsembleModel(
    forecasting_models=models,
    regression_train_n_points=12,
    regression_model=regression_model,
)

backtest = ensemble_model.historical_forecasts(
    series_air, start=0.6, forecast_horizon=3, num_samples=500, verbose=True
)

print("MAPE = %.2f" % (mape(backtest, series_air)))
series_air.plot()
backtest.plot()

RegressionEnsembleModel uses the stacking technique to train and combine the forecasting_models: each one of them is trained independently and the regression_model is then trained using their predictions as future_covariates.

Filtering models

In addition to forecasting models, which are able to predict future values of series, Darts also contains a couple of helpful filtering models, which can model “in sample” series’ values distributions.

Fitting a Kalman Filter

KalmanFilter implements a Kalman Filter. The implementation relies on nfoursid, so it is for instance possible to provide a nfoursid.kalman.Kalman object containing a transition matrix, process noise covariance, observation noise covariance etc.

It is also possible to do system identification by calling fit() to “train” the Kalman Filter using the N4SID system identification algorithm:

[ ]:
from darts.models import KalmanFilter

kf = KalmanFilter(dim_x=3)
kf.fit(train_air_scaled)
filtered_series = kf.filter(train_air_scaled, num_samples=100)

train_air_scaled.plot()
filtered_series.plot()

Inferring missing values with Gaussian Processes

Darts also contains a GaussianProcessFilter which can be used for probabilistic modeling of series:

[ ]:
from darts.models import GaussianProcessFilter
from sklearn.gaussian_process.kernels import RBF

# create a series with holes:
values = train_air_scaled.values()
values[20:22] = np.nan
values[28:32] = np.nan
values[55:59] = np.nan
values[72:80] = np.nan
series_holes = TimeSeries.from_times_and_values(train_air_scaled.time_index, values)
series_holes.plot()

kernel = RBF()

gpf = GaussianProcessFilter(kernel=kernel, alpha=0.1, normalize_y=True)
filtered_series = gpf.filter(series_holes, num_samples=100)

filtered_series.plot()

A Word of Caution

So is N-BEATS, exponential smoothing, or a Bayesian ridge regression trained on milk production the best approach for predicting the future number of airline passengers? Well, at this point it’s actually hard to say exactly which one is best. Our time series is small, and our validation set is even smaller. In such cases, it’s very easy to overfit the whole forecasting exercise to such a small validation set. That’s especially true if the number of available models and their degrees of freedom is high (such as for deep learning models), or if we played with many models on a single test set (as done in this notebook).

As data scientists, it is our responsibility to understand the extent to which our models can be trusted. So always take results with a grain of salt, especially on small datasets, and apply the scientific method before making any kind of forecast :) Happy modeling!