Repository: felipeangelimvieira/prophetverse

Issue:
[BUG] HierarchicalProphet parameter validation is not accepting custom trends
`HierarchicalProphet` is raising error when passing a custom trend.

Discussion:

Patch:
diff --git a/src/prophetverse/sktime/base.py b/src/prophetverse/sktime/base.py
index e998aff..5dce588 100644
--- a/src/prophetverse/sktime/base.py
+++ b/src/prophetverse/sktime/base.py
@@ -474,6 +474,13 @@ class BaseBayesianForecaster(BaseForecaster):
                 lambda x: np.abs(x).max()
             )
 
+        if isinstance(self._scale, (float, int)):
+            if self._scale == 0:
+                self._scale = 1
+        elif isinstance(self._scale, (pd.Series, pd.DataFrame)):
+            # Map any values that are 0 to 1
+            self._scale = self._scale.replace(0, 1)
+
     def _scale_y(self, y: pd.DataFrame) -> pd.DataFrame:
         """
         Scales the input DataFrame y (divide it by the scaling factor).
@@ -762,8 +769,9 @@ class BaseProphetForecaster(_HeterogenousMetaEstimator, BaseBayesianForecaster):
 
     Parameters
     ----------
-    trend : Union[str, BaseEffect], optional, one of "linear" (default) or "logistic"
-        Type of trend to use. Can also be a custom effect object.
+    trend : Union[str, BaseEffect], optional
+        One of "linear" (default), "linear1" or "logistic". Type of trend to use.
+        Can also be a custom effect object.
 
     changepoint_interval : int, optional, default=25
         Number of potential changepoints to sample in the history.
@@ -1025,6 +1033,15 @@ class BaseProphetForecaster(_HeterogenousMetaEstimator, BaseBayesianForecaster):
                 offset_prior_scale=self.offset_prior_scale,
             )
 
+        elif self.trend == "linear_raw":
+            return PiecewiseLinearTrend(
+                changepoint_interval=self.changepoint_interval,
+                changepoint_range=self.changepoint_range,
+                changepoint_prior_scale=self.changepoint_prior_scale,
+                offset_prior_scale=self.offset_prior_scale,
+                remove_seasonality_before_suggesting_initial_vals=False,
+            )
+
         elif self.trend == "logistic":
             return PiecewiseLogisticTrend(
                 changepoint_interval=self.changepoint_interval,
@@ -1060,9 +1077,12 @@ class BaseProphetForecaster(_HeterogenousMetaEstimator, BaseBayesianForecaster):
             raise ValueError("capacity_prior_loc must be greater than 0.")
         if self.offset_prior_scale <= 0:
             raise ValueError("offset_prior_scale must be greater than 0.")
-        if self.trend not in ["linear", "logistic", "flat"] and not isinstance(
-            self.trend, BaseEffect
-        ):
+        if self.trend not in [
+            "linear",
+            "linear_raw",
+            "logistic",
+            "flat",
+        ] and not isinstance(self.trend, BaseEffect):
             raise ValueError('trend must be either "linear" or "logistic".')
 
     def _match_columns(
diff --git a/src/prophetverse/sktime/multivariate.py b/src/prophetverse/sktime/multivariate.py
index 3b0352e..212cadb 100644
--- a/src/prophetverse/sktime/multivariate.py
+++ b/src/prophetverse/sktime/multivariate.py
@@ -189,9 +189,6 @@ class HierarchicalProphet(BaseProphetForecaster):
         if self.correlation_matrix_concentration <= 0:
             raise ValueError("correlation_matrix_concentration must be greater than 0.")
 
-        if self.trend not in ["linear", "logistic"]:
-            raise ValueError('trend must be either "linear" or "logistic".')
-
     def _get_fit_data(self, y, X, fh):
         """
         Prepare the data for the NumPyro model.


Test patch:
diff --git a/tests/sktime/test_multivariate.py b/tests/sktime/test_multivariate.py
index ed865b5..e51587e 100644
--- a/tests/sktime/test_multivariate.py
+++ b/tests/sktime/test_multivariate.py
@@ -2,6 +2,7 @@ import pytest
 from numpyro import distributions as dist
 
 from prophetverse.effects.linear import LinearEffect
+from prophetverse.effects.trend import PiecewiseLinearTrend
 from prophetverse.sktime.multivariate import HierarchicalProphet
 from prophetverse.sktime.seasonality import seasonal_transformer
 
@@ -16,9 +17,14 @@ from ._utils import (
 
 HYPERPARAMS = [
     dict(
+        trend=PiecewiseLinearTrend(
+            changepoint_interval=20,
+            changepoint_range=0.8,
+            changepoint_prior_scale=0.001,
+        ),
         feature_transformer=seasonal_transformer(
             yearly_seasonality=True, weekly_seasonality=True
-        )
+        ),
     ),
     dict(
         feature_transformer=seasonal_transformer(
@@ -42,7 +48,7 @@ HYPERPARAMS = [
         ],
     ),
     dict(
-        trend="linear",
+        trend="linear_raw",
     ),
     dict(trend="logistic"),
     dict(inference_method="mcmc"),
@@ -61,7 +67,7 @@ def test_hierarchy_levels(hierarchy_levels):
     y = make_y(hierarchy_levels)
     X = make_random_X(y)
     forecaster = HierarchicalProphet(
-        optimizer_steps=20, changepoint_interval=2, mcmc_samples=2, mcmc_warmup=2
+        optimizer_steps=2, changepoint_interval=2, mcmc_samples=2, mcmc_warmup=2
     )
     execute_fit_predict_test(forecaster, y, X)
 
@@ -74,7 +80,7 @@ def test_hyperparams(hyperparams):
     X = make_random_X(y)
     forecaster = HierarchicalProphet(
         **hyperparams,
-        optimizer_steps=20,
+        optimizer_steps=2,
         changepoint_interval=2,
         mcmc_samples=2,
         mcmc_warmup=2
@@ -111,3 +117,16 @@ def test_extra_predict_methods(make_X):
     )
 
     execute_extra_predict_methods_tests(forecaster=forecaster, X=X, y=y)
+
+
+def test_hierarchical_with_series_with_zeros():
+    y = make_y((2, 2, 1))
+    # Set all values to 0
+    y.iloc[:, :] = 0
+
+    forecaster = HierarchicalProphet(
+        optimizer_steps=5, changepoint_interval=2, mcmc_samples=2, mcmc_warmup=2
+    )
+
+    forecaster.fit(y)
+    forecaster.predict(fh=[1, 2, 3])