A filter started from zero assumes no media ran before the series began, so
the first periods of a long-carryover channel are understated: at
decay = 0.85 a constant spend reaches only 15% of its steady-state adstock
in the first period. This function returns the state the filter would hold
had the average spend of the first periods been running forever, which is
the usual remedy when real pre-period spend is not available.
Arguments
- x
Numeric vector of media spend (or impressions, or GRPs) in time order.
xmust already be sorted by time and evenly spaced; the function has no index argument and cannot check this for you. Usestep_adstock()if you want the time index validated.- decay
Geometric decay coefficient in
[0, 1]. Seedecay_from_half_life()for the half-life vocabulary.- max_lag
Number of periods the kernel spans. The default
Infuses the infinite (recursive) form; a finite value truncates the kernel. See Details.- periods
Number of leading periods to average. Defaults to the geometric kernel's 90% effective window, capped at the series length, since that is how far back the start of the series "remembers".
- by
Optional grouping vector, or data frame of grouping vectors, the same length as
x. Adstock is applied independently within each group, which is what geo-level and panel models need. Never rely ondplyr::group_by()for this: grouping metadata does not reliably survive into every context where this function is called.
Value
A state in the form adstock_geometric(), adstock_filter() and
friends accept: a single raw accumulator for max_lag = Inf, the
max_lag - 1 preceding values for a finite kernel, or a named list of
these when by is supplied.
Details
For the recursive kernel the raw accumulator under constant spend \(m\) is \(m / (1 - \theta)\); for a finite kernel the preceding values are all \(m\). Either way a series that really was constant at \(m\) adstocks to \(m\) (normalised) from its first period.
The seed uses the series' own early media, never the KPI, so it does not
leak outcome information into a cross-validation. It does assume the
pre-period looked like the first few observed periods; when you have the
real pre-period spend, pass adstock_state() of it instead.
See also
adstock_state(), adstock_geometric(), tune_carryover(),
which accepts warm_start = TRUE.
Examples
x <- rep(100, 10)
round(adstock_geometric(x, decay = 0.85), 1) # cold start
#> [1] 15.0 27.8 38.6 47.8 55.6 62.3 67.9 72.8 76.8 80.3
s <- adstock_steady_state(x, decay = 0.85)
round(adstock_geometric(x, decay = 0.85, state = s), 1) # no burn-in
#> [1] 100 100 100 100 100 100 100 100 100 100
# Finite kernels and grouped series work the same way
adstock_steady_state(c(10, 20, 30, 40), decay = 0.5, max_lag = 3)
#> [1] 25 25
adstock_steady_state(c(10, 20, 100, 200), decay = 0.5,
by = c("a", "a", "b", "b"), periods = 2)
#> $a
#> [1] 30
#>
#> $b
#> [1] 300
#>