3
$\begingroup$

I've got a sales forecasting model using the fbprophet library.

The model is additive: calculates a base trend and then adds modifiers for each component/feature, like weekly seasonality, yearly seasonality and temperature.

Normal example:

sales = trend + weather + weekly + yearly
sales = 1518 + (1518 * 0.02) + (1518 * -0.624) + (1518 * 0.319)
sales = 1518 + 31 - 947 + 484
sales = 1086

I want to identify the effects on the sales prediction. Here I can easily see that the weekly seasonality (eg. a Monday) has a negative effect of 62% on the sales, resulting in a £947 reduction.

My problem: Sometimes the model calculates the trend as far higher than the median sales value, and therefore the weekly seasonality ranges between -50% and -80%, instead of being standardised with a mean of zero. This prevents understanding the output for a single prediction, and requires you to compare the component modifier with the other days of the week to see the relative change.

Example:

The model output shows the base trend is unusually high compared to actual sales (y). Trend is higher than Y actual sales

Consequently, this means the weekly seasonality weekly_pct is very low and isn't intuitive when looking at a single prediction. The other components look fine in this case.

Weekly seasonality is non-standard

Solution idea:

One naïve solution is to:

  1. Adjust the trend to be the median actual sales (y).
diff = old_trend - y_median
diff = 3800 - 1200 = 2600
  1. Add this difference to the weekly seasonality coefficient for this day (eg. Monday was -82% (-£3116) and Saturday was -47% (-£1786) in this case)
monday = old_monday + diff
monday = -3116 + 2600 = -£516
saturday = old_saturday + diff
saturday = -1786 + 2600 = £814
  1. Re-calculate the percentage modifiers using the new value of trend (y_median).

All we've done here is reduced one component (the trend), and added it to a different component (weekly seasonality), and kept the absolute values of the other components.

The problem with this solution is that we're only standardising the weekly seasonality. This wouldn't work if two or more of the components needed standardising. Am I missing a clever statistics trick here?

$\endgroup$
0
$\begingroup$

After discussing with Ben Letham, one of the Facebook Prophet library creators, we've found a suitable solution to the standardisation problem.

Relative comparisons remain valid: If we subtract C from each weekly(t), then the difference between weekly(t) and weekly(t+1) (e.g., Monday vs. Sunday) will be the same no matter what C is. So suppose we have weekly seasonality of [1, 2, 3, 4, 5, 3, 1] for Sun-Sat. You can decide what day is the reference day; let's say Monday, which has weekly=2. Then you can subtract 2 from weekly and add it to trend, and the model predictions remain unchanged but now "trend" is the Monday value, and weekly is the incremental increase/decrease from dow relative to Monday.

My approach was to force the trend to look like the median y value, whereas this method looks at each component's central tendency and uses that to modify the trend.

The maths on this checks out when I apply this method across multiple components too.

Before:

Trend = 850, Weekly median = 360, Weather median = -210
Weekly high = 850, Weekly low = 210, Weather high = -65, Weather low = -360

After (both components more standardised, same model output):

Trend = 1000, Weekly median = 0, Weather median = 0
Weekly high = 490, Weekly low = -150, Weather high = 145, Weather low = -150

Here's how you can implement this feature (in Python):

def standardise_prediction_output(prediction: dict, prophet_model, central_measure="mean"):
    """
    Get the central tendency measure (eg. mean) for each component.
    Subtract it from the component value of this prediction (to standardise around 0).
    Add it to the trend of this prediction (to ensure additive model property holds).
    Overwrite the original non-standardised prediction.
    """
    # Below should be done on the whole prediction dataset (not just a single date) 
    standardisable_components = {c: prediction[c].mean() for c in ["weekly", "yearly"]}

    prediction["trend_standardised"] = prediction["trend"]

    for component, ctm_delta in standardisable_components.items():
        component_mode = get_component_mode(prophet_model, component)

        absolute_ctm_delta = (
            ctm_delta * prediction["trend"]
            if component_mode == "multiplicative"
            else ctm_delta
        )

        prediction["trend_standardised"] += absolute_ctm_delta
        prediction[component] -= ctm_delta

    return prediction

def get_component_mode(prophet_model, component):
    if component in prophet_model.component_modes["multiplicative"]:
        return "multiplicative"
    return "additive"

Example:

Before: Unstandardised boxplot

After standardising weekly, yearly, temperatureMax features: Standardised features

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.