Choosing between a rolling window and a state space filter

Today let's understand time-varying parameters clearly, using a subscription business's weekly discount offer and new signups as the running example throughout.

Say you run a subscription business. Every week you set a discount for that week, somewhere between \$2 and \$20, and every week you count how many new people sign up; the code below calls that weekly count subscribers. Here are those weekly signup counts for the first 24 weeks.

RInteractive R
# Simulate 24 weeks of a subscription business's discount offer and new signups week <- 1:24 set.seed(40) discount <- round(runif(24, 2, 20), 1) beta_true <- rep(c(3, 6), each = 12) alpha_true <- 20 set.seed(41) noise <- rnorm(24, 0, 4) subscribers <- round(alpha_true + beta_true * discount + noise) subscribers #> [1] 60 74 67 37 40 53 39 51 50 42 26 62 45 56 136 119 77 141 43 #> [20] 104 79 73 39 94

  

Here is that same count, plotted week by week.

The count moves around a lot from week to week, from as low as 26 up to 141, with no obvious steady climb. Somewhere in there is a real relationship between the discount you offer and how many people sign up, but is that relationship the same one number every week, or does it change? That is the question this lesson answers.

Why a fixed coefficient can be the wrong assumption

Because this week's discount and signups are simulated rather than pulled from a live store, you get to know the true relationship behind them, something you would never know with real data.

Twenty people a week sign up organically, even at zero discount. But the signups-per-discount-dollar effect is not fixed. For the first 12 weeks, each extra dollar of discount buys 3 extra signups. Then the business simplifies its checkout flow, and from week 13 on, each extra dollar buys 6 extra signups instead, twice as much as before.

An ordinary regression cannot see any of that. It only ever returns one slope, fit across all 24 weeks at once.

RInteractive R
# Fit one OLS regression across all 24 weeks, and check it against two individual weeks df <- data.frame(week = week, discount = discount, subscribers = subscribers) ols_fit <- lm(subscribers ~ discount, data = df) round(coef(ols_fit), 2) #> (Intercept) discount #> 18.46 4.84 fitted_week3 <- unname(predict(ols_fit, newdata = data.frame(discount = discount[3]))) fitted_week18 <- unname(predict(ols_fit, newdata = data.frame(discount = discount[18]))) round(c(week3_discount = discount[3], week3_fitted = fitted_week3, week18_discount = discount[18], week18_fitted = fitted_week18), 1) #> week3_discount week3_fitted week18_discount week18_fitted #> 14.4 88.2 19.6 113.4

  

The fitted slope, 4.84, is a compromise between the early weeks' effect of 3 and the later weeks' effect of 6, because one straight line has no way to be both at once. Look at what that compromise costs on real weeks. Week 3's discount is \$14.4, and the true effect there is still 3, so the true average signup count is \(20 + 3 \times 14.4 = 63.2\). The OLS line fits 88.2 instead, a big overshoot. Week 18's discount is \$19.6, and the true effect there is already 6, so the true average is \(20 + 6 \times 19.6 = 137.6\). The OLS line fits only 113.4, a big undershoot in the other direction. One fixed slope cannot be right on both sides of a change it never sees.