Skip to main content

Module 6 — Exponential smoothing and Prophet

SARIMA gave us a solid rigorous model but stumbles on multiple seasonalities and abrupt changes. Two families side-step both problems by construction: exponential smoothing (Holt-Winters), the oldest workhorse in operational forecasting, and Prophet, an additive decomposition model built at Facebook to answer the everyday needs of analysts. This module fits both on the pharmacy series, adds their rows to the results table, and — critically — names the situations in which Prophet quietly disappoints.

Simple exponential smoothing

The simplest version of exponential smoothing predicts the next value as a weighted average of past observations, with weights decaying exponentially:

y^t+1=αyt+(1α)y^t\hat{y}_{t+1} = \alpha y_t + (1 - \alpha) \hat{y}_t

α(0,1)\alpha \in (0, 1) is the smoothing parameter: close to 1 pays attention mostly to the most recent value, close to 0 averages a long history. It is exactly a moving average with geometric weights, and it is the right model for series with no trend and no seasonality.

Fit in one line:

from statsmodels.tsa.holtwinters import SimpleExpSmoothing
fit = SimpleExpSmoothing(train).fit()
forecast = fit.forecast(28)

For our pharmacy series it is a straw man: the weekly cycle is not captured, MAE lands near 15. We include it to be honest about what smoothing alone gives us.

Holt-Winters: trend and seasonality

The full Holt-Winters model adds a trend component and a seasonal component, each with its own smoothing parameter. In additive form:

y^t+h=Lt+hBt+St+hmh/m\hat{y}_{t+h} = L_t + h \cdot B_t + S_{t + h - m \lceil h/m \rceil}

where LtL_t is the level, BtB_t the slope of the trend, and StS_t the seasonal index of period mm. Three parameters α,β,γ\alpha, \beta, \gamma update the three components; they are learned by minimizing training-set squared error.

from statsmodels.tsa.holtwinters import ExponentialSmoothing

hw = ExponentialSmoothing(
train,
trend="add",
seasonal="add",
seasonal_periods=7,
).fit(optimized=True)

fc = hw.forecast(28)

On our data this returns MAE 8.9 — better than seasonal-naïve, slightly worse than SARIMA. Holt-Winters excels on short series (200 observations is enough) with clean, stable seasonality; it degrades when the seasonal amplitude changes over years, and it does not support multiple periods natively.

Multiplicative seasonality. If seasonal amplitude scales with the level, set seasonal="mul". On the pharmacy data, the yearly amplitude is roughly proportional to the store-level growth, so a multiplicative model on the log-transformed series is a common improvement.

Prophet: the additive decomposition, packaged

Prophet was published by Facebook in 2017 with a specific target audience: analysts, not statisticians. It fits an additive model with four named components:

yt=g(t)+s(t)+h(t)+εty_t = g(t) + s(t) + h(t) + \varepsilon_t
  • g(t)g(t): a piecewise linear (or logistic) trend with automatic changepoints
  • s(t)s(t): seasonalities parameterized as Fourier series (weekly, yearly, or custom)
  • h(t)h(t): holidays as one-time regressors around named dates
  • εt\varepsilon_t: normal noise

Each component is easy to plot after fitting, which is Prophet's greatest strength in a business setting: the analyst can point at the yearly panel and say "here is the January peak the model learned".

Fitting Prophet on the pharmacy series

from prophet import Prophet

df_prophet = train.rename_axis("ds").reset_index().rename(columns={"units": "y"})

m = Prophet(
weekly_seasonality=True,
yearly_seasonality=True,
daily_seasonality=False,
changepoint_prior_scale=0.05,
seasonality_prior_scale=10.0,
)
m.add_country_holidays(country_name="FR")
m.fit(df_prophet)

future = m.make_future_dataframe(periods=28)
forecast = m.predict(future)
m.plot(forecast); m.plot_components(forecast)

Two prior scales control the model's flexibility. changepoint_prior_scale tunes how easily the trend can bend — too high and the trend chases noise, too low and it misses real changes. seasonality_prior_scale tunes the amplitude of the seasonal terms. These are the two knobs to sweep in a grid search when Prophet underperforms.

add_country_holidays injects a table of French holidays as additive regressors, each around a small window. On the pharmacy data, this dramatically improves prediction quality around November 1 (Toussaint) and December 25 (Christmas).

Result on the pharmacy series

ModelMAE (units/day)
naive24.6
seasonal_naive_79.8
SARIMA(1,1,1)(0,1,1)7_78.4
Holt-Winters8.9
Prophet with FR holidays7.6

Prophet edges ahead, primarily because it captures the yearly cycle (SARIMA saw only the weekly one) and treats holidays explicitly. That gain will grow further when we add external regressors in module 7.

Where Prophet actually helps

Prophet is a fantastic tool in three well-defined situations.

Business calendars. When holidays, promotions and campaigns matter more than raw autoregressive memory, the holidays argument and custom regressors let a non-specialist encode them in an afternoon. Getting the same information into SARIMAX requires more care.

Multiple seasonalities without a formal ARIMA fight. Setting yearly_seasonality=True and weekly_seasonality=True gives us the two cycles for free; SARIMA needed us to encode the yearly cycle as Fourier terms in an exogenous regressor and choose their order manually.

Interpretability. plot_components shows the analyst exactly what the model believes about each piece of the world. That is a communication superpower in a room where the decision-maker will not read a SARIMA summary table.

Where Prophet quietly disappoints

Prophet is not a universal answer, and pretending it is has cost several teams a year of misdirection.

Sub-daily and short-horizon forecasts. Prophet was optimized for daily-and-longer series with monthly or yearly horizons. On hourly data, or on 24-hour forecasts, its component fit tends to lag behind the tuning of a well-set exponential smoothing or gradient boosting. If your problem is "predict the next hour of demand", start elsewhere.

Long, memoryful autoregressions. Prophet's autoregressive memory is essentially zero — the model relies entirely on trend, seasonality and holidays. Series with real autocorrelation beyond the seasonal pattern (electricity spot prices, some financial series) lose information that ARIMA or an LSTM would keep.

Cases where the trend really is a random walk. Prophet enforces a piecewise linear trend. On a series whose trend has no linear structure — a stock price, a queue length — Prophet will draw a straight line through noise and produce an over-confident interval.

Uncertainty intervals. Prophet's default intervals are Monte-Carlo simulations of trend uncertainty, and they consistently under-estimate the true variance on short horizons. On our 28-day pharmacy forecast, the 80 % Prophet interval covered only 68 % of the actual observations in backtesting. Module 10 will build honest intervals from quantile regression on gradient boosting.

A representative failure: Prophet on hourly demand

We ran Prophet on an hourly variant of the pharmacy data — same stores, but hourly footfall rather than daily units. The default settings produced a smooth curve that missed the sharp lunch peak by 45 minutes and reported a MAE 30 % worse than a seasonal-naïve at 24 hours. Adding daily seasonality helped; even after tuning, gradient boosting on lagged features (module 7) beat Prophet by 20 % on the same data.

That is not a bug. It is Prophet operating in a regime it was not built for.

Prophet is not a default, it is a tool

"Just use Prophet" is a common suggestion in analyst circles, and it costs teams cycles on problems where it is genuinely a weak choice. Before adopting it, ask three questions: is the horizon daily-or-longer, is trend really piecewise linear, is autoregressive memory unimportant? Two "no"s should send you back to SARIMA or gradient boosting.

Summary

  • Simple exponential smoothing predicts next as a decaying weighted average; Holt-Winters adds a trend and a seasonal component with three smoothing parameters α,β,γ\alpha, \beta, \gamma.
  • Prophet fits an additive decomposition — piecewise-linear trend, Fourier seasonalities, holidays — with two main knobs (changepoint_prior_scale, seasonality_prior_scale).
  • On our pharmacy data with French holidays, Prophet beats SARIMA by 10 %; its plot-components output is the best communication tool in the family.
  • Prophet disappoints on sub-daily horizons, real autoregressive memory, non-linear trends, and its intervals consistently underestimate uncertainty — pick the tool to fit the problem.

Next module: turn the series into a supervised table with lags, calendar features and holidays, then hand it to gradient boosting — the approach that has won every recent large-scale forecasting competition.