目录(文末附数据和代码)
1. 简介
如果你有做过FB广告投放,对 ROI 和 ROAS 这两个词一定不陌生,因为招聘的时候肯定会问你两个问题:
你接触过多大的盘子?即花过多少预算。
你的ROI一般是多少?
广告客户使用营销组合模型(MMM)来衡量其广告媒介投入如何促进销售增加,从而优化未来的预算分配。广告投入回报率(ROAS)和边际投入回报率(mROAS)是要关注的关键指标。较高的投入回报率表示渠道有效,而较高的边际投入回报率则意味着根据当前投入水平,增加渠道投入将产生高回报收益。
广告投入回报率 Return on Ad Spending,指广告上每投入一元所获得的营收占比。比如这个广告花了 1000 刀,给带来了 5000 刀的收入,则 ROAS=5,意思是在FB上花 1 刀就能带来 5 刀的收入。这个指标主要是测量广告花费的,算是战术层级。而 ROI 不仅包含广告费,还考虑了成本等因素,属于战略层级。
投资报酬率 Return on Investment,指每投资一元所获得的毛利占比。用谷歌官方的例子:
某产品成本 100 刀,卖 200 刀,你卖了 6 件,销售收入 1200 刀,广告花费是 200 刀。赚的利润:1200-6*100-200= 400 刀,成本投入:6*100+200= 800 刀,ROI=50%,即投资回报率是50%。
2. 模型原理
传统的营销归因,有几种规则,比如按照实际客户旅程的多个接触点线性计算贡献值。一位客户在电视上看到一个产品>点击了一个展示广告>点击了一个付费搜索广告>购买了30美元。在这种情况下,有3个接触点为此次转化做出了贡献。
还有只追踪可接触的数据,比如上一个数字接触点为SEM,通常只能跟踪上一个数字接触点。在这种情况下,SEM将会获得所有贡献。
模型原理:
使用媒体渠道展示、投入数据和控制变量,以先验的系数拟合回归模型来预测销售额;
将销售额分解为每个媒体渠道的贡献。渠道贡献是通过比较移除该渠道后的原始销售额和预期销售额得出的,原理类比于shapley value方法;
使用渠道贡献和投入来计算广告指标:ROAS和mROAS。
缺陷:对于营销组合模型(MMM),离线渠道的效果很难追踪。例如,客户看到电视广告,但在商店购物。且媒体渠道的效果是交织在一起的,交互效应无法测量。因此,一个好的归因模型应该考虑所有导致转化的相关变量。
2.1 乘法营销组合模型(MMM)
由于媒体渠道都以交互形式投放,因此采用了乘法模型结构:
取双方的对数,我们得到对数线性模型:
系数约束
媒体系数为正。
折扣,宏观经济,活动/零售节假日等控制变量将对销售额产生积极影响,其系数也应为正。
问题1:为什么不是纯粹的回归问题?(有sklearn,为什么要用复杂的 STAN / MCMC ?)
因为媒体变量的系数存在约束。媒体系数表明渠道对销售额的贡献程度应该为正。系数为负数意味着在此渠道中投放广告将持续损害销售额,在广告上的投入越多,损失的销售额就越多。这是违反常识的。但是,如果用线性回归,则不可避免地会得到负系数或零系数。线性回归可朝着最小误差优化,它不关注系数方向。
问题2:STAN / MCMC采样器如何解决这个问题?
MMM是贝叶斯回归问题,我们对参数有先验知识。在下面的“3 模型实现”部分中,指定了参数的先验分布,并将其约束为正值/负值/在一定范围内。STAN 或 MCMC 采样器的作用是,从该样本空间中抽取样本多次(例如 1000 次迭代),并提出 1000 个可能的参数集合,将其平均值用作估计的参数值。
2.2 Adstock模型
广告理论是基于这样的假设,即电视广告的曝光会在消费者心中建立意识,从而影响他们的购买决策。每次新的广告曝光都会建立意识,如果最近有曝光,则该意识会更高,如果没有最近曝光,则该意识会更低。在没有进一步暴露的情况下,广告最终会衰减到可以忽略的水平。衡量和确定媒体渠道,尤其是在建立营销组合模型时,该假设是确定营销有效性的关键组成部分。
衰减或滞后效应:媒体对销售额的影响可能会滞后于初始曝光并且效果会持续数周。
L:媒体效果的时间长度
P:媒体效果的峰值,它比第一次曝光时间滞后几周
D:媒体的衰减率
累积到 L 周广告效果为当前第 L 周和之前 L-1 周的平均值:
示例如下:
不同的衰减率 D :通过拟合结果发现衰减率越高,效果越分散。
不同的时间长度 L: 时间长度影响相对较小。在模型训练中,长度参数可以固定为8周,也可以是直到媒体效果消失的一段时间范围。
import numpy as np
import pandas as pd
def apply_adstock(x, L, P, D):
‘’’
params:
x: original media variable, array
L: length
P: peak, delay in effect
D: decay, retain rate
returns:
array, adstocked media variable
‘’’
x = np.append(np.zeros(L-1), x)
weights = np.zeros(L)
for l in range(L):
weight = D**((l-P)**2)
weights[L-1-l] = weight
adstocked_x = []
for i in range(L-1, len(x)):
x_array = x[i-L+1:i+1]
xi = sum(x_array * weights)/sum(weights)
adstocked_x.append(xi)
adstocked_x = np.array(adstocked_x)
return adstocked_x
2.3 边际收益递减模型
广告数量的增加会增加广告所覆盖的受众群体的百分比,从而增加需求,但是广告曝光率的线性增加不会对需求产生类似的线性影响。通常,广告的每个增量都会对需求增长产生逐渐减小的影响。这是广告边际收益递减效应。由上面的衰减效应模型得知,累积广告收益会先增加后消失。但是对于边际广告收益,在某个饱和点之后,增加投入将产生递减的边际收益,随着广告投入不断超支,渠道将失去效率。递减收益由希尔函数建模:
下图展示了Hill函数随 K 和 S 的变化而变化:
def hill_transform(x, ec, slope):
return 1 / (1 + (x / ec)**(-slope))
3. 模型实现
3.1 数据集
四年(209周)的每周销售额,媒体展示和投入记录。
(1) 媒体变量
(2) 控制变量
宏观经济(前缀=“ me _”):CPI,汽油价格。
降价(prefix ='mrkdn_'):降价/折扣。
商店数量('st_ct')
零售节假日(prefix ='hldy_'):onehot编码
季节性(prefix ='seas_'):月份,11月和12月分为几周。onehot编码
(3) 销售变量(“ sales”)
df = pd.read_csv(‘data.csv’)
# 1. media variables# media impressionmdip_cols=[col for col in df.columns if ‘mdip_’ in col]# media spendingmdsp_cols=[col for col in df.columns if ‘mdsp_’ in col]
# 2. control variables# macro economics variablesme_cols = [col for col in df.columns if ‘me_’ in col]# store count variablesst_cols = [‘st_ct’]# markdown/discount variablesmrkdn_cols = [col for col in df.columns if ‘mrkdn_’ in col]# holiday variableshldy_cols = [col for col in df.columns if ‘hldy_’ in col]# seasonality variablesseas_cols = [col for col in df.columns if ‘seas_’ in col]base_vars = me_cols+st_cols+mrkdn_cols+va_cols+hldy_cols+seas_cols
# 3. sales variablessales_cols =[‘sales’]
3.2 模型框架
训练了三个模型并构建模型融合:
3.3 控制模型
目标:将预测的基准销售量(X_ctrl)为营销组合模型(MMM)的输入变量,这表示没有任何营销活动的基准销售额趋势。
X1:与销售额呈正相关的控制变量,包括宏观经济,门店数量,减价,节假日
X2:可能对销售额产生正面或负面影响的控制变量:季节性
y:sales
所有变量数据都标准化,参数先验分布如下:
import pystan
import os
os.environ[‘CC’] = ‘gcc-10’
os.environ[‘CXX’] = ‘g++-10’
# helper functions
def apply_mean_center(x):
mu = np.mean(x)
xm = x/mu
return xm, mu
def mean_center_trandform(df, cols):
df_new = pd.DataFrame()
sc = {}
for col in cols:
x = df[col].values
df_new[col], mu = apply_mean_center(x)
sc[col] = mu
return df_new, sc
def mean_log1p_trandform(df, cols):
df_new = pd.DataFrame()
sc = {}
for col in cols:
x = df[col].values
xm, mu = apply_mean_center(x)
sc[col] = mu
df_new[col] = np.log1p(xm)
return df_new, sc
# mean-centralize: sales, numeric base_vars
df_ctrl, sc_ctrl = mean_center_trandform(df, [‘sales’]+me_cols+st_cols+mrkdn_cols)
df_ctrl = pd.concat([df_ctrl, df[hldy_cols+seas_cols]], axis=1)
# variables positively related to sales: macro economy, store count, markdown, holiday
pos_vars = [col for col in base_vars if col not in seas_cols]
X1 = df_ctrl[pos_vars].values
# variables may have either positive or negtive impact on sales: seasonality
pn_vars = seas_cols
X2 = df_ctrl[pn_vars].values
ctrl_data = {
’N’: len(df_ctrl),
‘K1’: len(pos_vars),
‘K2’: len(pn_vars),
‘X1’: X1,
‘X2’: X2,
‘y’: df_ctrl[‘sales’].values,
‘max_intercept’: min(df_ctrl[‘sales’])
}
ctrl_code1 = ‘’’
data {
int N; // number of observations
int K1; // number of positive predictors
int K2; // number of positive/negative predictors
real max_intercept; // restrict the intercept to be less than the minimum y
matrix[N, K1] X1;
matrix[N, K2] X2;
vector[N] y;
}
parameters {
vector<lower=0>[K1] beta1; // regression coefficients for X1 (positive)
vector[K2] beta2; // regression coefficients for X2
real<lower=0, upper=max_intercept> alpha; // intercept
real<lower=0> noise_var; // residual variance
}
model {
// Define the priors
beta1 ~ normal(0, 1);
beta2 ~ normal(0, 1);
noise_var ~ inv_gamma(0.05, 0.05 * 0.01);
// The likelihood
y ~ normal(X1*beta1 + X2*beta2 + alpha, sqrt(noise_var));
}
‘’’
sm1 = pystan.StanModel(model_code=ctrl_code1, verbose=True)
fit1 = sm1.sampling(data=ctrl_data, iter=2000, chains=4)
fit1_result = fit1.extract()
控制模型的平均绝对百分比误差 MAPE 为8.63%,从拟合结果中提取控制模型的参数,并预测基准销售额。
3.4 营销组合模型
目标:
找到适合各媒体渠道的 Adstock 模型参数
将销售额分解为媒体渠道的贡献和非营销的贡献
L:媒体影响的时间长度
P:媒体影响的峰值
D:媒体影响的衰减
X:Adstock的媒体展示效果和基准销售额
y:sales