# How to Interpret the Quantile Forecast Output from TimesFM

> Learn to interpret Quantile Forecast output from Google Research's TimesFM. Understand mean predictions and quantile levels for your time series analysis.

- Repository: [Google Research/timesfm](https://github.com/google-research/timesfm)
- Tags: how-to-guide
- Published: 2026-04-02

---

**TimesFM returns probabilistic forecasts as a tensor of shape `(num_series, horizon, 1 + num_quantiles)` where the first slice contains the mean prediction and subsequent slices map to specific quantile levels using a `+1` index offset.**

TimesFM is a pretrained time series foundation model from Google Research that generates uncertainty-aware predictions through quantile regression. When calling the forecast API, the model outputs both point estimates and a full quantile tensor representing the conditional distribution at each future timestep. Correctly interpreting the quantile forecast output from TimesFM requires understanding the specific indexing convention used across the codebase.

## Understanding the `model.forecast()` Return Values

The `forecast()` method in [`v1/src/timesfm/timesfm_base.py`](https://github.com/google-research/timesfm/blob/main/v1/src/timesfm/timesfm_base.py) returns a tuple containing two distinct arrays:

- **`point`**: A tensor of shape `(num_series, horizon)` containing the mean forecast for each input series and prediction horizon.
- **`quantiles`**: A tensor of shape `(num_series, horizon, 1 + num_quantiles)` containing the mean followed by all configured quantile predictions.

The third dimension always includes one extra slot for the mean, making the actual tensor depth `1 + len(model.hparams.quantiles)`. This design allows the model to output both central tendencies and uncertainty bounds from a single forward pass.

## Decoding the Quantile Tensor Indexing

According to the source code in [`v1/src/timesfm/timesfm_base.py`](https://github.com/google-research/timesfm/blob/main/v1/src/timesfm/timesfm_base.py), the quantile tensor uses a fixed offset scheme:

- **Index 0** (`[..., 0]`): The mean forecast (identical to the `point` array).
- **Index 1 + i** (`[..., 1 + i]`): The quantile corresponding to `model.hparams.quantiles[i]`.

The model ships with `DEFAULT_QUANTILES = (0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9)` defined in [`timesfm_base.py`](https://github.com/google-research/timesfm/blob/main/timesfm_base.py). This creates the following mapping for the default configuration:

| Quantile | Position `i` | Tensor Index |
|----------|-------------|--------------|
| 0.1 (10th percentile) | 0 | `[..., 1]` |
| 0.2 (20th percentile) | 1 | `[..., 2]` |
| 0.5 (median) | 4 | `[[..., 5]` |
| 0.9 (90th percentile) | 8 | `[..., 9]` |

The decoder implementation in [`v1/src/timesfm/patched_decoder.py`](https://github.com/google-research/timesfm/blob/main/v1/src/timesfm/patched_decoder.py) confirms this structure, calculating `output_dims = horizon_len * (1 + len(config.quantiles))` to accommodate the mean slot before the quantile-specific outputs.

## Extracting Specific Percentiles in Python

To access individual quantiles from the forecast output, apply the `+1` offset to the quantile's position in the configuration list:

```python
import timesfm
import numpy as np

# Initialize model with default quantiles

model = timesfm.TimesFM.from_pretrained("timesfm_small")
inputs = [np.random.randn(500).astype(np.float32) for _ in range(3)]

# Generate forecasts

point, quantile_tensor = model.forecast(horizon=30, inputs=inputs)

# Access default quantiles (0.1, 0.2, ..., 0.9)

quantiles_list = model.hparams.quantiles  # (0.1, 0.2, ..., 0.9)

median_idx = quantiles_list.index(0.5)    # Returns 4

# Extract specific percentiles using +1 offset

mean_forecast = quantile_tensor[:, :, 0]           # Same as 'point'

p10_forecast = quantile_tensor[:, :, 1]            # 10th percentile (index 0 + 1)

median_forecast = quantile_tensor[:, :, 1 + median_idx]  # 50th percentile (index 5)

p90_forecast = quantile_tensor[:, :, 9]            # 90th percentile (index 8 + 1)

print(f"Median shape: {median_forecast.shape}")  # (3, 30)

```

For custom quantile configurations, calculate the index dynamically:

```python
def get_quantile_slice(quantile_tensor, target_quantile, model):
    """Extract specific quantile using the +1 offset rule."""
    idx = model.hparams.quantiles.index(target_quantile)
    return quantile_tensor[:, :, 1 + idx]

p25 = get_quantile_slice(quantile_tensor, 0.25, model)

```

## Using the High-Level CSV Helper

The [`forecast_csv.py`](https://github.com/google-research/timesfm/blob/main/forecast_csv.py) script in `timesfm-forecasting/scripts/` provides a reference implementation for mapping tensor indices to human-readable keys:

```python
results[col] = {
    "forecast": point[i].tolist(),
    "lower_90": quantiles[i, :, 1].tolist(),   # 10th percentile (index 1)

    "lower_80": quantiles[i, :, 2].tolist(),   # 20th percentile (index 2)

    "median":   quantiles[i, :, 5].tolist(),   # 50th percentile (index 5)

    "upper_80": quantiles[i, :, 8].tolist(),   # 80th percentile (index 8)

    "upper_90": quantiles[i, :, 9].tolist(),   # 90th percentile (index 9)

}

```

This pattern demonstrates the consistent application of the offset rule: `lower_90` maps to the 0.1 quantile at tensor index 1, while `upper_90` maps to the 0.9 quantile at tensor index 9.

## Enabling Quantile Outputs in Configuration

Quantile forecasts require the continuous quantile head to be active. In [`src/timesfm/configs.py`](https://github.com/google-research/timesfm/blob/main/src/timesfm/configs.py), the `ForecastConfig` class includes `use_continuous_quantile_head=True` to enable this output mode. When this configuration is set, the model produces the `(1 + num_quantiles)` dimensional output described above; otherwise, it may return only point predictions.

## Summary

- **TimesFM returns a tuple** `(point, quantiles)` where `quantiles` has shape `(num_series, horizon, 1 + num_quantiles)`.
- **The mean occupies index 0** of the last dimension, with configured quantiles starting at index 1.
- **Apply a +1 offset** when accessing quantiles: the i-th quantile in `hparams.quantiles` is found at tensor index `1 + i`.
- **Default quantiles** range from 0.1 to 0.9, placing the median (0.5) at index 5.
- **Reference implementations** in [`forecast_csv.py`](https://github.com/google-research/timesfm/blob/main/forecast_csv.py) demonstrate practical extraction patterns for common confidence intervals.

## Frequently Asked Questions

### Why does the quantile tensor have an extra dimension for the mean?

The tensor includes the mean at index 0 to provide a consistent output structure regardless of whether quantile regression is enabled. As implemented in [`v1/src/timesfm/patched_decoder.py`](https://github.com/google-research/timesfm/blob/main/v1/src/timesfm/patched_decoder.py), the decoder computes `output_dims = horizon_len * (1 + len(config.quantiles))`, ensuring the mean is always available alongside uncertainty estimates. This design allows the `point` return value to simply reference `quantiles[..., 0]` without requiring separate computation paths.

### How do I calculate the 95% prediction interval from TimesFM output?

With the default quantile configuration (0.1 to 0.9), you cannot extract exact 95% bounds directly. You would access `quantiles[:, :, 1]` for the 10th percentile (lower bound) and `quantiles[:, :, 9]` for the 90th percentile (upper bound) to create an 80% interval. For a true 95% interval, you must configure custom quantiles including 0.025 and 0.975 in `hparams.quantiles` before model initialization, then access them at indices `1 + i` where `i` corresponds to their positions in your custom list.

### What is the difference between the `point` return value and `quantiles[:, :, 0]`?

There is no mathematical difference; they contain identical values. The `point` array is provided as a convenience method to access mean forecasts without indexing into the quantile tensor. According to [`timesfm_base.py`](https://github.com/google-research/timesfm/blob/main/timesfm_base.py), the implementation returns the mean slice separately while also including it as the first channel of the quantile tensor for architectural consistency in the decoder output.

### Can I change the quantile levels after loading a pretrained model?

No, the quantile levels are fixed during model initialization based on `hparams.quantiles`. The decoder in [`patched_decoder.py`](https://github.com/google-research/timesfm/blob/main/patched_decoder.py) constructs the output projection layer with dimensions determined by `len(config.quantiles)`, making the architecture dependent on the specific quantile configuration chosen at load time. To use different quantile levels, you must reload the model with a modified `quantiles` hyperparameter tuple in the configuration object passed to `from_pretrained()`.