Lagged predictors, calendar effects and holiday dummies

Today let's add three more kinds of predictors to a regression: a value carried over from an earlier month, a fact taken straight off the calendar, and a flag for a month that only happened once.

Northgate Appliances is a home appliance retailer. Its owner keeps two numbers every month: total sales, and how much the store spent on ads that month. Below are all 36 months, January 2021 through December 2023.

Sales climb slowly overall, but three months tower over their neighbors, $68,721, $63,954 and $70,435, and every one of them is a November. And one month sits far below everything else, $15,324, deep in the spring of 2022. The slow climb alone cannot explain either pattern.

What a lagged predictor is, and building it with lag()

A lagged predictor carries a predictor's value from an earlier period onto the current row. In R, lag(x, 1) shifts a column x down by one position, so the value that used to sit on the row above now sits on the current row. Build that for Northgate's ad spend, then look at the first few months.

RInteractive R
# Build Northgate's 36 months of ad spend and sales, then lag ad spend by one month library(tsibble) library(fable) library(fabletools) library(dplyr) library(lubridate) set.seed(305) month <- yearmonth("2021 Jan") + 0:35 t <- seq_along(month) ad_spend <- round(5000 + rnorm(36, 0, 900)) ad_spend[15] <- 600 # March 2022: the store closed for a renovation and cut its ad spend ad_spend_lag1 <- lag(ad_spend, 1) weekdays_in <- function(the_month) { first_day <- as.Date(the_month) last_day <- first_day + lubridate::days_in_month(first_day) - 1 all_days <- seq(first_day, last_day, by = "day") sum(!weekdays(all_days) %in% c("Saturday", "Sunday")) } # sales already carries a few calendar and business effects this lesson uncovers later noise <- rnorm(36, 0, 1400) sales <- round( 8000 + 180 * t + 3.8 * ad_spend_lag1 + 950 * sapply(month, weekdays_in) + 14000 * as.integer(lubridate::month(as.Date(month)) == 11) - 32000 * as.integer(t == 15) + noise ) sales[1] <- round(8000 + 180 * t[1] + 950 * weekdays_in(month[1]) + noise[1]) shop <- tsibble(month = month, ad_spend = ad_spend, ad_spend_lag1 = ad_spend_lag1, sales = sales, index = month) shop |> as_tibble() |> select(month, ad_spend, ad_spend_lag1) |> head(4) #> # A tibble: 4 x 3 #> month ad_spend ad_spend_lag1 #> <mth> <dbl> <dbl> #> 1 2021 Jan 5759 NA #> 2 2021 Feb 4901 5759 #> 3 2021 Mar 4432 4901 #> 4 2021 Apr 4815 4432

  

Read February 2021's row: ad_spend is $4,901, February's own spend, while ad_spend_lag1 is $5,759, January's spend carried forward one month. Every row's ad_spend_lag1 is simply the row above's ad_spend. January 2021's ad_spend_lag1 is NA, because no month sits before it in the data.