Using Prophet after COVID? Read this first. | by JZ Lu | Towards Data Science

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. It works best with time series that have strong seasonal effects and several seasons of historical data. Prophet is robust to missing data and shifts in the trend, and typically handles outliers well.
By using a decomposable time series model, the Prophet model has three main components:
y(t) = g(t) + s(t) + h(t) + e(t)g(t): trend functions that models non-periodic changes; s(t): seasonality functions that models periodic changes; i.e. weekly, monthly, yearly seasonality;h(t): holdiay function that represents the effects of potentially irregular scheduled holidays;e(t): error terms that represents changes that are not captured by the model;
If you are new to Prophet and would like to learn more details about it, please check these awesome Medium articles (one, two, three) as well as the original publication that can give you a quick and deep start. Assuming you’ve played with Prophet models for a while and understand the fundamentals, this article covers 3 important advanced topics that you may come across sooner or later when modeling time series with Prophet after COVID-19.
How to deal with COVID’s impact?
Additive vs Multiplicative model? which one to choose?
How to deal with non-daily data?
COVID’s impact has penetrated into every aspect. If you’re dealing with time-series data, you probably have noticed some weird model performance after 2020. This is like to be driven by the impact that COVID has made on the trend and seasonality of your time-series data. There are several ways to deal with this problem.
Models don’t have emotions. We could do a simple “trick” to let the model treat COVID as an event or “holiday”. The concept is straightforward. Just like during holiday shopping seasons where we will expect increasing shopping mall traffic, the model will treat COVID as a special “holiday” where it estimates decreasing shopping mall traffic. The codes below will do the work.
COVID_lockdown = pd.DataFrame({        'holiday': 'covid',        'ds':  pd.date_range(start='2020-03-15',                              end='2020-05-01',                             freq='D'),        'lower_window': 0,        'upper_window': 0,        'prior_scale': 1         })
However, this method is tricky since there are so many external factors (state policy changes) that make the COVID “holiday” effect inconsistent over time and across different scenarios.
This is the simplest method. Just drop 2020’s data or interpolate 2020’s data based on 2019’s data. However, as you could imagine, this method is very unreliable especially when you have short historical data. We still want to include real 2020 data to help the model learn the seasonalities. However, if your time-series data is generally stable year over year, and you think COVID doesn't play a role in your data anymore, this method is worth to be tested out.
COVID may create a weekly seasonal pattern that is different during the pandemic/lock-down than it is after the pandemic. This method will allow you to train two separate seasonality patterns, which might be helpful for some time series data. However, during the experiments, I found this method yielded the worst model performance. So think again whether it makes sense to use two different seasonalities to model your data.
#define pre and post covid perioddef is_postcovid_seasonality(ds):            date = pd.to_datetime(ds)            return date.date() >= pd.to_datetime('2021-03-15').date()dfprophet['pre_covid_seasonality']=~dfprophet['ds'].apply(is_postcovid_seasonality)dfprophet['post_covid_seasonality'] = dfprophet['ds'].apply(is_postcovid_seasonality)#add pre-covid and post-covid seasonality to the model base_model.add_seasonality(name='pre_covid_seasonality', period=7, fourier_order=3, condition_name='pre_covid_seasonality')base_model.add_seasonality(name='post_covid_seasonality', period=7, fourier_order=3, condition_name='post_covid_seasonality')
This is probably the best approach to deal with COVID impact in many business scenarios. We will add external regressors such as mobility data here to help the model pick up COVID’s impact. Since those external regressors will greatly absorb COVID impact, it will minimize the impact of COVID on seasonality. Below are some public available indicators you could use. Consumer confidence index (CCI), Google mobility, COVID cases prediction
#add a list of external regressorsregressors = ['CCI','google mobility','covid cases']for regressor in regressors:            base_model = base_model.add_regressor(regressor,                                                  prior_scale=1,                                                   standardize='auto',   mode='multiplicative')
One thing to be aware of is that some of these indicators may only be temporarily available such as google mobility data, also it doesn’t provide forecast data. To leverage it, we may need to do our own predictions of these external regressors. Still, including these indicators will greatly improve model performance. And we may just need to revisit the model when COVID impact subsides and we have more stable time series data.
This second topic also gets lots of questions and discussions. You may know that Prophet has two modes for seasonality and regressors, one is the additive mode (default), another is the multiplicative mode.With additive mode, seasonality/regressor is constant year over year; While, with multiplicative mode, the magnitude of seasonality/regressor is changing along with trend (see below chart).
The formula of Prophet could be written as:
df['yhat'] = df['trend'] * (1 + df['multiplicative_terms']) + df['additive_terms']
Since both seasonality and regressors have two modes, there are four combinations:
yhat(t) = trend(t) + seasonality(t) + beta * regressor(t)meaning a unit increase in regressor will produce a beta increase in yhat.
yhat(t) = trend(t) * (1 + seasonality(t) + beta * regressor(t))meaning a unit increase in regressor will produce a beta * trend increase in yhat.
yhat(t) = trend(t) * (1 + beta * regressor) + seasonality(t)meaning a unit increase in regressor will produce a beta * trend increase in yhat.
yhat(t) = trend(t) * (1 + beta * seasonality(t)) + regressor(t)meaning a unit increase in regressor will produce a unit increase in yhat.
So before choosing additive or multiplicative mode, one question to ask — will one unit change in regressor always have a constant effect? or It will depend on the base of the trend. Normally, it makes more sense to use multiplicative mode, as we would imagine the magnitude of seasonality is aligned with the magnitude of trend. See this for more information.
For example, a degree increase in temperature during summer must trigger more ice cream sales than a degree increase in temperature during winter. So under this situation, a multiplicative model makes much more sense.
However, the rule is a little different because of COVID...
Due to COVID impact, the 2020 number could be very low, so if we use multiplicative seasonality, the model will be misled by the low baseline and thinks that there is a big decrease in the magnitude of seasonality from 2019 to 2020, and then predicts that magnitude of 2021’s seasonality is even smaller than 2020’s. The model predictions could be seriously underestimated by using multiplicative.
With the above said, it makes more sense to use additive seasonality during the pandemic so that it’s not biased by 2020’s abnormal baseline, especially when COVID greatly impacts your time series data. When we come out of the pandemic, we may need to revisit the model hyperparameters.
Now, you may want to aggregate your data to a weekly or monthly level to offset the fluctuation introduced by COVID. There are many caveats when dealing with non-daily data in Prophet. Below I listed two fundamental ones:
Although you have monthly or weekly aggregated data, the Prophet model doesn’t know this and is still fitting a continuous model and treating weekly or monthly data points as if they were single datapoint just on those particular days. Thus, those holidays that don’t fall on the particular dates used in the data will be ignored!!!
For example, if you have monthly data, and each month is denoted by the first day of that month (01/01/2021, 02/01/2021, 03/01/2021), then if you want to include Christmas as a holiday event, you need to set Christmas day as of 12/01/2021 rather than 12/25/2021. To deal with this potential issue, we just need to move the holiday to the date that represents the week or month (see below codes).
#Convert date to first day of monthdf['First_day_of_the_month'] =df['date'].to_numpy().astype('datetime64[M]')#Convert date to first day of week (sunday)df["First_day_of_the_week"] = df['Date'].apply(lambda x: (x - timedelta(days=x.dayofweek + 1)), axis = 1)
In Prophet, you could set priors for trend, seasonality, holidays, and extra regressors. However, choosing the correct range of priors is not an easy task.
For example, if the change in data is already captured by extra regressors, then the holiday effect could be simply wrong, e.g. if we use marketing spending as extra regressors to predict demand, we may surprisingly see Black Friday negatively impacting demand. This is because marketing spending already explained the majority variations of demand, which then led to an incorrect (counter-intuitive) effect of holidays.
Another example is that if you increase holidays prior and decrease seasonality prior, then the model would prefer to use the holiday to explain the variations in data rather than seasonality. If you indeed want to use seasonality, you could set holidays prior to a very small number such as 0.00001 and set seasonality prior to a much large number such as 0.5.
Of course, you could use a grid search method (see below code block) to tune hyperparameters if you aim for short-term accuracy. But in case you also care about explainability, you need to think about which is more impactful to the specific variations or changes, seasonality, holidays, or extra regressors, and relatively increase the prior for that component.
# Pythonimport itertoolsparam_grid = {      'changepoint_prior_scale': [0.001, 0.01, 0.1, 0.5],    'seasonality_prior_scale': [0.01, 0.1, 1.0, 10.0],}# Generate all combinations of parametersall_params = [dict(zip(param_grid.keys(), v)) for v in itertools.product(*param_grid.values())]rmses = []  # Store the RMSEs for each params here# Use cross validation to evaluate all parametersfor params in all_params:    m = Prophet(**params).fit(df)  # Fit model with given params    df_cv = cross_validation(m, cutoffs=cutoffs, horizon='30 days', parallel="processes")    df_p = performance_metrics(df_cv, rolling_window=1)    rmses.append(df_p['rmse'].values[0])# Find the best parameterstuning_results = pd.DataFrame(all_params)tuning_results['rmse'] = rmsesprint(tuning_results)
I hope this article could help you gain insights on how to deal with COVID impact, how to choose additive vs multiplicative mode, and how to deal with non-daily data. These three topics have become particularly important and even a must-know when using Prophet after COVID-19.
I’m also very interested in how you treat COVID’s impact in your model, feel free to comment below.
Stay positive, thanks for reading!
Taylor SJ, Letham B. 2017. Forecasting at scale. PeerJ Preprints 5:e3190v2 https://doi.org/10.7287/peerj.preprints.3190v2
Taylor SJ, Letham B. 2017. Forecasting at scale. PeerJ Preprints 5:e3190v2 https://doi.org/10.7287/peerj.preprints.3190v2