Skip to content

Forecasting from a DataFrame

Use ForecastModel.forecast_df to forecast directly from a dataframe. You don't need to create TimeseriesType tensors yourself. The method uses narwhals to work with eager dataframes from pandas, Polars, PyArrow, Modin, and cuDF. It returns the forecast in the same dataframe library you used for the input.

narwhals is installed with TiRex-2, so forecast_df is always available. Install the dataframe library you want to use separately, for example with pip install pandas, pip install polars, or pip install pyarrow.

Downloading data

We'll use the aus_production dataset from Forecasting: Principles and Practice, the Pythonic Way. It has 218 quarterly observations of Australian beer, cement, electricity, and gas production, from 1956 Q1 to 2010 Q2.

Run the following command to download the CSV file:

curl -O https://otexts.com/fpppy/data/aus_production.csv

Load it into a pandas frame and drop the two series that stop early:

import pandas as pd

df = (
    pd.read_csv("aus_production.csv", parse_dates=["ds"])
    .rename(columns={"ds": "timestamp"})
    .drop(columns=["Tobacco", "Bricks"])  # these two series stop early
)
# columns: timestamp, Beer, Cement, Electricity, Gas

Loading the model

from tirex2 import load_model

model = load_model("NX-AI/TiRex-2", device="cpu")  # or device="cuda"/"mps"

Forecasting from a DataFrame

Forecasting a single column

Simply point target at the column to forecast and timestamp_column at the time axis:

forecast = model.forecast_df(
    df,
    prediction_length=8,  # eight quarters
    target="Beer",
    timestamp_column="timestamp",
)

Note

When calling forecast_df, pass every argument after prediction_length by name.

You get a long-format frame — one row per target column and forecast step — with a prediction column (the median) and one column per quantile level:

timestamp target prediction 0.1 … 0.9
0 2010-07-01 Beer 408.48 391.33 … 425.11
1 2010-10-01 Beer 479.12 458.42 … 500.15
2 2011-01-01 Beer 405.48 384.93 … 424.99

The input ends in 2010 Q2, so the forecast starts in 2010 Q3. forecast_df picks up the quarterly schedule from the input timestamps and continues it. It also understands calendar schedules such as month ends. If there is no usable time axis, the forecast uses integer positions instead.

If your pandas dataframe has a DatetimeIndex, you can leave out timestamp_column. The index is used automatically, including for future_df.

Forecasting multiple columns

Leave target unset and every numeric column that is not the id, timestamp or a covariate becomes a target. They are forecast jointly, so the model can use their cross-variate structure:

forecast = model.forecast_df(
    df,
    prediction_length=8,
    timestamp_column="timestamp",
)
forecast["target"].unique().tolist()
# ['Beer', 'Cement', 'Electricity', 'Gas']

Use separate columns in a wide dataframe when the series are related. If each column represents a different SKU, sensor, or customer, reshape the data into long format and give each series its own id, as shown next.

Forecasting many series in one frame

To forecast several series at once, stack them in one dataframe and give each series an id. Pass the name of that column as id_column:

long_df = df.melt(id_vars="timestamp", var_name="product", value_name="production")

forecast = model.forecast_df(
    long_df,
    prediction_length=8,
    target="production",
    id_column="product",
    timestamp_column="timestamp",
)

Each product is forecast as its own series, and the output includes the product column. Products stay in the order they first appear in the input, while rows within each product are sorted by timestamp. You don't need to sort the input first.

If an id has multiple target columns, the model forecasts those columns together. It never combines data from different ids. Here, each id has just one target column, so the products are forecast independently.

Forecasting with covariates

past_covariates names columns observed only over the context — see Covariates for what the model does with them:

forecast = model.forecast_df(
    df,
    prediction_length=8,
    target="Beer",
    timestamp_column="timestamp",
    past_covariates=["Cement", "Electricity", "Gas"],
)

Use future_covariates for columns whose values you already know during the forecast period, such as calendar features, holidays, or promotions. Put those future values in future_df, using the same layout as the input dataframe, and past values in the input dataframe:

df["quarter"] = df["timestamp"].dt.quarter.astype("float32")

future_df = pd.DataFrame({"timestamp": pd.date_range("2010-07-01", periods=8, freq="QS")})
future_df["quarter"] = future_df["timestamp"].dt.quarter.astype("float32")

forecast = model.forecast_df(
    df,
    prediction_length=8,
    target="Beer",
    timestamp_column="timestamp",
    future_covariates=["quarter"],
    future_df=future_df,
)

future_df needs a row for every series you are forecasting. Series it contains that df never mentions are ignored, with a warning.

Scaling up

forecast_df processes up to batch_size series at a time (512 by default). You can pass thousands of series in one call; it handles the batches for you.

Note

For each batch, TiRex-2 packs the series into one tensor and pads shorter histories on the left to match the longest one. This means a larger batch_size can use more GPU memory. If CUDA or MPS runs out of memory, forecast_df halves the batch size and retries, so the value you set is a starting point.

batch_size counts series, not rows. For example, 100 ids count as 100 series whether each id has one target column or four.

If the full forecast would be too large to hold in memory, set yield_per_batch=True. This yields one dataframe per batch instead of combining them all into one:

for batch in model.forecast_df(
    long_df,
    prediction_length=8,
    target="production",
    id_column="product",
    timestamp_column="timestamp",
    batch_size=2,
    yield_per_batch=True,
):
    write_somewhere(batch)

Each yielded frame holds the ids of one batch, in input order, so you can write it straight out without waiting for the rest.

Switching dataframe libraries

For example, pass in a Polars dataframe and you get a Polars dataframe back:

import polars as pl

pl_df = pl.read_csv(
    "aus_production.csv", try_parse_dates=True, null_values="NA"
).drop("Tobacco", "Bricks")

forecast = model.forecast_df(
    pl_df, prediction_length=8, target="Beer", timestamp_column="ds"
)
type(forecast)  # polars.DataFrame

pyarrow.csv.read_csv works the same way and returns a pyarrow.Table.

Supported output types

A dataframe call returns a dataframe. output_type accepts exactly two values:

output_type Returns
"dataframe" (default) one long-format frame, in the input's dataframe library
"pandas" the same frame, always as pandas

Anything else raises a ValueError.