FAIR is a forecasting tool developed to support decision making in a retail environment. It provides multi-step-ahead sales forecasts at the category-store level, which are based on an interpretable and transparent model. These aspects make it an objective tool with which different promotional strategies (scenarios) can be compared and insights can be generated. Additionally, the FAIRforecast package provides plotting functions that generate figures showing the relative strength of the interactions between categories and important promotional variables.

The main function of the package, FAIR_train, fits a FAIR model to the data as detailed by Gür Ali and Gürlek (2020). The model is fitted in three stages. First, FAIR orthogonalizes the sales and marketing data with respect to seasonality by fitting category-store-specific linear models. This stage helps FAIR to guard against regularization induced bias (Hahn et al., 2018). Next, the deseasonalized sales is explained by the deseasonalized marketing with Elastic net models (Zou & Hastie, 2005). This stage employs regularization and pools data to the category level1 to be able to deal with high dimensionality. It can account for the cross-category effects within a store. The last stage extrapolates the residuals to incorporate the random disturbances that cannot be explained with the seasonality or marketing activity.

Sample Data

The FAIRforecast package is accompanied by a simulated sample dataset to showcase its abilities. The data contains the weekly category-store-level sales of a retail chain. It provides the promotional activity variables as well as the seasonality and calendar variables. The following is the list of variables.

Model Fitting

The FAIRforecast package trains the forecasting model with its core function, FAIR_train. It requires the training data, the names of the different components of the data as strings, and the options. Type ?FAIR_train to see the details of the arguments. Note that marketing argument contains all the marketing columns in the data whereas cc_marketing contains only the ones that are assumed to have cross-category effects. The example below estimates within-category effects for “price”, “display”, “discount”, and “ad”; and also cross-category effects for “price”, “display”, and “discount”.

library(FAIRforecast)
data_ret <- sample_data()
train_data <- data_ret[data_ret$week < 184, ]
my_model <- FAIR_train(train_data = train_data,
                       store = "store_n",
                       category = "category",
                       time_id = "week",
                       seasonality = c("thanksgiving", "christmas", 
                                       "other_holidays", "season", "first",
                                       "fifteenth", "month"),
                       marketing = c("price", "display", "discount", "ad"),
                       cc_marketing = c("price", "display", "discount"),
                       sales = "dollar_sales",
                       lag = 2,
                       alpha = c(0.1, 0.5, 0.9),
                       horizon = 13)
names(my_model)
#> [1] "fit"      "models"   "pars"     "lag_data"

It returns an R list with four elements:

Below is the head of fit data.frame. Base_Sales column shows the forecasted sales as a result of the “typical” marketing activity for the given seasonality. Marketing_deviation_multiplier is the multiplier that modifies the forecast by accounting the marketing scenario deviation from the established pattern. Similarly, Disturbance_multiplier is the multiplier that incorporates the random disturbances which cannot be captured by the seasonality or marketing variables. Sales_estimate column shows the final prediction calculated by multiplying the all2. The first lag estimates -in this case, 2- do not have the multipliers since Stage 2 uses the lagged marketing information.

head(my_model$fit)
#>   store_n category week Base_Sales Marketing_deviation_multiplier
#> 1       1     beer    1  10202.560                             NA
#> 2       1     beer    2   8212.279                             NA
#> 3       1     beer    3   8008.027                       1.003037
#> 4       1     beer    4   8241.773                       1.007518
#> 5       1     beer    5   8111.515                       1.005672
#> 6       1     beer    6  10011.854                       1.001056
#>   Disturbance_multiplier Sales_estimate
#> 1                     NA      10201.560
#> 2                     NA       8211.279
#> 3              0.9917328       7964.943
#> 4              0.9794253       8131.885
#> 5              1.1251614       9177.527
#> 6              1.0754403      10777.526

The second element in the output of FAIR_train is a list of the models. The first in the list is store-category-specific Stage 1 models for each marketing variable and sales. Note that these are multiplicative OLS models and the dependent variable is the logarithmic transformation of the respective sales or marketing variable. Here, for example, is the OLS model that explains the seasonality in the discounts for the beer category in Store 1 on the logarithmic scale.

my_model$models$Stage1$`1.beer`$discount
#> 
#> Call:
#> stats::lm(formula = myformula, data = train, model = F)
#> 
#> Coefficients:
#>    (Intercept)    thanksgiving       christmas  other_holidays    seasonspring  
#>      3.851e-02       1.838e-04       8.570e-05       6.866e-05       1.756e-04  
#>   seasonsummer    seasonwinter           first       fifteenth          month2  
#>      3.919e-04       5.747e-05       1.031e-04       6.162e-05      -2.100e-05  
#>         month3          month4          month5          month6          month7  
#>     -2.751e-04      -3.665e-04      -5.448e-04      -3.271e-04      -6.835e-04  
#>         month8          month9         month10         month11         month12  
#>     -4.856e-04      -1.269e-04      -2.302e-04      -1.435e-04      -3.448e-04  
#>           week  
#>     -1.167e-07

The next in the list is category-specific Stage 2 Elastic net models. One can obtain the Elastic net coefficients for the beer category with the following code. Note that we have lags of the marketing variables and price, display, and discount of the other categories.

coef(my_model$models$Stage2$beer)
#> 40 x 1 sparse Matrix of class "dgCMatrix"
#>                             s0
#> (Intercept)       -0.001015938
#> price              .          
#> display           -0.122178079
#> discount           .          
#> ad                 .          
#> price_lag1         .          
#> price_lag2         .          
#> display_lag1       .          
#> display_lag2       .          
#> discount_lag1      .          
#> discount_lag2      .          
#> ad_lag1           -0.032824503
#> ad_lag2            0.014401934
#> blades_price       .          
#> carbbev_price      0.043591816
#> cigets_price       .          
#> coffee_price      -0.093470777
#> coldcer_price     -0.111860909
#> deod_price         0.050689467
#> diapers_price      .          
#> factiss_price     -0.046079257
#> fzdinent_price     .          
#> blades_display     .          
#> carbbev_display   -0.413518772
#> cigets_display     .          
#> coffee_display    -0.278604229
#> coldcer_display   -1.194173778
#> deod_display      -5.633566483
#> diapers_display    .          
#> factiss_display    .          
#> fzdinent_display   .          
#> blades_discount    3.159688406
#> carbbev_discount   .          
#> cigets_discount    .          
#> coffee_discount    1.152174725
#> coldcer_discount   .          
#> deod_discount      .          
#> diapers_discount   .          
#> factiss_discount   .          
#> fzdinent_discount  0.553481583

Similarly, the Stage3 object in the models list is the output obtained from forecast::stlm() function in the 3rd Stage of FAIR.

Prediction

Having fitted our FAIR model, we can predict the sales for the following weeks. To do that, we use FAIR_predict function. It requires the object we obtained from FAIR_train and the new data. The new data should have the store and category identifiers, time id, seasonality and calendar variables, and marketing activity columns. The names should match the ones in train_data argument from the training. time_id column should contain weeks after the last week of train_data and cannot be out of the horizon specified for FAIR_train.

test_data <- data_ret[data_ret$week >= 184,
                         c("store_n", "category", "week",
                           c("thanksgiving", "christmas", "other_holidays",
                             "season", "first", "fifteenth", "month"),
                           c("price", "display", "discount", "ad"))]
predictions <- FAIR_predict(my_model, test_data)
head(predictions)
#>   store_n category week Base_Sales Marketing_deviation_multiplier
#> 1       1     beer  184   12892.75                      0.5670866
#> 2       1     beer  185   12572.09                      0.5674181
#> 3       1     beer  186   12939.05                      0.5670711
#> 4       1     beer  187   12962.27                      0.5670819
#> 5       1     beer  188   12757.40                      0.5674915
#> 6       1     beer  189   11862.13                      0.5700347
#>   Disturbance_multiplier Forecast
#> 1              1.2172934 8899.003
#> 2              0.9292128 6627.660
#> 3              0.9464542 6943.478
#> 4              1.0344155 7602.643
#> 5              1.0012123 7247.494
#> 6              1.1072632 7486.122

Output of FAIR_predict function is a data.frame with forecasts which has the same structure as fit object in the output list of FAIR_train.

Insight Generation

Beyond being a forecasting tool, FAIR provides tools to understand the mechanism that governs the sales. First, the user can input a set of scenarios to FAIR_predict function to guide a decision. FAIR uses modern machine learning techniques to make sure coefficient estimates are not biased. Therefore, different marketing strategies can be compared on the basis of the sales predicted by FAIR. Additionally, the user can gain insights with the plotting functions variable_importance and cross_category.

variable_importance plots the average absolute coefficients of the marketing variables scaled by the maximum effect. The most important variable has the value of 100 and the others have a value proportional to their average absolute coefficient. The default plot shows the top 10 variables and includes only the variables which belong to the focal category, i.e., the cross-category variables are excluded. The user can choose the categories over which the averages are calculated. All the categories are included by default.

variable_importance(object = my_model, n_vars = 10, categories = NULL,
                                only_own = T)

cross_category plots the matrix of the interaction strength. It averages the absolute value of the cross-category marketing variables of the x-axis category in the model for the y-axis category.

cross_category(my_model)

References

Gür Ali, Ö. and Gürlek, R. (2020) Automatic Interpretable Retail Forecasting (FAIR) with Promotional Scenarios. International Journal of Forecasting, forthcoming.

Hahn, P. R., Carvalho, C. M., Puelz, D., and He, J. (2018). Regularization and confounding in linear regression for treatment effect estimation. Bayesian Analysis, 13(1):163– 182.

Zou, H. and Hastie, T. (2005), Regularization and variable selection via the elastic net. Journal of the Royal Statistical Society: Series B (Statistical Methodology), 67: 301-320.


  1. The stores are pooled.↩︎

  2. It also subtracts 1 from the multiplication because of the way FAIR is formulated.↩︎