大促期间广告加码、折扣加深、库存快速消耗,销售也同步上涨。模型看到的是相关性;如果没有把促销、成交价和可售状态正确放进方程,渠道 ROI 很可能从一开始就算错了。
假设一个品牌在大促周把 Meta 和 Google 预算提高 60%,同时把主力商品从原价 499 元降到 399 元。销量翻倍后,模型如果只看渠道花费和销售额,很自然会把大部分增长分给广告。问题是,消费者究竟因为看到了更多广告而购买,还是因为价格下降 20% 才购买?如果促销和媒体总是一起发生,历史数据本身很难把两者自动拆开。
缺货会制造相反方向的错误。广告把需求推高,但主力 SKU 在周中断货,最终记录的销售额被库存上限截断。模型看到“高花费但销售没有继续增长”,可能误判渠道已经饱和、ROI 很低。真实情况却是需求存在,只是没有库存可以完成交易。
MMM 的目标变量通常是观测到的销售或收入,而业务真正关心的是需求与媒体增量。促销和价格会改变需求,缺货会限制需求转化为销售。三者如果被遗漏、口径错误或放错因果位置,都会把非媒体效应转移到渠道系数上。
为了看清机制,先把真实的数据生成过程简化为:
如果模型漏掉 Promo,而促销期间又恰好加大媒体投放,那么估计出来的媒体系数不只包含媒体自身效应,还会吸收与媒体相关的促销效应。在线性简化下:
当促销提高销售(βp > 0)且促销与广告同向变化时,媒体系数通常被向上推;如果折扣期广告反而收缩,偏差方向也可能相反。因此“遗漏促销一定高估广告”不是普适定律,偏差方向取决于变量对 KPI 的影响方向以及它与媒体的相关方向。
公式同样适用于价格、库存、门店覆盖、竞品活动和宏观需求。遗漏变量偏差的关键不是“模型少了一列”,而是被遗漏变量同时影响 KPI,并且与已纳入的媒体变量相关。
促销日历是很好的起点,但单一 promo_flag 只能告诉模型“这一周有促销”,不能区分九折、七折、满减、优惠券、Prime Day 或全店大促。它默认所有促销强度和作用方式相同,这在 Q4 通常不成立。
| 字段 | 示例 | 回答的问题 | 注意事项 |
|---|---|---|---|
| 促销标记 | promo_flag = 0/1 | 有促销与无促销的平均差异 | 数据少时稳定,但无法表达强度 |
| 折扣深度 | 1 - net_price/list_price | 折扣每加深一个百分点的影响 | 要用实际支付口径,不能只看页面标价 |
| 促销类型 | 满减、券、会员价、大促 | 不同机制是否有不同 lift | 类型太多会稀释样本,先合并业务相近活动 |
| 促销覆盖率 | 促销 SKU 销售前权重 | 活动覆盖了多少业务体量 | 避免用活动后销售权重,后者已经受结果影响 |
还要区分促销的即时效应和跨期效应。囤货型品类在大促周销量上升,之后几周可能回落,这是消费者提前购买造成的 pull-forward,而不是需求永久增加。只放当周标签,模型可能把大促后的回落错误归因给广告衰减或渠道疲劳。可根据业务机制加入有限的促销前导/滞后项,但不应无约束地堆很多 lag。
价格变量最常见的错误是使用商品标价。真正影响消费者选择和企业收入的,通常是扣除券、满减、会员优惠后的实际成交价(net realized price)。多 SKU 业务还需要控制产品组合:高价 SKU 占比上升会推高平均成交价,即使每个 SKU 都没有涨价。
更稳健的做法是构造可比价格指数,例如固定一组 SKU 权重计算周度价格,或者在可行时先到 SKU/品类层建模再汇总。简单的收入加权平均价存在机械关系:收入本身参与了价格计算,畅销商品的销量变化会改变权重,可能引入内生性。
固定基期数量权重的价格指数能减少销售结果反过来改变权重的问题,但仍不能自动解决定价内生性。企业往往在预期需求疲软时降价,在预期旺季时涨价;此时价格由需求预期共同决定。MMM 中加入价格可以减少遗漏偏差,却不意味着价格系数天然具有严格因果解释。若目标是估计价格弹性,通常需要更强的识别设计、工具变量、自然实验或受控价格测试。
缺货时,数据记录的是“能卖出去多少”,不是“消费者想买多少”。可以把观测销售理解为受供给约束的结果:
一个简单 stockout_flag 也常常不够。周日断货和周一断货都标记为 1,但损失完全不同;一个边缘 SKU 缺货与主力 SKU 缺货也不应等权。更好的变量包括可售率、缺货天数占比、按历史销售权重计算的库存可用率,以及可售门店/配送区域覆盖率。
缺货变量的作用还取决于 KPI:
把可售率放进回归可以减少渠道系数偏差,但一个线性控制项通常无法精确推断缺货期间本来会卖多少。严重缺货期可以做敏感性分析、使用流量/加购等需求代理,或在有充分依据时将其视为截尾观测;不能简单把缺货周删除,因为缺货往往正是高需求和高投放共同造成的,删除会形成选择偏差。
“与销售相关”不等于“应该控制”。在加入任何变量前,先画出业务因果路径,判断它发生在媒体之前、媒体之后,还是与媒体和销售共享上游原因。
| 角色 | 典型路径 | 建模原则 |
|---|---|---|
| 混杂变量 | 大促计划 → 广告加码;大促计划 → 销售增长 | 通常需要控制,否则促销效应会进入媒体系数 |
| 中介变量 | 广告 → 网站流量/品牌搜索 → 销售 | 估计广告总效应时不应作为普通控制变量直接截断;需要中介或 Full-Funnel 结构 |
| 碰撞变量 | 广告 → 缺货 ← 自然需求 | 机械控制可能打开虚假关联;应结合库存机制和目标 estimand 设计 |
| 结果代理 | 销售增长 → 动态调价或追加促销 | 可能内生,不能仅凭相关性解释为原因 |
例如网站会话数看起来是很好的需求控制,但如果大部分会话由广告产生,它就是媒体效应的一部分。把它塞进标准 MMM 会让模型只估计“控制流量后的广告直接效应”,系统性低估广告总效应。类似地,广告导致需求暴增后引发缺货,缺货既是供给约束,也可能位于广告影响销售的路径上;此时要先说明目标是估计实际已实现收入,还是无库存约束下的潜在需求。
字段设计应优先使用在决策时可获得、口径稳定、不会机械使用目标变量的信息。下面是一套周度 DTC / 电商 MMM 的实用起点:
| 业务概念 | 推荐字段 | 不推荐的替代 | 原因 |
|---|---|---|---|
| 促销 | 折扣深度、活动类型、覆盖率、活动前后有限 lag | 所有活动共用一个永久不变的 dummy | 强度和机制不同 |
| 价格 | 实际成交价指数、固定 SKU 权重、相对竞品价 | 简单收入/销量得到的平均售价 | 与 KPI 机械相关,且受产品组合影响 |
| 库存 | 可售率、缺货天数占比、主力 SKU 加权可用率 | 周内任何缺货即记 1 | 不能表达持续时间和业务权重 |
| 分销 | 可售门店数、可配送邮编覆盖、有效 listing 数 | 累计注册门店或历史 listing | 不代表当周真实可购买范围 |
所有连续变量都应检查异常值、定义变化和缺失机制。缺失库存数据不能默认填 0,因为 0 通常意味着“完全无库存”,而不是“未知”;价格缺失也不应直接前向填充跨过换品或币种变化。字段口径发生变化时,应增加定义变更标记或重新构造一致历史。
媒体和促销可能不只是相加。大促落地页、优惠价格和广告曝光共同出现时,广告转化效率可能显著提高,可以用有限且有业务依据的交互项表达:
但交互系数要求数据里同时存在“有/无促销”和不同媒体强度的足够变化。如果品牌每次促销都把广告固定提升到同一个水平,主效应和交互效应仍然不可识别。不要为每个渠道、每种促销都创建交互项;参数数量会迅速超过周度样本能够支持的范围。可以先用业务假设选择一两个关键交互,再通过时间回测和正则化检验是否带来稳定增益。
加入业务控制变量后,不要只看训练集 R² 是否提高。一个变量可能仅仅更会解释历史,甚至把媒体真实效应截走。建议按以下顺序验证:
下面是用于说明机制的示例,不代表真实客户或 SparkX 基准。模型使用同一份周度数据,仅逐步增加业务信息:
| 模型 | Meta ROI | Search ROI | Holdout MAPE | 解释 |
|---|---|---|---|---|
| 仅媒体 + 季节性 | 4.1 | 6.0 | 19% | 大促增长大量归给同期广告 |
| + 促销强度 + 价格指数 | 2.9 | 4.5 | 13% | 折扣和价格效应从媒体贡献中分离 |
| + 可售率 + 促销后 lag | 3.2 | 4.6 | 11% | 缺货期需求受限与促销后回落得到解释 |
最终模型里 Meta ROI 比第二版略高,不矛盾:控制缺货后,模型认识到部分高花费周销售没有继续上涨是供给受限,而不是媒体失效。这个例子说明控制变量不会只把 ROI 往下调;它们的作用是把不同业务机制分开,方向取决于数据和因果结构。
广告 ROI 被高估或低估,很多时候不是建模引擎不够复杂,而是模型没有看到真实业务同时发生了什么。促销决定购买理由,价格改变需求强度,库存决定需求能否兑现。把三者放进正确的因果位置,再用样本外验证和实验校准检查结论,才有资格把模型输出交给预算优化器。