Diminishing returns to media. Adstock says when money works; saturation says how hard it works at the margin. Four curves are provided; they differ in whether the response is bounded, and in whether they allow an S-shape.
Usage
saturate_hill(x, half_max, shape = 1)
saturate_exponential(x, rate)
saturate_michaelis_menten(x, vmax = 1, km)
saturate_power(x, exponent)
saturate(x, type = c("hill", "exponential", "michaelis_menten", "power"), ...)Arguments
- x
Numeric vector of media, normally already adstocked. Values must be non-negative: all four curves are defined for spend, not for arbitrary reals.
- half_max
The value of
xat which the response reaches half its ceiling. Interpretable in the units ofx, which is why it is used here in preference to the equivalent unnamed scale parameter.- shape
Hill exponent, positive.
shape = 1gives a concave curve with diminishing returns everywhere.shape > 1gives an S-shape with an initial convex region, the usual representation of a threshold effect.- rate
Exponential rate parameter, positive. Larger values saturate sooner.
- vmax
Michaelis–Menten asymptote: the response as
xgrows without bound.- km
Michaelis–Menten constant: the value of
xat which the response reaches half ofvmax.- exponent
Power exponent in
(0, 1].exponent = 1is the identity (no saturation); smaller values bend the curve harder.- type
Which curve
saturate()should dispatch to.- ...
Passed to the individual curve function.
Details
saturate_hill() and saturate_exponential() are bounded on [0, 1), so
the fitted coefficient carries the channel's ceiling.
saturate_michaelis_menten() is bounded by vmax. All three approach their
ceiling smoothly at arbitrarily large x, including Inf, rather than
overflowing.
saturate_power() is unbounded but concave: response keeps growing, just
ever more slowly. Unboundedness is not automatically wrong, but it does mean
the model will happily extrapolate a return on a spend level never observed.
saturate_hill(x, half_max, shape = 1) and
saturate_michaelis_menten(x, vmax = 1, km = half_max) are the same
function. Both are provided because the two literatures name it differently
and practitioners arrive expecting one or the other.
Transform order
Saturation is applied after adstock, not before. Saturating first would
cap each period's spend in isolation and then let carryover accumulate the
capped values past the cap, which defeats the point of having a ceiling. Use
media_transform() to get the order right without having to remember it.
References
Hill, A. V. (1910). The possible effects of the aggregation of the molecules of haemoglobin on its dissociation curves. The Journal of Physiology, 40(Suppl), iv–vii.
Examples
spend <- c(0, 25, 50, 100, 200, 400)
# Concave: diminishing returns from the first pound
round(saturate_hill(spend, half_max = 100), 3)
#> [1] 0.000 0.200 0.333 0.500 0.667 0.800
# S-shaped: a threshold below which media barely registers
round(saturate_hill(spend, half_max = 100, shape = 3), 3)
#> [1] 0.000 0.015 0.111 0.500 0.889 0.985
# The four curves side by side. Hill, exponential and Michaelis-Menten are
# bounded; power keeps growing, just ever more slowly.
round(rbind(
hill = saturate_hill(spend, half_max = 100),
exponential = saturate_exponential(spend, rate = 0.007),
michaelis = saturate_michaelis_menten(spend, vmax = 1, km = 100),
power = saturate_power(spend, exponent = 0.5)
), 3)
#> [,1] [,2] [,3] [,4] [,5] [,6]
#> hill 0 0.200 0.333 0.500 0.667 0.800
#> exponential 0 0.161 0.295 0.503 0.753 0.939
#> michaelis 0 0.200 0.333 0.500 0.667 0.800
#> power 0 5.000 7.071 10.000 14.142 20.000
# Bounded means bounded: no overflow, even at the extremes
saturate_hill(c(1e300, Inf), half_max = 100, shape = 3)
#> [1] 1 1
# The dispatcher, for programmatic use
round(saturate(spend, type = "hill", half_max = 100), 3)
#> [1] 0.000 0.200 0.333 0.500 0.667 0.800