The full paper can be found here: link
Table of Contents
— — —
To begin, media mix models (MMM) aim to uncover the causal effect of paid media on a metric of interest, typically sales. Historically, the problem has largely been modeled via linear regression and the causal impact has been derived using Rubin’s potential outcomes framework.
In simple (data science) terms, this translates to
Estimating casual impact from observational data has a number of issues i.e. “correlation doesn’t equal causation” for starters. And media mix models have a host of unique issues to take note of. An excellent review of these issues can be found here: Challenges And Opportunities In Media Mix Modeling
This paper focuses on two specific issues:
While also providing a Bayesian model, ROAS calculations and optimization methods.
Carryover effects, often called lagged effects, occur when media spend effects sales across a number of days. For example, if we spend $100 on display advertising today, we may not see the effects of this spend for several days. The adstock function attempts to parameterize this phenomenon and the paper takes two approaches to adstock modeling:
Geometric
Delayed Adstock
def geoDecay(alpha, L):
'''
weighted average with geometric decay
weight_T = alpha ^ T-1
returns: weights of length L to calculate weighted averages with.
'''
return alpha**(np.ones(L).cumsum()-1)
def delayed_adstock(alpha, theta, L):
'''
weighted average with dealyed adstock function
weight_T =
returns: weights of length L to calculate weighted averages with.
'''
return alpha**((np.ones(L).cumsum()-1)-theta)**2
def carryover(x, alpha, L, theta = None, func='geo'):
'''
1\. x is a vector of media spend going back L timeslots, so it should be len(x) == L
2\. Weights is a vector of length L showing how much previous time periods spend has on current period.
3\. L is max length of Lag.
returns transformed vector of spend
## update with numpy average
## np.average(x[:2], weights=[1,.9])
'''
transformed_x = []
if func=='geo':
weights = geoDecay(alpha, L)
elif func=='delayed':
weights = delayed_adstock(alpha, theta, L)
for t in range(x.shape[0]):
upper_window = t+1
lower_window = max(0,upper_window-L)
current_window_x = x[:upper_window]
t_in_window = len(current_window_x)
if t < L:
new_x = (current_window_x*np.flip(weights[:t_in_window], axis=0)).sum()
transformed_x.append(new_x/weights[:t_in_window].sum())
elif t >= L:
current_window_x = x[upper_window-L:upper_window]
ext_weights = np.flip(weights, axis=0)
new_x = (current_window_x*ext_weights).sum()
transformed_x.append(new_x/ext_weights.sum())
return np.array(transformed_x)
#causality #causal-inference #data-science #modeling #media-mix-modeling