Skip to content

Builtin Exogenous Effects

AbstractEffect

Bases: ABC

Abstract class for effects.

Effects should inherit from this class and implement the compute_effect method. The id is used to create unique names for the samples in the model.

Source code in src/prophetverse/effects.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
class AbstractEffect(ABC):
    """Abstract class for effects.

    Effects should inherit from this class and implement the `compute_effect` method.
    The id is used to create unique names for the samples in the model.

    """

    def __init__(self, id="", regex=None, **kwargs):
        self.id = id
        self.regex = regex


    def match_columns(self, columns : pd.Index) -> pd.Index:
        """Match the columns of the DataFrame with the regex pattern.

        Args:
            X (pd.DataFrame): The DataFrame to match.

        Returns:
            pd.Index: The columns that match the regex pattern.
        """

        if isinstance(columns, List):
            columns = pd.Index(columns)

        if self.regex is None:
            raise ValueError("To use this method, you must set the regex pattern")
        return columns[columns.str.match(self.regex)]

    @staticmethod
    def split_data_into_effects(X : pd.DataFrame, effects : List) -> Dict[str, pd.DataFrame]:
        """Split the data into effects.

        Args:
            X (pd.DataFrame): The DataFrame to split.
            effects (List[AbstractEffect]): The effects to split the data into.

        Returns:
            Dict[str, pd.DataFrame]: A dictionary mapping effect names to DataFrames.
        """
        data = {}
        for effect in effects:
            data[effect.id] = X[effect.match_columns(X.columns)]
        return data

    def sample(self, name : str, *args, **kwargs):
        """
        Sample a random variable with a unique name.
        """
        return numpyro.sample(f"{self.id}__{name}", *args, **kwargs)

    @abstractmethod
    def compute_effect(self, trend : jnp.ndarray, data : jnp.ndarray) -> jnp.ndarray: 
        """Compute the effect based on the trend and data.

        Args:
            trend (jnp.ndarray): The trend.
            data (jnp.ndarray): The data concerning this effect.

        Returns:
            jnp.ndarray: The effect.
        """
        ...

    def __call__(self, trend, data):

        return self.compute_effect(trend, data)

compute_effect(trend, data) abstractmethod

Compute the effect based on the trend and data.

Parameters:

Name Type Description Default
trend ndarray

The trend.

required
data ndarray

The data concerning this effect.

required

Returns:

Type Description
ndarray

jnp.ndarray: The effect.

Source code in src/prophetverse/effects.py
68
69
70
71
72
73
74
75
76
77
78
79
@abstractmethod
def compute_effect(self, trend : jnp.ndarray, data : jnp.ndarray) -> jnp.ndarray: 
    """Compute the effect based on the trend and data.

    Args:
        trend (jnp.ndarray): The trend.
        data (jnp.ndarray): The data concerning this effect.

    Returns:
        jnp.ndarray: The effect.
    """
    ...

match_columns(columns)

Match the columns of the DataFrame with the regex pattern.

Parameters:

Name Type Description Default
X DataFrame

The DataFrame to match.

required

Returns:

Type Description
Index

pd.Index: The columns that match the regex pattern.

Source code in src/prophetverse/effects.py
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
def match_columns(self, columns : pd.Index) -> pd.Index:
    """Match the columns of the DataFrame with the regex pattern.

    Args:
        X (pd.DataFrame): The DataFrame to match.

    Returns:
        pd.Index: The columns that match the regex pattern.
    """

    if isinstance(columns, List):
        columns = pd.Index(columns)

    if self.regex is None:
        raise ValueError("To use this method, you must set the regex pattern")
    return columns[columns.str.match(self.regex)]

sample(name, *args, **kwargs)

Sample a random variable with a unique name.

Source code in src/prophetverse/effects.py
62
63
64
65
66
def sample(self, name : str, *args, **kwargs):
    """
    Sample a random variable with a unique name.
    """
    return numpyro.sample(f"{self.id}__{name}", *args, **kwargs)

split_data_into_effects(X, effects) staticmethod

Split the data into effects.

Parameters:

Name Type Description Default
X DataFrame

The DataFrame to split.

required
effects List[AbstractEffect]

The effects to split the data into.

required

Returns:

Type Description
Dict[str, DataFrame]

Dict[str, pd.DataFrame]: A dictionary mapping effect names to DataFrames.

Source code in src/prophetverse/effects.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@staticmethod
def split_data_into_effects(X : pd.DataFrame, effects : List) -> Dict[str, pd.DataFrame]:
    """Split the data into effects.

    Args:
        X (pd.DataFrame): The DataFrame to split.
        effects (List[AbstractEffect]): The effects to split the data into.

    Returns:
        Dict[str, pd.DataFrame]: A dictionary mapping effect names to DataFrames.
    """
    data = {}
    for effect in effects:
        data[effect.id] = X[effect.match_columns(X.columns)]
    return data

HillEffect

Bases: AbstractEffect

Represents a Hill effect in a time series model.

Attributes:

Name Type Description
half_max_prior

Prior distribution for the half-maximum parameter.

slope_prior

Prior distribution for the slope parameter.

max_effect_prior

Prior distribution for the maximum effect parameter.

effect_mode

Mode of the effect (either "additive" or "multiplicative").

Source code in src/prophetverse/effects.py
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
class HillEffect(AbstractEffect):
    """
    Represents a Hill effect in a time series model.

    Attributes:
        half_max_prior: Prior distribution for the half-maximum parameter.
        slope_prior: Prior distribution for the slope parameter.
        max_effect_prior: Prior distribution for the maximum effect parameter.
        effect_mode: Mode of the effect (either "additive" or "multiplicative").
    """

    def __init__(
        self,
        half_max_prior=None,
        slope_prior=None,
        max_effect_prior=None,
        effect_mode="multiplicative",
        **kwargs,
    ):

        if half_max_prior is None:
            half_max_prior = dist.Gamma(1, 1)
        if slope_prior is None:
            slope_prior = dist.HalfNormal(10)
        if max_effect_prior is None:
            max_effect_prior = dist.Gamma(1, 1)

        self.half_max_prior = half_max_prior
        self.slope_prior = slope_prior
        self.max_effect_prior = max_effect_prior
        self.effect_mode = effect_mode
        super().__init__(**kwargs)

    def compute_effect(self, trend, data):
        """
        Computes the effect using the log transformation.

        Args:
            trend: The trend component.
            data: The input data.

        Returns:
            The computed effect.
        """

        half_max = self.sample("half_max", self.half_max_prior)
        slope = self.sample("slope", self.slope_prior)
        max_effect = self.sample("max_effect", self.max_effect_prior)

        x = _exponent_safe(data / half_max, -slope)
        effect = max_effect / (1 + x)

        if self.effect_mode == "additive":
            return effect
        return trend * effect

compute_effect(trend, data)

Computes the effect using the log transformation.

Parameters:

Name Type Description Default
trend

The trend component.

required
data

The input data.

required

Returns:

Type Description

The computed effect.

Source code in src/prophetverse/effects.py
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
def compute_effect(self, trend, data):
    """
    Computes the effect using the log transformation.

    Args:
        trend: The trend component.
        data: The input data.

    Returns:
        The computed effect.
    """

    half_max = self.sample("half_max", self.half_max_prior)
    slope = self.sample("slope", self.slope_prior)
    max_effect = self.sample("max_effect", self.max_effect_prior)

    x = _exponent_safe(data / half_max, -slope)
    effect = max_effect / (1 + x)

    if self.effect_mode == "additive":
        return effect
    return trend * effect

LinearEffect

Bases: AbstractEffect

Represents a linear effect in a hierarchical prophet model.

Parameters:

Name Type Description Default
id str

The identifier for the effect.

required
prior Distribution

A numpyro distribution to use as prior. Defaults to dist.Normal(0, 1)

None
effect_mode str

The mode of the effect, either "multiplicative" or "additive".

'multiplicative'

Attributes:

Name Type Description
dist type

The distribution class used for sampling coefficients.

dist_args tuple

The arguments passed to the distribution class.

effect_mode str

The mode of the effect, either "multiplicative" or "additive".

Methods:

Name Description
compute_effect

Computes the effect based on the given trend and data.

Source code in src/prophetverse/effects.py
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
class LinearEffect(AbstractEffect):
    """
    Represents a linear effect in a hierarchical prophet model.

    Args:
        id (str): The identifier for the effect.
        prior (dist.Distribution): A numpyro distribution to use as prior. Defaults to dist.Normal(0, 1)
        effect_mode (str): The mode of the effect, either "multiplicative" or "additive".

    Attributes:
        dist (type): The distribution class used for sampling coefficients.
        dist_args (tuple): The arguments passed to the distribution class.
        effect_mode (str): The mode of the effect, either "multiplicative" or "additive".

    Methods:
        compute_effect(trend, data): Computes the effect based on the given trend and data.

    """

    def __init__(
        self,
        prior=None,
        effect_mode="multiplicative",
        **kwargs):
        self.prior = prior or dist.Normal(0, 0.1)
        self.effect_mode = effect_mode
        super().__init__(**kwargs)

    def compute_effect(self, trend, data):
        """
        Computes the effect based on the given trend and data.

        Args:
            trend: The trend component of the hierarchical prophet model.
            data: The data used to compute the effect.

        Returns:
            The computed effect based on the given trend and data.

        """
        n_features = data.shape[-1]

        with numpyro.plate(f"{self.id}_plate", n_features, dim=-1):
            coefficients = self.sample(
                "coefs",
                self.prior
            )

        if coefficients.ndim == 1:
            coefficients = jnp.expand_dims(coefficients, axis=-1)

        if data.ndim == 3 and coefficients.ndim == 2:
            coefficients = jnp.expand_dims(coefficients, axis=0)
        if self.effect_mode == "multiplicative":
            return multiplicative_effect(trend, data, coefficients)
        return additive_effect(trend, data, coefficients)

compute_effect(trend, data)

Computes the effect based on the given trend and data.

Parameters:

Name Type Description Default
trend

The trend component of the hierarchical prophet model.

required
data

The data used to compute the effect.

required

Returns:

Type Description

The computed effect based on the given trend and data.

Source code in src/prophetverse/effects.py
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
def compute_effect(self, trend, data):
    """
    Computes the effect based on the given trend and data.

    Args:
        trend: The trend component of the hierarchical prophet model.
        data: The data used to compute the effect.

    Returns:
        The computed effect based on the given trend and data.

    """
    n_features = data.shape[-1]

    with numpyro.plate(f"{self.id}_plate", n_features, dim=-1):
        coefficients = self.sample(
            "coefs",
            self.prior
        )

    if coefficients.ndim == 1:
        coefficients = jnp.expand_dims(coefficients, axis=-1)

    if data.ndim == 3 and coefficients.ndim == 2:
        coefficients = jnp.expand_dims(coefficients, axis=0)
    if self.effect_mode == "multiplicative":
        return multiplicative_effect(trend, data, coefficients)
    return additive_effect(trend, data, coefficients)

LogEffect

Bases: AbstractEffect

Log effect for a variable.

Computes the effect using the formula:

effect = scale * log(rate * data + 1)

A gamma prior is used for the scale and rate parameters.

Parameters:

Name Type Description Default
id str

The identifier for the effect.

required
scale_prior Distribution

The prior distribution for the scale parameter.

None
rate_prior Distribution

The prior distribution for the rate parameter.

None
Source code in src/prophetverse/effects.py
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
class LogEffect(AbstractEffect):
    """
    Log effect for a variable.

    Computes the effect using the formula:

    effect = scale * log(rate * data + 1)

    A gamma prior is used for the scale and rate parameters.

    Args:
        id (str): The identifier for the effect.
        scale_prior (dist.Distribution): The prior distribution for the scale parameter.
        rate_prior (dist.Distribution): The prior distribution for the rate parameter.
    """

    def __init__(
        self,
        scale_prior=None,
        rate_prior=None,
        effect_mode="multiplicative",
        **kwargs,
    ):
        if scale_prior is None:
            scale_prior = dist.Gamma(1, 1)
        if rate_prior is None:
            rate_prior = dist.Gamma(1, 1)

        self.scale_prior = scale_prior
        self.rate_prior = rate_prior
        self.effect_mode = effect_mode
        super().__init__(**kwargs)

    def compute_effect(self, trend, data):
        """
        Computes the effect using the log transformation.

        Args:
            trend: The trend component.
            data: The input data.

        Returns:
            The computed effect.
        """
        scale = self.sample("log_scale", self.scale_prior)
        rate = self.sample("log_rate", self.rate_prior)
        effect = scale * jnp.log(rate * data + 1)
        if self.effect_mode == "additive":
            return effect
        return trend * effect

compute_effect(trend, data)

Computes the effect using the log transformation.

Parameters:

Name Type Description Default
trend

The trend component.

required
data

The input data.

required

Returns:

Type Description

The computed effect.

Source code in src/prophetverse/effects.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
def compute_effect(self, trend, data):
    """
    Computes the effect using the log transformation.

    Args:
        trend: The trend component.
        data: The input data.

    Returns:
        The computed effect.
    """
    scale = self.sample("log_scale", self.scale_prior)
    rate = self.sample("log_rate", self.rate_prior)
    effect = scale * jnp.log(rate * data + 1)
    if self.effect_mode == "additive":
        return effect
    return trend * effect