03: Facebook Prophetยถ
โProphet is a procedure for forecasting time series data based on an additive model where non-linear trends are fit with yearly, weekly, and daily seasonality, plus holiday effects.โ - Facebook Research
Welcome to Facebook Prophet! This notebook covers one of the most popular automated forecasting tools, designed for business forecasting with interpretable parameters.
๐ฏ Learning Objectivesยถ
By the end of this notebook, youโll be able to:
Understand Prophetโs additive forecasting model
Handle holidays and special events
Configure seasonality and trend parameters
Evaluate and interpret Prophet forecasts
Apply Prophet to real-world business problems
๐ Prophet Overviewยถ
Facebook Prophet is an open-source forecasting tool designed for business time series with strong seasonal effects and historical data.
Key Features:ยถ
Automated forecasting: Minimal parameter tuning required
Handles seasonality: Multiple seasonal patterns (daily, weekly, yearly)
Holiday effects: Incorporates special events and holidays
Trend changes: Automatically detects trend changes
Uncertainty quantification: Built-in confidence intervals
Interpretable: Clear model components and parameters
Prophet Model:ยถ
Where:
g(t): Trend function (piecewise linear or logistic)
s(t): Seasonal component (Fourier series)
h(t): Holiday effects
ฮตโ: Error term
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from prophet import Prophet
from prophet.plot import plot_plotly, plot_components_plotly
from prophet.diagnostics import cross_validation, performance_metrics
from sklearn.metrics import mean_absolute_error, mean_squared_error
import warnings
warnings.filterwarnings('ignore')
# Set up plotting
plt.style.use('default')
sns.set_palette("husl")
plt.rcParams['figure.figsize'] = [12, 8]
plt.rcParams['font.size'] = 12
# Set random seed
np.random.seed(42)
print("Facebook Prophet Libraries Loaded!")
print(f"Prophet version available for automated forecasting")
def generate_business_data(n_days=365, trend_slope=0.1, weekly_seasonal=5, yearly_seasonal=10):
"""Generate synthetic business time series data"""
# Create date range
dates = pd.date_range('2020-01-01', periods=n_days, freq='D')
# Base trend
t = np.arange(n_days)
trend = 100 + trend_slope * t
# Weekly seasonality (business week pattern)
weekly_pattern = np.sin(2 * np.pi * t / 7) * weekly_seasonal
# Yearly seasonality
yearly_pattern = np.sin(2 * np.pi * t / 365) * yearly_seasonal
# Add some holidays/special events
holidays = np.zeros(n_days)
# Christmas period
christmas_days = [354, 355, 356, 357, 358, 359, 360] # Last week of year
holidays[christmas_days] = 15
# Summer vacation period
summer_days = list(range(170, 200)) # Summer months
holidays[summer_days] = -8
# Random noise
noise = np.random.normal(0, 3, n_days)
# Combine components
y = trend + weekly_pattern + yearly_pattern + holidays + noise
# Create DataFrame in Prophet format
df = pd.DataFrame({
'ds': dates,
'y': y,
'trend': trend,
'weekly': weekly_pattern,
'yearly': yearly_pattern,
'holidays': holidays,
'noise': noise
})
return df
# Generate business data
business_data = generate_business_data()
print(f"Generated {len(business_data)} days of business data")
print(f"Date range: {business_data['ds'].min()} to {business_data['ds'].max()}")
print(f"Value range: {business_data['y'].min():.2f} to {business_data['y'].max():.2f}")
# Plot the generated data
plt.figure(figsize=(15, 8))
plt.plot(business_data['ds'], business_data['y'], 'b-', linewidth=1.5, alpha=0.8)
plt.title('Synthetic Business Time Series Data')
plt.xlabel('Date')
plt.ylabel('Value')
plt.grid(True, alpha=0.3)
plt.show()
# Show data structure
print("\nData Structure:")
print(business_data.head())
print("\nData Info:")
print(business_data.info())
๐ง Basic Prophet Modelยถ
Getting started with Prophet is incredibly simple - just fit the model and make predictions!
Basic Workflow:ยถ
Prepare data: DataFrame with โdsโ (date) and โyโ (value) columns
Create model:
Prophet()with default parametersFit model:
model.fit(df)Make predictions:
model.predict(future_df)Visualize: Built-in plotting functions
Key Parameters:ยถ
growth: โlinearโ or โlogisticโ trend
changepoints: Dates where trend changes are allowed
n_changepoints: Number of potential changepoints
seasonality_mode: โadditiveโ or โmultiplicativeโ
interval_width: Width of uncertainty intervals (default 0.8)
def basic_prophet_forecast(data, forecast_periods=30):
"""Create basic Prophet forecast"""
# Prepare data (select only ds and y columns)
df = data[['ds', 'y']].copy()
# Split into train/test
train_size = len(df) - forecast_periods
train_df = df[:train_size]
test_df = df[train_size:]
print(f"Training on {len(train_df)} observations")
print(f"Testing on {len(test_df)} observations")
# Create and fit model
model = Prophet(
growth='linear',
seasonality_mode='additive',
interval_width=0.95,
daily_seasonality=False, # No daily pattern in our data
weekly_seasonality=True, # Weekly patterns
yearly_seasonality=True # Yearly patterns
)
model.fit(train_df)
# Create future dataframe
future = model.make_future_dataframe(periods=forecast_periods, freq='D')
# Make predictions
forecast = model.predict(future)
return model, forecast, train_df, test_df
# Run basic Prophet forecast
model, forecast, train_df, test_df = basic_prophet_forecast(business_data, forecast_periods=30)
# Plot forecast
fig, ax = plt.subplots(figsize=(15, 8))
# Plot actual data
ax.plot(train_df['ds'], train_df['y'], 'k-', linewidth=2, label='Training Data')
ax.plot(test_df['ds'], test_df['y'], 'b-', linewidth=2, label='Test Data')
# Plot forecast
ax.plot(forecast['ds'], forecast['yhat'], 'r-', linewidth=2, label='Prophet Forecast')
ax.fill_between(forecast['ds'], forecast['yhat_lower'], forecast['yhat_upper'],
color='red', alpha=0.2, label='95% Confidence Interval')
# Add vertical line at train/test split
ax.axvline(x=train_df['ds'].iloc[-1], color='k', linestyle='--', alpha=0.7, label='Train/Test Split')
ax.set_title('Basic Prophet Forecast')
ax.set_xlabel('Date')
ax.set_ylabel('Value')
ax.legend()
ax.grid(True, alpha=0.3)
plt.show()
# Calculate accuracy
test_forecast = forecast[forecast['ds'].isin(test_df['ds'])]
mae = mean_absolute_error(test_df['y'], test_forecast['yhat'])
rmse = np.sqrt(mean_squared_error(test_df['y'], test_forecast['yhat']))
print(f"\nForecast Accuracy:")
print(f"MAE: {mae:.4f}")
print(f"RMSE: {rmse:.4f}")
print(f"Mean absolute percentage error: {np.mean(np.abs((test_df['y'] - test_forecast['yhat']) / test_df['y']))*100:.2f}%")
# Show forecast components
print("\nForecast Components:")
print(forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].tail())
๐ Model Componentsยถ
Prophet automatically decomposes the forecast into interpretable components.
Trend Component:ยถ
Piecewise linear: Connected linear segments
Changepoints: Points where slope changes
Automatic detection: Or manually specified
Seasonal Components:ยถ
Weekly: 7-day patterns
Yearly: Annual cycles
Custom: User-defined seasonalities
Fourier series: Smooth periodic functions
Holiday Effects:ยถ
Special events: One-time or recurring
Holiday windows: Before/after effects
Country-specific: Built-in holiday calendars
def analyze_prophet_components(model, forecast):
"""Analyze and visualize Prophet model components"""
# Plot components
fig = model.plot_components(forecast)
fig.set_size_inches(12, 10)
plt.show()
# Analyze trend changepoints
print("\n=== Trend Analysis ===")
changepoints = model.changepoints
if changepoints is not None:
print(f"Number of changepoints: {len(changepoints)}")
print("Changepoint dates:")
for cp in changepoints[:5]: # Show first 5
print(f" {cp.date()}")
if len(changepoints) > 5:
print(f" ... and {len(changepoints) - 5} more")
# Analyze seasonal parameters
print("\n=== Seasonal Parameters ===")
params = model.params
if 'weekly' in params:
weekly_params = params['weekly'][0]
print(f"Weekly seasonality parameters shape: {weekly_params.shape}")
print(f"Weekly seasonality magnitude: {np.abs(weekly_params).max():.4f}")
if 'yearly' in params:
yearly_params = params['yearly'][0]
print(f"Yearly seasonality parameters shape: {yearly_params.shape}")
print(f"Yearly seasonality magnitude: {np.abs(yearly_params).max():.4f}")
# Show forecast decomposition
print("\n=== Forecast Decomposition (last 5 days) ===")
components = ['trend', 'weekly', 'yearly']
available_components = [c for c in components if c in forecast.columns]
if available_components:
decomp_cols = ['ds', 'yhat'] + available_components
print(forecast[decomp_cols].tail())
# Plot decomposition
fig, axes = plt.subplots(len(available_components), 1, figsize=(12, 4*len(available_components)))
if len(available_components) == 1:
axes = [axes]
for i, comp in enumerate(available_components):
axes[i].plot(forecast['ds'], forecast[comp], 'b-', linewidth=2)
axes[i].set_title(f'{comp.capitalize()} Component')
axes[i].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Analyze the model components
analyze_prophet_components(model, forecast)
๐ Holiday Effectsยถ
Holidays and special events can significantly impact time series. Prophet handles these through:
Holiday Specification:ยถ
holiday DataFrame: โdsโ, โholidayโ, โlower_windowโ, โupper_windowโ
lower_window/upper_window: Days before/after holiday to include effect
holiday: Name of the holiday/event
Built-in Holidays:ยถ
Country-specific:
add_country_holidays(country_name='US')Multiple countries: Combine different holiday calendars
Custom holidays: Manually defined special events
Applications:ยถ
Retail: Christmas, Black Friday, holidays
Finance: Earnings announcements, economic events
Sports: Major events, championships
Weather: Storms, heatwaves, special weather events
def create_holidays_df():
"""Create holidays DataFrame for Prophet"""
holidays = pd.DataFrame({
'holiday': 'christmas_season',
'ds': pd.to_datetime(['2020-12-25', '2021-12-25', '2022-12-25']),
'lower_window': -7, # 7 days before
'upper_window': 7 # 7 days after
})
# Add more holidays
more_holidays = pd.DataFrame([
{'holiday': 'new_year', 'ds': pd.to_datetime('2021-01-01'), 'lower_window': -3, 'upper_window': 3},
{'holiday': 'summer_vacation', 'ds': pd.to_datetime('2021-07-15'), 'lower_window': -14, 'upper_window': 14},
{'holiday': 'black_friday', 'ds': pd.to_datetime('2021-11-26'), 'lower_window': -1, 'upper_window': 1},
{'holiday': 'labor_day', 'ds': pd.to_datetime('2021-09-06'), 'lower_window': -1, 'upper_window': 1},
])
holidays = pd.concat([holidays, more_holidays], ignore_index=True)
return holidays
def prophet_with_holidays(data, forecast_periods=30):
"""Create Prophet forecast with holiday effects"""
# Prepare data
df = data[['ds', 'y']].copy()
train_df = df[:-forecast_periods]
test_df = df[-forecast_periods:]
# Create holidays
holidays_df = create_holidays_df()
# Create model with holidays
model = Prophet(
holidays=holidays_df,
seasonality_mode='additive',
interval_width=0.95
)
# Add country holidays
model.add_country_holidays(country_name='US')
# Fit model
model.fit(train_df)
# Make forecast
future = model.make_future_dataframe(periods=forecast_periods, freq='D')
forecast = model.predict(future)
return model, forecast, train_df, test_df, holidays_df
# Run Prophet with holidays
model_holidays, forecast_holidays, train_df, test_df, holidays_df = prophet_with_holidays(business_data)
# Plot forecast with holidays
fig, ax = plt.subplots(figsize=(15, 8))
# Plot data and forecast
ax.plot(train_df['ds'], train_df['y'], 'k-', linewidth=2, label='Training Data')
ax.plot(test_df['ds'], test_df['y'], 'b-', linewidth=2, label='Test Data')
ax.plot(forecast_holidays['ds'], forecast_holidays['yhat'], 'r-', linewidth=2, label='Prophet Forecast')
ax.fill_between(forecast_holidays['ds'], forecast_holidays['yhat_lower'], forecast_holidays['yhat_upper'],
color='red', alpha=0.2)
# Highlight holidays
for _, holiday in holidays_df.iterrows():
holiday_dates = pd.date_range(holiday['ds'] + pd.Timedelta(days=holiday['lower_window']),
holiday['ds'] + pd.Timedelta(days=holiday['upper_window']))
for h_date in holiday_dates:
if h_date in forecast_holidays['ds'].values:
ax.axvline(x=h_date, color='orange', alpha=0.3, linewidth=1)
ax.axvline(x=train_df['ds'].iloc[-1], color='k', linestyle='--', alpha=0.7, label='Train/Test Split')
ax.set_title('Prophet Forecast with Holiday Effects')
ax.set_xlabel('Date')
ax.set_ylabel('Value')
ax.legend()
ax.grid(True, alpha=0.3)
plt.show()
# Show holiday effects
print("\nHoliday Effects in Forecast:")
holiday_cols = [col for col in forecast_holidays.columns if 'holiday' in col.lower() or 'christmas' in col.lower()]
if holiday_cols:
print(forecast_holidays[['ds'] + holiday_cols].tail(10))
# Calculate accuracy with holidays
test_forecast_holidays = forecast_holidays[forecast_holidays['ds'].isin(test_df['ds'])]
mae_holidays = mean_absolute_error(test_df['y'], test_forecast_holidays['yhat'])
rmse_holidays = np.sqrt(mean_squared_error(test_df['y'], test_forecast_holidays['yhat']))
print(f"\nAccuracy with Holiday Effects:")
print(f"MAE: {mae_holidays:.4f}")
print(f"RMSE: {rmse_holidays:.4f}")
print(f"Improvement over basic model: {mae - mae_holidays:.4f} MAE")
โ๏ธ Advanced Configurationยถ
Prophet offers extensive customization for different forecasting scenarios.
Trend Configuration:ยถ
changepoint_prior_scale: Flexibility of trend changes (higher = more changes)
changepoints: Manually specify changepoint dates
n_changepoints: Number of potential changepoints
Seasonality Configuration:ยถ
seasonality_prior_scale: Strength of seasonal effects
Custom seasonalities:
add_seasonality(name, period, fourier_order)seasonality_mode: โadditiveโ vs โmultiplicativeโ
Uncertainty & Performance:ยถ
mcmc_samples: Full Bayesian inference (slower but more accurate)
uncertainty_samples: Number of simulations for uncertainty
stan_backend: Use PyStan or CmdStanPy
def advanced_prophet_configurations(data, forecast_periods=30):
"""Compare different Prophet configurations"""
df = data[['ds', 'y']].copy()
train_df = df[:-forecast_periods]
test_df = df[-forecast_periods:]
configurations = {
'Conservative': {
'changepoint_prior_scale': 0.01, # Less flexible trend
'seasonality_prior_scale': 1.0,
'seasonality_mode': 'additive'
},
'Flexible': {
'changepoint_prior_scale': 0.5, # More flexible trend
'seasonality_prior_scale': 10.0, # Stronger seasonality
'seasonality_mode': 'additive'
},
'Multiplicative': {
'changepoint_prior_scale': 0.05,
'seasonality_prior_scale': 1.0,
'seasonality_mode': 'multiplicative'
},
'Custom_Seasonality': {
'changepoint_prior_scale': 0.05,
'seasonality_prior_scale': 1.0,
'seasonality_mode': 'additive',
'custom_seasonality': True
}
}
results = {}
for name, config in configurations.items():
print(f"\n=== {name} Configuration ===")
# Create model
model = Prophet(
changepoint_prior_scale=config['changepoint_prior_scale'],
seasonality_prior_scale=config['seasonality_prior_scale'],
seasonality_mode=config['seasonality_mode'],
interval_width=0.95
)
# Add custom seasonality if specified
if config.get('custom_seasonality', False):
# Add quarterly seasonality
model.add_seasonality(name='quarterly', period=91.25, fourier_order=5)
# Add monthly seasonality
model.add_seasonality(name='monthly', period=30.44, fourier_order=3)
# Fit model
model.fit(train_df)
# Make forecast
future = model.make_future_dataframe(periods=forecast_periods, freq='D')
forecast = model.predict(future)
# Calculate accuracy
test_forecast = forecast[forecast['ds'].isin(test_df['ds'])]
mae = mean_absolute_error(test_df['y'], test_forecast['yhat'])
rmse = np.sqrt(mean_squared_error(test_df['y'], test_forecast['yhat']))
results[name] = {
'model': model,
'forecast': forecast,
'mae': mae,
'rmse': rmse
}
print(f"MAE: {mae:.4f}, RMSE: {rmse:.4f}")
return results
# Run advanced configurations
config_results = advanced_prophet_configurations(business_data)
# Plot comparison
plt.figure(figsize=(15, 8))
# Plot actual data
plt.plot(business_data['ds'], business_data['y'], 'k-', linewidth=2, alpha=0.8, label='Actual Data')
plt.axvline(x=business_data['ds'].iloc[-30], color='k', linestyle='--', alpha=0.7, label='Train/Test Split')
# Plot forecasts from different configurations
colors = ['red', 'blue', 'green', 'orange']
for i, (name, result) in enumerate(config_results.items()):
forecast = result['forecast']
test_forecast = forecast[forecast['ds'] > business_data['ds'].iloc[-30]]
plt.plot(test_forecast['ds'], test_forecast['yhat'],
color=colors[i % len(colors)], linewidth=2,
label=f'{name} (MAE: {result["mae"]:.2f})')
plt.title('Prophet Configuration Comparison')
plt.xlabel('Date')
plt.ylabel('Value')
plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Print summary
print("\n=== Configuration Performance Summary ===")
print("Configuration | MAE | RMSE")
print("-" * 40)
for name, result in config_results.items():
print(f"{name:20} | {result['mae']:.4f} | {result['rmse']:.4f}")
best_config = min(config_results.items(), key=lambda x: x[1]['mae'])
print(f"\n๐ Best configuration: {best_config[0]} (MAE: {best_config[1]['mae']:.4f})")
๐ Cross-Validation & Diagnosticsยถ
Proper model evaluation is crucial for reliable forecasting.
Time Series Cross-Validation:ยถ
Rolling window: Fixed training size, expanding test
Expanding window: Growing training set
Multiple horizons: Evaluate different forecast lengths
Prophet Diagnostics:ยถ
cross_validation(): Automated time series CV
performance_metrics(): Calculate accuracy metrics
plot_cross_validation_metric(): Visualize performance over horizons
Key Metrics:ยถ
MAE: Mean Absolute Error
MSE: Mean Squared Error
RMSE: Root Mean Squared Error
MAPE: Mean Absolute Percentage Error
Coverage: Prediction interval coverage
from prophet.diagnostics import cross_validation, performance_metrics
from prophet.plot import plot_cross_validation_metric
def prophet_cross_validation(data, initial_days=180, period_days=30, horizon_days=30):
"""Perform time series cross-validation for Prophet"""
# Prepare data
df = data[['ds', 'y']].copy()
# Create model
model = Prophet(
changepoint_prior_scale=0.05,
seasonality_prior_scale=1.0,
seasonality_mode='additive'
)
# Fit model
model.fit(df)
# Perform cross-validation
print(f"Performing cross-validation...")
print(f"Initial training: {initial_days} days")
print(f"Period: {period_days} days")
print(f"Horizon: {horizon_days} days")
df_cv = cross_validation(
model,
initial=f'{initial_days} days',
period=f'{period_days} days',
horizon=f'{horizon_days} days'
)
# Calculate performance metrics
df_p = performance_metrics(df_cv)
return df_cv, df_p, model
# Perform cross-validation
df_cv, df_p, cv_model = prophet_cross_validation(business_data,
initial_days=180,
period_days=30,
horizon_days=30)
# Display cross-validation results
print("\nCross-Validation Results:")
print(df_cv.head())
print("\nPerformance Metrics by Horizon:")
print(df_p.head(10))
# Plot performance over horizons
fig, ax = plt.subplots(figsize=(12, 6))
ax.plot(df_p['horizon'], df_p['mae'], 'b-', linewidth=2, label='MAE')
ax.plot(df_p['horizon'], df_p['rmse'], 'r-', linewidth=2, label='RMSE')
ax.set_title('Cross-Validation Performance by Forecast Horizon')
ax.set_xlabel('Forecast Horizon')
ax.set_ylabel('Error')
ax.legend()
ax.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
# Plot coverage
fig, ax = plt.subplots(figsize=(12, 6))
ax.plot(df_p['horizon'], df_p['coverage'], 'g-', linewidth=2, label='Coverage')
ax.axhline(y=0.95, color='r', linestyle='--', alpha=0.7, label='Target (95%)')
ax.set_title('Prediction Interval Coverage by Horizon')
ax.set_xlabel('Forecast Horizon')
ax.set_ylabel('Coverage')
ax.legend()
ax.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
# Summary statistics
print("\n=== Cross-Validation Summary ===")
print(f"Total predictions: {len(df_cv)}")
print(f"Average MAE: {df_p['mae'].mean():.4f}")
print(f"Average RMSE: {df_p['rmse'].mean():.4f}")
print(f"Average MAPE: {df_p['mape'].mean():.2f}%")
print(f"Average Coverage: {df_p['coverage'].mean():.2%}")
print(f"MAE range: {df_p['mae'].min():.4f} - {df_p['mae'].max():.4f}")
print(f"Coverage range: {df_p['coverage'].min():.2%} - {df_p['coverage'].max():.2%}")
๐ฏ Key Takeawaysยถ
Prophet is automated: Minimal parameter tuning required for good results
Handles seasonality: Automatically detects and models seasonal patterns
Holiday effects: Incorporates special events and holidays
Interpretable: Clear decomposition into trend, seasonality, holidays
Uncertainty quantification: Built-in confidence intervals
Cross-validation: Proper evaluation for time series models
๐ When to Use Prophetยถ
โ Good For:ยถ
Business forecasting: Sales, demand, KPIs
Strong seasonality: Weekly, monthly, yearly patterns
Holiday effects: Retail, tourism, events
Interpretable results: Stakeholder communication
Limited data science resources: Automated approach
โ Less Ideal For:ยถ
High-frequency data: Intraday, real-time
Complex non-linear patterns: Deep learning may be better
Causal inference: Doesnโt handle interventions well
Multivariate forecasting: Limited exogenous variable support
๐ก Pro Tipsยถ
Data quality matters: Clean, regular time series works best
Tune changepoint flexibility: More data = more changepoints allowed
Include relevant holidays: Domain knowledge improves accuracy
Cross-validate thoroughly: Test different horizons and periods
Monitor performance: Retrain regularly with new data
Combine with other methods: Ensemble Prophet with ARIMA/ML
๐ Next Stepsยถ
Now that you understand Prophet, youโre ready for:
Deep Learning for Time Series: LSTM, Transformer models
Advanced Forecasting: Bayesian methods, ensemble techniques
Real-world Applications: Production deployment and monitoring
Multivariate Forecasting: Multiple time series simultaneously
๐ Further Readingยถ
Prophet Paper: โForecasting at Scaleโ by Taylor & Letham
Prophet Documentation: Comprehensive guides and tutorials
Time Series Books: โForecasting: Principles and Practiceโ
Ready to explore deep learning approaches for time series? Letโs continue! ๐ง ๐