diff --git a/mozaic/core.py b/mozaic/core.py index e5a1b64..4e7ca3c 100644 --- a/mozaic/core.py +++ b/mozaic/core.py @@ -1,3 +1,5 @@ +import warnings + import numpy as np import pandas as pd @@ -291,6 +293,19 @@ def _predict_holiday_effects(self): .sum() .reindex(self.forecast_dates, fill_value=0) ) + # warn if any holiday effects will be clipped at the -0.6 boundary + clipped = self.proportional_holiday_effects[self.proportional_holiday_effects < -0.6] + if len(clipped) > 0: + details = ", ".join( + f"{date.strftime('%Y-%m-%d')} ({value:.3f})" + for date, value in clipped.items() + ) + warnings.warn( + f"Holiday effects clipped at -0.6 boundary for dates: {details}", + UserWarning, + stacklevel=2, + ) + # ensure no single-day impact becomes too large self.proportional_holiday_effects.clip(lower=-0.6, upper=0, inplace=True) diff --git a/mozaic/models.py b/mozaic/models.py index 63106bc..ee065dd 100644 --- a/mozaic/models.py +++ b/mozaic/models.py @@ -10,9 +10,9 @@ def desktop_forecast_model(historical_data, historical_dates, forecast_dates): params = { "daily_seasonality": False, "weekly_seasonality": True, - "yearly_seasonality": False, + "yearly_seasonality": True, "uncertainty_samples": 1000, - "changepoint_range": 0.8, + "changepoint_range": 0.7, "seasonality_prior_scale": 0.00825, "changepoint_prior_scale": 0.15983, "growth": "logistic", @@ -20,15 +20,9 @@ def desktop_forecast_model(historical_data, historical_dates, forecast_dates): x = historical_data - # if x.max() >= 10e6: - # params["growth"] = "logistic" - if (x.abs().corr(x.diff().abs()) or 0) > 0.0: params["seasonality_mode"] = "multiplicative" params["growth"] = "linear" - # params["seasonality_prior_scale"] = 20 - # params["changepoint_prior_scale"] = 0.05 - # params["growth"] = "logistic" if (len(x.dropna()) > (365 * 2)) and ( np.quantile(x.dropna(), 0.5) / (np.quantile(x.dropna(), 0.1) + 1e-8) < 5 @@ -49,25 +43,13 @@ def desktop_forecast_model(historical_data, historical_dates, forecast_dates): ) future = pd.DataFrame({"ds": forecast_dates}) - # if "growth" in params: - # if historical_data.max() >= 10e6: - # cap = observed["y"].max() * 1.2 - # floor = observed["y"].min() * 0.8 - # observed["cap"] = cap - # observed["floor"] = floor - # future["cap"] = cap - # future["floor"] = floor - # else: - # cap = observed["y"].max() * 1.2 - # floor = 0.0 - # observed["cap"] = cap - # observed["floor"] = floor - # future["cap"] = cap - # future["floor"] = floor - if params["growth"] == "logistic": - cap = observed["y"].max() * 1.2 - floor = observed["y"].min() * 0.8 + cap = observed["y"].tail(366).max() * 1.05 + if cap > 100e6: + floor = observed["y"].tail(366).min() * 1 + else: + floor = observed["y"].tail(366).min() * 0.92 + observed["cap"] = cap observed["floor"] = floor future["cap"] = cap @@ -96,13 +78,14 @@ def mobile_forecast_model(historical_data, historical_dates, forecast_dates): "weekly_seasonality": True, "yearly_seasonality": len(historical_data.dropna()) > (365 * 2), "uncertainty_samples": 1000, - "changepoint_range": 0.8, + "changepoint_range": 0.82, "growth": "logistic", } if historical_data.max() >= 1e6: params["seasonality_prior_scale"] = 0.1 params["changepoint_prior_scale"] = 0.1 + params["growth"] = "linear" if historical_data.max() <= 2e6: params["seasonality_mode"] = "multiplicative" @@ -118,14 +101,14 @@ def mobile_forecast_model(historical_data, historical_dates, forecast_dates): if "growth" in params: if historical_data.max() >= 10e6: - cap = historical_data.max() * 2.0 - floor = historical_data.min() * 0.8 + cap = observed["y"].tail(366).max() * 1.10 + floor = observed["y"].tail(366).min() * 1.05 observed["cap"] = cap observed["floor"] = floor future["cap"] = cap future["floor"] = floor else: - cap = historical_data.max() * 2.0 + cap = historical_data.max() * 1.1 floor = 0.0 observed["cap"] = cap observed["floor"] = floor