diff --git a/tests/models/utils/test_forecaster.py b/tests/models/utils/test_forecaster.py index f3875010..2b96d450 100644 --- a/tests/models/utils/test_forecaster.py +++ b/tests/models/utils/test_forecaster.py @@ -2,6 +2,7 @@ import pytest from utilsforecast.data import generate_series +from timecopilot.models.prophet import Prophet from timecopilot.models.stats import SeasonalNaive from timecopilot.models.utils.forecaster import ( QuantileConverter, @@ -332,3 +333,16 @@ def test_detect_anomalies_short_series_error(): ) with pytest.raises(ValueError, match="Cannot perform anomaly detection"): model.detect_anomalies(df, h=5, freq="D") + + +def test_prophet_detect_anomalies_short_series_error(): + model = Prophet() + df = pd.DataFrame( + { + "unique_id": ["A", "A"], + "ds": pd.date_range("2023-01-01", periods=2, freq="D"), + "y": [1.0, 2.0], + } + ) + with pytest.raises(ValueError, match="Cannot perform anomaly detection"): + model.detect_anomalies(df, h=1, freq="D") diff --git a/timecopilot/models/utils/forecaster.py b/timecopilot/models/utils/forecaster.py index eb1b5df1..2201e49f 100644 --- a/timecopilot/models/utils/forecaster.py +++ b/timecopilot/models/utils/forecaster.py @@ -9,6 +9,7 @@ from gluonts.time_feature.seasonality import ( get_seasonality as _get_seasonality, ) +from prophet import Prophet as ProphetBase from scipy import stats from tqdm import tqdm from utilsforecast.plotting import plot_series @@ -304,6 +305,7 @@ def detect_anomalies( Args: df (pd.DataFrame): DataFrame containing the time series to detect anomalies. + Minimum series length is 1 higher for Prophet than other models. h (int, optional): Forecast horizon specifying how many future steps to predict. In each cross validation window. If not provided, the seasonality @@ -348,14 +350,19 @@ def detect_anomalies( min_series_length = df.groupby("unique_id").size().min() # we require at least one observation before the first forecast max_possible_windows = (min_series_length - 1) // h + # 3 row minimum for a df with Prophet + if isinstance(self, ProphetBase): + max_possible_windows -= 1 if n_windows is None: _n_windows = max_possible_windows else: _n_windows = min(n_windows, max_possible_windows) if _n_windows < 1: + # min series length should be 1 higher for Prophet than other models + exp_min_series_length = h + 2 if isinstance(self, ProphetBase) else h + 1 raise ValueError( f"Cannot perform anomaly detection: series too short. " - f"Minimum series length required: {h + 1}, " + f"Minimum series length required: {exp_min_series_length}, " f"actual minimum length: {min_series_length}" ) cv_results = self.cross_validation(