Time Series Forecasting with Prophet Cheat Sheet
Forecast seasonal time series data using Meta's Prophet library, covering trend, seasonality, holidays, and cross-validation.
Fit a Model and Forecast
Prophet expects a two-column DataFrame with 'ds' (date) and 'y' (value) columns.
import pandas as pdfrom prophet import Prophetdf = pd.DataFrame({"ds": dates, "y": values}) # ds: datetime, y: numericmodel = Prophet( yearly_seasonality=True, weekly_seasonality=True, daily_seasonality=False, seasonality_mode="multiplicative",)model.fit(df)future = model.make_future_dataframe(periods=90) # 90 days aheadforecast = model.predict(future)print(forecast[["ds", "yhat", "yhat_lower", "yhat_upper"]].tail())
Add Holidays and Extra Regressors
Incorporate known holiday effects and additional predictive signals into the model.
holidays = pd.DataFrame({ "holiday": "black_friday", "ds": pd.to_datetime(["2025-11-28", "2026-11-27"]), "lower_window": -1, "upper_window": 1,})model = Prophet(holidays=holidays)model.add_regressor("marketing_spend")model.fit(df) # df must include a 'marketing_spend' column
Plot Forecast and Components
Visualize the forecast with uncertainty intervals and decompose it into trend/seasonality.
fig1 = model.plot(forecast)fig2 = model.plot_components(forecast) # trend, weekly, yearly panels# mark changepoints where the trend shiftedfrom prophet.plot import add_changepoints_to_plotadd_changepoints_to_plot(fig1.gca(), model, forecast)
Cross-Validate Forecast Accuracy
Backtest the model across multiple rolling cutoffs and compute error metrics.
from prophet.diagnostics import cross_validation, performance_metricscv_results = cross_validation( model, initial="730 days", period="180 days", horizon="90 days")metrics = performance_metrics(cv_results)print(metrics[["horizon", "mape", "rmse"]].head())
Key Model Parameters
The parameters most likely to need tuning for a real dataset.
- changepoint_prior_scale- controls trend flexibility; higher = trend adapts more to fluctuations
- seasonality_mode- 'additive' (default) vs 'multiplicative' for seasonality that scales with the trend
- seasonality_prior_scale- controls how strongly seasonal components can fit the data
- growth- 'linear', 'logistic' (needs a cap), or 'flat' trend assumption
- interval_width- width of the uncertainty interval, default 0.80
Saturating Forecasts with Logistic Growth
Use a capacity ceiling and floor when growth must level off, e.g. market saturation or bounded metrics.
df["cap"] = 10000 # total addressable market / hard ceilingdf["floor"] = 0model = Prophet(growth="logistic")model.fit(df)future = model.make_future_dataframe(periods=180)future["cap"] = 10000 # cap/floor must be set on future rows toofuture["floor"] = 0forecast = model.predict(future)
Conditional Custom Seasonality
Replace the default weekly pattern with two mutually-exclusive seasonalities that only apply during certain periods.
df["on_season"] = df["ds"].dt.month.isin([11, 12])df["off_season"] = ~df["on_season"]model = Prophet(weekly_seasonality=False)model.add_seasonality( name="weekly_on_season", period=7, fourier_order=3, condition_name="on_season")model.add_seasonality( name="weekly_off_season", period=7, fourier_order=3, condition_name="off_season")model.fit(df) # df must carry the on_season/off_season boolean columns
Grid Search Hyperparameters via Cross-Validation
Sweep changepoint and seasonality priors and pick the combination with the lowest backtested RMSE.
import itertoolsimport numpy as npfrom prophet.diagnostics import cross_validation, performance_metricsparam_grid = { "changepoint_prior_scale": [0.001, 0.01, 0.1, 0.5], "seasonality_prior_scale": [0.1, 1.0, 10.0],}all_params = [dict(zip(param_grid.keys(), v)) for v in itertools.product(*param_grid.values())]rmses = []for params in all_params: m = Prophet(**params).fit(df) cv = cross_validation( m, initial="730 days", period="180 days", horizon="90 days", parallel="processes" ) metrics = performance_metrics(cv, rolling_window=1) rmses.append(metrics["rmse"].values[0])best_params = all_params[int(np.argmin(rmses))]
Serialize a Model for Production
Persist a fitted Prophet model to JSON so a serving process can load it without refitting on every request.
from prophet.serialize import model_to_json, model_from_jsonwith open("prophet_model.json", "w") as f: f.write(model_to_json(model))# in the serving processwith open("prophet_model.json", "r") as f: model = model_from_json(f.read())forecast = model.predict(future) # no re-fit needed
performance_metrics() Output Columns
What each backtest error column measures, from most to least sensitive to outliers.
- mse / rmse- squared error; heavily penalizes large misses, good for spotting rare bad forecasts
- mae- mean absolute error, same units as y, less sensitive to outliers than rmse
- mape- mean absolute percentage error; unstable when y is near zero
- mdape- median absolute percentage error, robust to a handful of extreme percentage errors
- smape- symmetric MAPE, bounded and more stable than plain MAPE near zero
- coverage- fraction of actuals falling inside the yhat_lower/yhat_upper interval; should track interval_width
Always run cross_validation with realistic initial/period/horizon windows before trusting a Prophet forecast — a model that looks great on an in-sample plot.model() call often has much worse MAPE once you backtest it on rolling out-of-sample cutoffs.