How to Use Time Weighting¶
This guide shows you how to weight observations by recency or seasonal relevance so that forecasters and scorers focus on the most informative parts of your history.
Prerequisites¶
- A fitted forecaster or scorer (Getting Started)
- For panel-aware weights: familiarity with panel data (Work with Panel Data)
Try it interactively
Apply exponential decay, linear decay, and seasonal emphasis weighting to forecast evaluation, prioritising recent or periodic time steps.
ViewOpen in marimoUse time_weight and sample_weight_alignment to emphasise recent or seasonal training samples in PointReductionForecaster, with visualisation of weight curves and alignment strategy comparison.
ViewOpen in marimo1. Choose a Weighting Strategy¶
The time_weight parameter accepts a callable, a pl.DataFrame, or a dict.
Built-in weight functions (callables) cover the most common patterns.
If you want to down-weight old observations smoothly, use
exponential_decay_weight.
It halves the weight every half_life days, keeping the most recent
observation at 1.0:
from yohou.utils.weighting import exponential_decay_weight
weight_fn = exponential_decay_weight(half_life=365)
half_life accepts an int, float (both interpreted as days), or a
timedelta for explicit units.
If you prefer a simple ramp from 0 (oldest) to 1 (newest), use
linear_decay_weight:
To zero out observations older than a fixed window, pass max_steps:
If seasonality matters more than recency, use
seasonal_emphasis_weight
to boost observations at the same seasonal position as the most recent one:
from yohou.utils.weighting import seasonal_emphasis_weight
# Emphasize same-month observations (monthly data with yearly cycle)
weight_fn = seasonal_emphasis_weight(seasonality=12, emphasis=2.0)
In-phase observations get the emphasis weight (default 2.0), all others get
1.0. For multiple seasonalities, pass a list:
If you already have pre-computed weights, pass a pl.DataFrame with "time"
and "weight" columns:
import polars as pl
time_weight = pl.DataFrame({
"time": y_train["time"],
"weight": [1.0, 1.0, 0.5, 0.5, 0.0],
})
For panel data, use group-specific columns (e.g. "store_a_weight",
"store_b_weight") or a single "weight" column applied to all groups.
You can also pass a dict mapping timestamps to weights. Timestamps not in
the dict get a default weight of 1.0 (or use "*" to set a different default):
from datetime import datetime
time_weight = {
datetime(2024, 6, 1): 2.0,
datetime(2024, 7, 1): 2.0,
"*": 0.5, # all other timestamps
}
2. Compose Multiple Weights¶
To combine recency and seasonal effects, use
compose_weights.
It multiplies the weight functions element-wise:
from yohou.utils.weighting import compose_weights
weight_fn = compose_weights(
exponential_decay_weight(half_life=365),
seasonal_emphasis_weight(seasonality=12, emphasis=2.0),
)
3. Apply Weights During Training¶
Pass the weight function as time_weight when fitting a reduction forecaster:
from sklearn.linear_model import Ridge
from yohou.point import PointReductionForecaster
forecaster = PointReductionForecaster(estimator=Ridge())
forecaster.fit(
y_train,
forecasting_horizon=12,
time_weight=weight_fn,
)
The forecaster converts time weights to sklearn sample_weight internally.
Because each training sample spans multiple forecast steps, the per-timestamp
weights must be collapsed into one weight per sample. The
sample_weight_alignment parameter controls how:
forecaster.fit(
y_train,
forecasting_horizon=12,
time_weight=weight_fn,
sample_weight_alignment="mean_step",
)
The default is "first_step". See Weighting for
a full comparison of alignment strategies.
4. Apply Weights During Scoring¶
Scorers accept time_weight in score() to weight per-timestep errors:
from yohou.metrics import MeanAbsoluteError
scorer = MeanAbsoluteError()
scorer.fit(y_train)
weighted_score = scorer.score(y_test, y_pred, time_weight=weight_fn)
Scorers also support step_weight and vintage_weight for multi-vintage
predictions. See Multi-vintage Scoring for details.
5. Customize Weights for Panel Data¶
Weight functions can accept a second parameter with the group name, letting you assign different weight profiles per panel:
def panel_weight(time: pl.Series, group_name: str | None) -> pl.Series:
if group_name == "store_a":
return exponential_decay_weight(half_life=180)(time)
return exponential_decay_weight(half_life=365)(time)
forecaster.fit(y_train, forecasting_horizon=12, time_weight=panel_weight)
The framework detects whether your callable takes one or two parameters
automatically. For global (non-panel) data, group_name is None.
6. Visualize the Weight Profile¶
plot_time_weight
shows weights over time. Build the expected DataFrame and pass it in:
from yohou.plotting import plot_time_weight
weights = weight_fn(y_train["time"])
weights_df = y_train.select("time").with_columns(time_weight=weights)
plot_time_weight(weights_df)
If your weight column has a different name, pass weight_column="my_col". To
disable the filled area under the curve, pass fill=False.
See Also¶
- Weighting for the conceptual overview of weight types, alignment strategies, and normalization
- Evaluate Forecast Accuracy for using scorers with and without weights
- Handle Long Series for limiting history length as an alternative to down-weighting old data
- Multi-vintage Scoring for
step_weightandvintage_weightin context - API Reference: yohou.utils.weighting for the full parameter listing