作者:Ryan O'Sullivan
欢迎使用我的营销组合建模系列(MMM)的第3部分,这是一份动手指南,可帮助您掌握MMM。在整个系列中,我们将涵盖关键主题,例如模型培训,验证,校准和预算优化,所有这些都使用强大PYMC营销Python包。无论您是新手MMM还是希望提高自己的技能,该系列都将为您提供实用的工具和见解,以改善营销策略。
如果您错过了第2部分,请在此处查看:
在该系列的第三部分中,我们将通过涵盖以下领域来介绍如何从营销组合模型中获得业务价值:
完整的笔记本可以在此处找到:
这名著名的报价(来自约翰·瓦纳纳克(John Wanamaker),我想?!)既说明了营销的挑战和机会。尽管现代分析已经走了很长一段路,但挑战仍然相关:了解营销预算的哪些部分具有价值。
由于几个因素,营销渠道在其性能和投资回报率方面可能有很大差异:
这种可变性创造了一个机会,提出可以改变您的营销策略的关键问题:
有效的预算优化是现代营销策略的关键组成部分。通过利用MMM的产出,企业可以就何处分配资源以最大程度地影响做出明智的决定。MMM提供了有关各种渠道如何促进整体销售的见解,从而使我们能够确定改进和优化的机会。在以下各节中,我们将探讨如何将MMM输出转化为可行的预算分配策略。
响应曲线可以将MMM的产出转化为一种全面的形式,以表明销售对每个营销渠道的响应方式。
仅响应曲线非常强大,使我们能够运行什么情况。以上面的响应曲线为例,我们可以估计,随着我们花费更多,社会变化的销售贡献如何。我们还可以在视觉上查看降低的回报开始生效的地方。但是,如果我们想尝试回答更复杂的情况,什么场景(例如在固定的总体预算中优化渠道水平预算)怎么办?这是线性编程的来源,让我们在下一节中探讨这一点!
线性编程是一种优化方法,可用于在给定某些约束的情况下找到线性函数的最佳解决方案。这是运营研究领域的一种非常多功能的工具,但并没有得到应有的认可。它用于解决调度,运输和资源分配问题。我们将探索如何使用它来优化营销预算。
让我们尝试使用简单的预算优化问题理解线性编程:
所有约束的交集形成了一个可行的区域,这是满足给定约束的所有可能解决方案的集合。线性编程的目的是找到优化目标函数的可行区域内的点。
鉴于我们应用于每个营销渠道的饱和转换,优化渠道级别的预算实际上是一个非线性编程问题。顺序最小二乘编程(SLSQP)是一种用于解决非线性编程问题的算法。它允许平等和不平等约束,使其成为我们用例的明智选择。
Scipy对SLSQP的实现非常好:
下面的示例说明了我们如何使用它:
从scipy.ipimize进口最小化结果=最小化(
fun = Objective_function,#在此处定义您的ROI功能
x0 = initial_guess,#支出的初始猜测
bounds =界限,#频道级预算约束
约束=约束,#平等和不平等约束
方法='SLSQP'
)
打印(结果)
从头开始编写预算优化代码是一个复杂但非常有意义的练习。幸运的是,PYMC营销团队进行了繁重的工作,为运行预算优化方案提供了强大的框架。在下一节中,我们将探讨他们的包装如何简化预算分配过程并使分析师更容易访问。
现在,我们了解如何使用MMM的输出来优化预算,让我们看看使用上一篇文章中使用模型可以驾驶的价值!在本演练中,我们将介绍:
我们将重新使用第一篇文章中的数据生成过程。如果您想提醒数据生成过程,请查看第一篇文章,我们进行了详细的演练:
np.random.seed(10)#设置数据生成器的参数
start_date =“ 2021-01-01”
周期= 52 * 3
频道= [“电视”,“社交”,“搜索”]
adstock_alphass = [0.50,0.25,0.05]
饱和_lamdas = [1.5,2.5,3.5]
beta = [350,150,50]
speed_scalars = [10,15,20]
df = dg.data_generator(start_date,oferders,channels,spend_scalars,adstock_alphas,ataperation_lamdas,betas)
#使用最大销售价值的比例BETA-这与PYMC的拟合beta相当(PYMC确实具有和目标缩放使用Sklearn的MaxAbsScaler)
betas_scaled = [
((df [“ tv_sales”] / df [“ sales”]。max()) / df [“ tv_satatrated”])。
((DF [“ Social_Sales”] / df [“ sales”]。max()) / df [“ social_satured”])。
((df [“ search_sales”] / df [“ sales”]。max()) / df [“ search_satured”])。这是给出的
#计算贡献
贡献= np.Asarray([[
((df [“ tv_sales”]。sum() / df [“ sales”]。
((df [“ socile_sales”]。sum() / df [“ sales”]。
((df [“ search_sales”]。sum() / df [“ sales”]。
((df [“需求”]。sum() / df [“ sales”]。sum()),2
)))
df [[“日期”,“需求”,“ demand_proxy”,“ tv_spend_raw”,“ social_spend_raw”,“ search_spend_raw”,“ sales”]]
用户生成的图像
我们将以与上次相同的方式准备培训数据:
但是,由于本文的重点不是模型校准,因此我们将包括需求作为控制变量,而不是需求_proxy。这意味着该模型将经过很好的校准,尽管这是不切实际的,它将为我们提供一些好的结果,以说明如何优化预算。
#设置日期列
date_col =“日期”#设置成果列
y_col =“销售”
#设置营销变量
channel_cols = [“ tv_spend_raw”,
“ socile_spend_raw”,
“ search_spend_raw”]
#设置控制变量
control_cols = [“需求”]
#创建数组
x = df [[date_col] + channel_cols + control_cols]
y = df [y_col]
#设置测试(样本外)长度
test_len = 8
#创建火车和测试索引
train_idx =切片(0,len(df) - test_len)
out_of_time_idx = slice(len(df)-test_len,len(df))
mmm_default = mmm(
adstock =几何adstock(l_max = 8),
饱和=物流饱和(),
date_column = date_col,
channel_columns = channel_cols,
control_columns = control_cols,
)
fit_kwargs = {
“曲调”:1_000,
“链”:4,
“绘制”:1_000,
“ target_accept”:0.9,
}
mmm_default.fit(x [train_idx],y [train_idx],** fit_kwargs)
在进行优化之前,让我们检查模型良好。首先,我们检查真正的贡献:
频道= np.array([“电视”,“社交”,“搜索”,“需求”])true_contributions = pd.dataframe({'channels':channels,'贡献':贡献})
true_contributions = true_contributions.sort_values(by ='贡献',casting = false).Reset_index(drop = true)
true_contributions = true_contributions.style.bar(subset = ['贡献'],color ='lightblue')true_contributions
用户生成的图像
mmm_default.plot_waterfall_components_decomposition(figsize =(10,6));
用户生成的图像
有两种方法可以查看响应曲线PYMC营销包裹:
让我们从直接响应曲线开始。在直接响应曲线中,我们只需在每周的每周供款中创建一个每周支出的散点图。
下面我们绘制直接响应曲线:
无花果= mmm_default.plot_direct_contribution_curves(show_fit = true,xlim_max = 1.2)
[ax.set(xlabel =“ onding”)用于图轴中的ax];
成本份额响应曲线是比较渠道有效性的另一种方法。当= 1.0时,频道支出保持与培训数据相同的水平。当®= 1.2时,渠道支出增加了20%。
下面我们绘制成本份额响应曲线:
mmm_default.plot_channel_contributions_grid(start = 0,stop = 1.5,num = 12,figsize =(15,7));
我们还可以更改X轴以显示绝对的支出价值:
mmm_default.plot_channel_contributions_grid(start = 0,stop = 1.5,num = 12,absolute_xrange = true,figsize =(15,7));
响应曲线是帮助考虑在渠道级别计划未来营销预算的绝佳工具。接下来,让它们采取行动并运行一些预算优化方案!
首先,让我们设置一些参数:
我们将首先使用预算方案的所需长度来选择最新数据。
perc_change = 0.20
budgeb_len = 12
budged_idx = slice(len(df)-test_len,len(df))
erash_period = x [bucked_idx] [channel_cols]最近_period用户生成的图像
#设置总体预算约束(到最接近1k)
预算= round(erast_period.sum(axis = 0).sum() / budgeb_len,-3)
#记录当前预算按频道分配current_budget_split = round(erash_period.mean() / erase_period.mean()。sum(),2)
#设置频道级别约束
lower_bounds = round(erast_period.min(axis = 0) *(1- perc_change))
upper_bounds = round(最近_period.max(axis = 0) *(1 + perc_change))
budgeb_bounds = {
频道:[lower_bounds [channel],upper_bounds [channel]]
用于channel_cols中的频道
}
打印(f'overall All预算约束:{预算}')
打印('频道约束:')
对于渠道,bugds_bounds.items()中的界限:
print(f'{channel}:下限= {bounds [0]},上限= {bounds [1]}')
用户生成的图像
我们以相关的数据和参数为食,并恢复最佳支出。我们将其与获取总预算并按照当前的预算分配比例(我们称为实际支出)进行比较。
model_granularity =“每周”#运行方案
Allocation_strategy,importization_result = mmm_default.optimize_budget(
预算=预算,
num_periods = buckit_len,
budgeb_bounds = budgeb_bounds,
minimize_kwargs = {
“方法”:“ SLSQP”,
“选项”:{“ ftol”:1e-9,“ maxiter”:5_000},},,
)
响应= mmm_default.sample_response_distribution(
分配_strategy =分配_strategy,
time_granularity = model_granularity,
num_periods = buckit_len,
noings_level = 0.05,
)
#提取最佳支出
opt_spend = pd.Series(salocation_strategy,index = erstic_period.mean()。index).to_frame(name =“ opt_spend”)
opt_spend [“ avg_spend”] =预算 * current_budget_split
#绘制实际和最佳支出
图,ax = plt.subplots(figsize =(9,4))
opt_spend.plot(bink ='barh',ax = ax,color = ['蓝色','橙色'])
plt.xlabel(“支出”)
plt.ylabel(“通道”)
plt.title(“频道的实际和最佳支出”)
plt.Legend([“最佳支出”,“实际支出”])
plt.legend([“最佳支出”,“实际支出”],loc ='右',bbox_to_anchor =(1.5,0.0))
plt.show()
用户生成的图像
但是对销售有什么影响?
为了计算最佳支出的贡献,我们需要以每个通道的新支出值以及模型中的任何其他变量为食。我们只有需求,因此我们以最近时期的平均价值为食。我们还将以相同的方式计算平均支出的贡献。
#以最佳支出创建数据框
last_date = mmm_default.x [“ date”]。max()
new_dates = pd.date_range(start = last_date,oferts = 1 + budgect_len,freq =“ w-mon”)[1:]
budged_scenario_opt = pd.dataframe({“ date”:new_dates,})
budged_scenario_opt [“ tv_spend_raw”] = opt_spend [“ opt_spend”] [tv_spend_raw']
budged_scenario_opt [“ socile_spend_raw”] = opt_spend [“ opt_spend”] [social_spend_raw']
budged_scenario_opt [“ search_spend_raw”] = opt_spend [“ opt_spend”] [“ search_spend_raw”]
budged_scenario_opt [“需求”] = x [budgect_idx] [control_cols] .mean()[0]#计算总体贡献
方案_contrib_opt = mmm_default.sample_posterior_predictive(
X_PRED = budged_scenario_opt,Extend_idata = false
)
opt_contrib = scenario_contrib_opt.mean(dim =“ sample”)。sum()[“ y”]。值
#用AVG支出创建数据框
last_date = mmm_default.x [“ date”]。max()
new_dates = pd.date_range(start = last_date,oferts = 1 + budgect_len,freq =“ w-mon”)[1:]
budged_scenario_avg = pd.dataframe({“ date”:new_dates,})
budged_scenario_avg [“ tv_spend_raw”] = opt_spend [“ avg_spend”] [tv_spend_raw']
budged_scenario_avg [“ social_spend_raw”] = opt_spend [“ avg_spend”] [“ social_spend_raw”]
budged_scenario_avg [“ search_spend_raw”] = opt_spend [“ avg_spend”] [“ search_spend_raw”]
budged_scenario_avg [“需求”] = x [budgect_idx] [control_cols] .mean()[0]
#计算总体贡献
方案_contrib_avg = mmm_default.sample_posterior_predictive(
X_PRED = budgeb_scenario_avg,estext_idata = false
)
avg_contrib = saceario_contrib_avg.mean(dim =“ sample”)。sum()[y“”]。值
#计算销售额增加%
打印(f'%增加销售额:{((opt_contrib / avg_contrib) - 1,2)}')
最佳支出使我们的销售额增加了6%!这是令人印象深刻的,特别是考虑到我们已经确定了总体预算!
今天,我们看到了预算优化的强大优化。它可以帮助组织每月/季度/年度预算计划和预测。与往常一样,提出好建议的关键又回到了一个强大的校准模型。
希望您喜欢第三部分!这就是本系列掌握MMM的系列。但是,如果您想了解衡量长期品牌建设效果的复杂主题,请继续关注!