Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 14 additions & 16 deletions docs/source/forecasting.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Time Series Forecasting

Time series forecasting models (TSFMs) predict future values of a series from its observed history.
Time series foundation models (TSFMs) predict future values of a series from its observed history.
While many forecasting foundation models originally focused on predicting a single variable from its own history (**univariate forecasting**), newer models such as [Chronos-2](https://arxiv.org/abs/2510.15821) and [TimesFM-3](https://research.google/blog/timesfm-3-a-zero-shot-foundation-model-for-multivariate-forecasting) extend this paradigm to multiple targets (**multivariate forecasting**) and **covariates**: additional variables that inform the forecast (*e.g.*, historical temperature or planned promotions).
These capabilities allow models to draw on relationships between targets, past observations of external factors, and known future information.

Expand Down Expand Up @@ -39,7 +39,7 @@ We begin with a single target, the daily sales of ice cream, observed for five d
</tr>
</table>

A time series is a {py:class}`~sdm.tensor.TableTensor` in which each row is one time step, sorted in time, and each column is one variable:
We represent a time series as a {py:class}`~sdm.tensor.TableTensor` with one row per time step, sorted in time, and one column per variable:

```python
import sdm
Expand All @@ -51,7 +51,7 @@ history = sdm.TableTensor.from_columns(
)
```

We can forecast with any time series foundation model in [`sdm.models`](api/models).
We can forecast with any time series forecasting model in [`sdm.models`](api/models).
Here, we use {py:class}`~sdm.models.TimesFM3`:

```python
Expand All @@ -65,7 +65,7 @@ out = model(

Note that `x_context` and `x_query` carry the covariates.
Since we have none yet, both tables hold zero columns.
However, we still need to pass them to {py:meth}`~sdm.models.ICLModel.forward`, in particular because the number of rows in `x_query` define the **forecast horizon**: here, we request predictions for the next three time steps.
However, we still need to pass them to {py:meth}`~sdm.models.ICLModel.forward`, in particular because the number of rows in `x_query` defines the **forecast horizon**: here, we request predictions for the next three time steps.

The forecast is returned as a {py:class}`~sdm.tensor.TableTensor` with one row per future step.
Its output is model-specific, *e.g.*, {py:class}`~sdm.models.TimesFM3` outputs one column per target and quantile, from the 10th to the 90th percentile, named `"<target>__q<level>"`:
Expand All @@ -78,7 +78,7 @@ out["ice_cream__q90"].numerical # upper end of an 80% prediction interval

## Multivariate Forecasting

Models with {py:attr}`sdm.models.ICLModel.supports_multi_target` can forecast multiple targets jointly, allowing them to exploit dependencies between them.
A model that {py:attr}`~sdm.models.ICLModel.supports_multi_target` can forecast multiple targets jointly, allowing them to exploit dependencies between them.

In this case, we can simply add the daily sales of cold drinks as a second target:

Expand Down Expand Up @@ -112,8 +112,8 @@ out = model(
## Covariates

Lastly, we add known future and past-only covariates to our model.
For example, the measured **temperature** is a past covariate: its values are unavailable for the forecast horizon.
Models with {py:attr}`sdm.models.ICLModel.supports_context_only_columns` can exploit such context-only columns.
For example, the measured **temperature** is a past-only covariate: its values are unavailable for the forecast horizon.
A model that {py:attr}`~sdm.models.ICLModel.supports_context_only_columns` can exploit such past-only covariates.
In contrast, **promotion** is a known future covariate that indicates whether a promotion was or is scheduled for a given day.

<table>
Expand All @@ -123,10 +123,8 @@ In contrast, **promotion** is a known future covariate that indicates whether a
<th>ice cream</th><td>40</td><td>55</td><td>80</td><td>75</td><td>50</td><td>?</td><td>?</td><td>?</td>
</tr><tr>
<th>cold drinks</th><td>25</td><td>30</td><td>45</td><td>41</td><td>28</td><td>?</td><td>?</td><td>?</td>
</tr>
</tr><tr>
<th>temperature</th><td>21</td><td>24</td><td>28</td><td>27</td><td>22</td><td></td><td></td><td></td>
</tr>
</tr><tr>
<th>promotion</th><td>0</td><td>0</td><td>1</td><td>1</td><td>0</td><td>1</td><td>0</td><td>0</td>
</tr>
Expand All @@ -142,9 +140,9 @@ history = sdm.TableTensor.from_columns(
},
stypes={
"ice_cream": "numerical",
"cold_drinks": "numerical"
"temperature": "numerical"
"promotion": "numerical"
"cold_drinks": "numerical",
"temperature": "numerical",
"promotion": "numerical",
},
device="cuda",
)
Expand All @@ -171,7 +169,7 @@ In summary, the columns that are passed determine which covariates the model see

As with any other {py:class}`~sdm.models.ICLModel`, the `num_estimators` argument controls how many ensemble members contribute to the forecast.
Each member may receive a differently transformed view of the series, and their predictions are averaged into a single forecast.
For example, the default recipe for {py:class}`~sdm.models.TimesFM3` alternatives between the original series and a sign-flipped version.
For example, the default recipe for {py:class}`~sdm.models.TimesFM3` alternates between the original series and a sign-flipped version.
Using two estimators includes both views:

```python
Expand All @@ -186,7 +184,7 @@ Each task itself can contain multiple targets and covariates.

For example, store sales can be represented in two ways: as separate univariate tasks in a batch (forecasting each store independently) or as a multivariate task (allowing the model to use relationships between stores).

To forecast a batch series in one call, stack them along a leading batch dimension.
To forecast a batch of series in one call, stack them along a leading batch dimension.
All series in a batch share the same horizon and number of context rows, and shorter series should be left-padded with `NaN`.
Set `num_estimators` explicitly so that the leading dimension is interpreted as a batch of independent forecasting tasks rather than as ensemble members:

Expand All @@ -195,10 +193,10 @@ import torch

out = model(
x_context=torch.randn(64, 128, 0, device="cuda"),
x_context=torch.randn(64, 128, 2, device="cuda"),
y_context=torch.randn(64, 128, 2, device="cuda"),
x_query=torch.randn(64, 32, 0, device="cuda"),
num_estimators=1,
)
```

See [`examples/timeseries/quickstart.py`](https://github.com/NVIDIA/structured-data-models/blob/main/examples/timeseries/quickstart.py) for runnable examples on real-world datasets and tasks.
See [`examples/timeseries/forecast.py`](https://github.com/NVIDIA/structured-data-models/blob/main/examples/timeseries/forecast.py) for runnable examples on real-world datasets and tasks.
Loading