Customizing exogenous effects
Checkout how to create a non-linear effect using the exogenous effect API, and expand the model with custom effects.
You may also wish to customize the trend component of the model. We have an interesting applied example!
You can create effect that capture a custom interaction between different exogenous variables.
The exogenous effect API allows you to create custom exogenous components for the Prophetverse model. This is useful when we want to model specific patterns or relationships between the exogenous variables and the target variable. For example, enforcing a positive effect of a variable on the mean, or modeling a non-linear relationship.
If you have read the theory section, by effect we mean each function \(f_i\). You can implement those custom functions by subclassing the BaseEffect
class, and then use them in the Prophetverse
model. Some effects are already implemented in the library, and you can find them in the prophetverse.effects
module.
When creating a model instance, effects can be specified through exogenous_effects
parameter of the Prophetverse
model. This parameter is a list of tuples of three values: the name, the effect object, and a regex to filter columns related to that effect. The regex is what defines \(x_i\) in the previous section. The prophetverse.utils.regex
module provides some useful functions to create regex patterns for common use cases, include starts_with
, ends_with
, contains
, and no_input_columns
.
For example:
from prophetverse.sktime import Prophetverse
from prophetverse.effects import LinearFourierSeasonality, HillEffect
from prophetverse.utils.regex import starts_with, no_input_columns
= [
exogenous_effects
("seasonality", # The name of the effect
# The object
LinearFourierSeasonality( ="D",
freq=[7, 365.25],
sp_list=[3, 10],
fourier_terms_list=0.1,
prior_scale="multiplicative",
effect_mode
),# The regex
no_input_columns,
),
("exog",
="additive"),
HillEffect(effect_mode"exog")
starts_with(
)
]
= Prophetverse(exogenous_effects=exogenous_effects) model
The effects can be any object that implements the BaseEffect
interface, and you can create your own effects by subclassing BaseEffect
and implementing _fit
, _transform
and _predict
methods.
_fit
(optional): This method is called during fit() of the forecasting and should be used to initialize any necessary parameters or data structures. It receives the exogenous variables dataframe X, the seriesy
, and the scale factorscale
that was used to scale the timeseries._transform
(optional): This method receives the exogenous variables dataframe, and should return an object containing the data needed for the effect. This object will be passed to the predict method asdata
. By default the columns of the dataframe that match the regex pattern are selected, and the result is converted to ajnp.ndarray
._predict
(mandatory): This method receives the output of_transform
and all previously computed effects. It should return the effect values as ajnp.ndarray
, which will be added to the model.
Example
Log Effect
The BaseAdditiveOrMultiplicativeEffect
provides an init argument effect_mode
that allows you to specify if the effect is additive or multiplicative. Letβs take as an example the LogEffect
:
#prophetverse/effects/log.py
from typing import Dict, Optional
import jax.numpy as jnp
import numpyro
from numpyro import distributions as dist
from numpyro.distributions import Distribution
from prophetverse.effects.base import (
EFFECT_APPLICATION_TYPE,
BaseAdditiveOrMultiplicativeEffect,
)
__all__ = ["LogEffect"]
class LogEffect(BaseAdditiveOrMultiplicativeEffect):
"""Represents a log effect as effect = scale * log(rate * data + 1).
Parameters
----------
scale_prior : Optional[Distribution], optional
The prior distribution for the scale parameter., by default Gamma
rate_prior : Optional[Distribution], optional
The prior distribution for the rate parameter., by default Gamma
effect_mode : effects_application, optional
Either "additive" or "multiplicative", by default "multiplicative"
"""
def __init__(
self,
= "multiplicative",
effect_mode: EFFECT_APPLICATION_TYPE = None,
scale_prior: Optional[Distribution] = None,
rate_prior: Optional[Distribution]
):self.scale_prior = scale_prior or dist.Gamma(1, 1)
self.rate_prior = rate_prior or dist.Gamma(1, 1)
super().__init__(effect_mode=effect_mode)
def _predict( # type: ignore[override]
self,
data: jnp.ndarray,str, jnp.ndarray]] = None,
predicted_effects: Optional[Dict[-> jnp.ndarray:
) """Apply and return the effect values.
Parameters
----------
data : Any
Data obtained from the transformed method.
predicted_effects : Dict[str, jnp.ndarray], optional
A dictionary containing the predicted effects, by default None.
Returns
-------
jnp.ndarray
An array with shape (T,1) for univariate timeseries, or (N, T, 1) for
multivariate timeseries, where T is the number of timepoints and N is the
number of series.
"""
= numpyro.sample("log_scale", self.scale_prior)
scale = numpyro.sample("log_rate", self.rate_prior)
rate = scale * jnp.log(jnp.clip(rate * data + 1, 1e-8, None))
effect
return effect
The _fit
and _transform
methods are not implemented, and the default behaviour is preserved (the columns of the dataframe that match the regex pattern are selected, and the result is converted to a jnp.ndarray
with key βdataβ).
Composition of effects
We can go further and create a custom effect that adds a likelihood term to the model. The LiftExperimentLikelihood
tackles the use case of having a lift experiment, and wanting to incorporate it to guide the exogenous effect. The likelihood term is added in the _predict
method, and the observed lift preprocessed in _transform
method.
To see more, check the custom effect how-to.