🎓 レベル:標準 | 重要度:A(必須)
📎 関連:第4章 市場反応モデル 目次 | 前提:広告・販促の効果測定
要点(BLUF)
- MMM(マーケティングミックスモデリング)は、集計された時系列の売上を、複数チャネルの出稿額+ベース+季節性に回帰して、各チャネルの貢献を分解し予算配分に使うトップダウン手法です。個人ログを使わず集計データだけで全体像を描くため、プライバシー規制下であらためて注目されています。
- 線形 MMM は 売上 ベース 出稿季節性ノイズ。最小二乗で係数を推定すると、本ノートの合成データでは真値 を推定 とよく回復し、決定係数 の当てはまりになります。
- 各チャネルの貢献額 係数 平均出稿で分解でき、貢献シェア(例 TV 30%・Digital 38%・Promo 32%)が予算配分の出発点。ただし MMM は相関ベースで因果保証ではなく、多重共線性や、残存効果(adstock)・飽和(収穫逓減)の無視で係数がバイアスしうる点に注意します(拡張は 04-04)。
1. MMM とは:複数チャネルの貢献を1本の回帰で分解する
広告・販促の効果測定 は単一施策の増分を回帰で取り出しました。MMM は同じ回帰の発想を、複数チャネルが同時に動く集計時系列に広げたものです。週次・月次の総売上を、TV・デジタル・店頭販促といった各チャネルの出稿額と、ベース需要・季節性に回帰し、「売上のうちどれだけが各チャネルのおかげか」を分解します。
特徴はトップダウンであること——個人レベルのクリックや購買ログを使わず、集計済みのマクロな時系列だけで全体像を描きます。Cookie 規制やプライバシー保護でユーザー単位の追跡が難しくなった近年、MMM が再評価されているのはこのためです。出力は各チャネルの限界効果(出稿1単位あたりの売上)と貢献額で、これを予算配分の根拠にします。「次の100万円をどのチャネルに積むか」をデータで語るための道具です。
flowchart LR TV["TV 出稿"] --> M["売上の回帰モデル"] DG["Digital 出稿"] --> M PR["Promo 出稿"] --> M S["ベース・季節性"] --> M M --> C["チャネル別の貢献分解"] C --> B["予算配分の見直し"]
2. 線形 MMM の数式と貢献分解
線形 MMM は、週 の売上 を次のように表します。
- :ベース。広告ゼロでも売れる分(ブランド・流通・既存顧客・口コミ)。
- :チャネル の出稿額(週 )。係数 がその限界効果(出稿を1単位増やすと売上が 増える)。
- :季節性。年周期(52週)の三角関数 などで表します。
- :ノイズ。
これは説明変数が増えただけの重回帰で、最小二乗で を一気に推定できます。複数チャネルを同時に入れるのが肝心で、各 は「他チャネルを一定に保ったときのそのチャネルの効果」を表します——単一チャネルだけを見る前後比較と違い、チャネル同士の交絡をある程度ほどけます。
推定したら、各チャネルの貢献額を
(係数 平均出稿)で求めます。これは平均的な売上のうちチャネル が押し上げている金額で、貢献シェア が予算配分の出発点です。限界効果 が大きいチャネルほど、追加予算の1単位が効く——ただしこれは出稿と売上が**線形(収穫一定)**という仮定の上での話です。
本ノートでは見通しを良くするため、残存効果(adstock)も飽和(収穫逓減)も入れない素の線形で、最小二乗が真の係数を回復することを確かめます。これらの非線形性を入れる拡張は本章後半 04-04 で扱います。
3. 係数の回復と貢献分解(コード)
週ぶんの売上を「ベース + TV + Digital + Promo + 季節性 + ノイズ」で合成し、説明変数 を numpy.linalg.lstsq で当てます。推定係数が真値 を回復すること、貢献額 係数 平均出稿で貢献シェアに分解できることを確かめます。
import numpy as np
import pandas as pd
# 104週ぶんの週次データを合成。3チャネルの出稿額(TV/Digital/Promo)と、
# 真の係数 β=[3,5,8]、ベース2000、年周期の季節性、ノイズから売上を作る。
rng = np.random.default_rng(1)
t = np.arange(104)
TV = rng.uniform(0, 100, 104) # 出稿額(乱数の生成順がデータを決めるので順序厳守)
Digital = rng.uniform(0, 80, 104)
Promo = rng.uniform(0, 50, 104)
beta_true = np.array([3.0, 5.0, 8.0]) # TV/Digital/Promo の真の限界効果(出稿1単位あたり売上)
season = 150 * np.sin(2 * np.pi * t / 52)
noise = rng.normal(0, 80, 104)
sales = 2000 + beta_true[0] * TV + beta_true[1] * Digital + beta_true[2] * Promo + season + noise
# 説明変数 [1, TV, Digital, Promo, sin, cos] を最小二乗で当てる
X = np.column_stack([
np.ones_like(t, dtype=float),
TV, Digital, Promo,
np.sin(2 * np.pi * t / 52),
np.cos(2 * np.pi * t / 52),
])
beta_hat, *_ = np.linalg.lstsq(X, sales, rcond=None)
names = ["TV", "Digital", "Promo"]
spend_mean = np.array([TV.mean(), Digital.mean(), Promo.mean()])
contrib = beta_hat[1:4] * spend_mean # 貢献額 = 推定係数 × 平均出稿
tbl = pd.DataFrame({
"真の係数": beta_true,
"推定係数": beta_hat[1:4],
"平均出稿": spend_mean,
"貢献額": contrib,
"貢献シェア": contrib / contrib.sum(),
}, index=names)
print("=== MMM:推定係数の回復とチャネル別貢献の分解 ===")
print(tbl.to_string(formatters={
"真の係数": "{:.1f}".format,
"推定係数": "{:.2f}".format,
"平均出稿": "{:.1f}".format,
"貢献額": "{:.1f}".format,
"貢献シェア": "{:.1%}".format}))
# ベース(切片)と当てはまり(決定係数 R^2)
resid = sales - X @ beta_hat
r2 = 1 - resid.var() / sales.var()
print(f"\nベース(切片): 真 2000 → 推定 {beta_hat[0]:.1f}")
print(f"3チャネル貢献の合計 = {contrib.sum():.1f}(平均売上 {sales.mean():.0f} のうち)")
print(f"決定係数 R^2 = {r2:.3f}")
出力:
=== MMM:推定係数の回復とチャネル別貢献の分解 ===
真の係数 推定係数 平均出稿 貢献額 貢献シェア
TV 3.0 3.12 51.8 161.8 29.9%
Digital 5.0 5.08 40.4 205.4 37.9%
Promo 8.0 7.75 22.5 174.7 32.2%
ベース(切片): 真 2000 → 推定 1996.1
3チャネル貢献の合計 = 541.9(平均売上 2538 のうち)
決定係数 R^2 = 0.905
出力の意味:まず推定係数が真値をよく回復しました——TV (真 )、Digital (真 )、Promo (真 )、ベース (真 )。重回帰は3チャネルを同時に入れることで、各チャネルの限界効果を分離して取り出せています。次に貢献分解。Promo は限界効果()こそ最大ですが、平均出稿が と小さいため貢献額は 。いっぽう Digital は限界効果 でも出稿 が大きく、貢献額 で最大です。貢献額は「効率(係数)×規模(出稿量)」の積であり、係数だけ・出稿だけでは決まらないのがポイントです。貢献シェア(TV 30%・Digital 38%・Promo 32%)は、現状の出稿構成のもとでの売上の取り分で、予算配分を議論する共通の土台になります。3チャネル合計の貢献 は平均売上 の約2割で、残りはベースと季節性——広告で動かせる部分は売上の一部にすぎないという現実感も得られます。
各チャネルの貢献を積み上げた「売上の分解」と、モデルの当てはまり(実測 vs 予測)を1枚にすると、MMM の出力像がつかめます。
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
rng = np.random.default_rng(1)
t = np.arange(104)
TV = rng.uniform(0, 100, 104)
Digital = rng.uniform(0, 80, 104)
Promo = rng.uniform(0, 50, 104)
season = 150 * np.sin(2 * np.pi * t / 52)
noise = rng.normal(0, 80, 104)
sales = 2000 + 3 * TV + 5 * Digital + 8 * Promo + season + noise
X = np.column_stack([np.ones_like(t, dtype=float), TV, Digital, Promo,
np.sin(2 * np.pi * t / 52), np.cos(2 * np.pi * t / 52)])
beta_hat, *_ = np.linalg.lstsq(X, sales, rcond=None)
pred = X @ beta_hat
spend_mean = np.array([TV.mean(), Digital.mean(), Promo.mean()])
contrib = beta_hat[1:4] * spend_mean
base_hat = beta_hat[0]
fig, ax = plt.subplots(1, 2, figsize=(11, 4.3))
# 左:平均売上の分解(ベース+各チャネルの貢献を積み上げ)
labels = ["ベース", "TV", "Digital", "Promo"]
vals = [base_hat, contrib[0], contrib[1], contrib[2]]
colors = ["#bbbbbb", "C0", "C1", "C2"]
bottom = 0.0
for lab, v, col in zip(labels, vals, colors):
ax[0].bar(0, v, bottom=bottom, color=col, label=f"{lab}: {v:.0f}")
bottom += v
ax[0].set_xlim(-0.8, 0.8)
ax[0].set_xticks([])
ax[0].set_ylabel("平均週次売上の内訳")
ax[0].set_title("売上の分解:ベース+チャネル貢献")
ax[0].legend(loc="upper right", fontsize=9)
# 右:実測 vs 予測(当てはまり)
ax[1].plot(t, sales, color="C0", lw=1.0, marker="o", ms=2.5, label="実測売上")
ax[1].plot(t, pred, color="C3", lw=1.6, label="モデル予測")
ax[1].set_xlabel("週")
ax[1].set_ylabel("売上")
ax[1].set_title("実測 vs 予測")
ax[1].legend(loc="upper right", fontsize=9)
fig.tight_layout()
plt.show()
左の積み上げ棒は、平均売上 の大部分がベース (広告ゼロでも売れる分)で、その上に TV・Digital・Promo の貢献が薄く乗ることを示します。右は実測(青)と予測(赤)の重なりで、 の当てはまりが目で確認できます。予算配分の議論は、この「動かせる薄い層」をどう組み替えるかの話だ、という縮尺感が大切です。
⚠️ よくある誤解
- MMM は相関ベースで、因果を保証しない:係数 は「出稿と売上の統計的な関連」であって、出稿の割り当てがランダムでない限り、純粋な因果効果とは限りません。出稿は需要が強い時期に増やされがち(内生性)で、それが係数を膨らませることもあります。実務では MMM の係数を実験(ジオ実験・A/B)で較正します(第7章 実験と因果推論)。
- 多重共線性で係数が不安定になる:チャネルの出稿が連動して動く(例:TV とデジタルを同時に増やす)と、どちらの効果か分離できず、係数の分散が膨らんで符号が反転することすらあります。本ノートは出稿を独立に乱数生成したので素直に分離できましたが、実データでは出稿計画の相関に要注意です。推定の安定性・標準誤差・正則化は統計テキストの回帰論へ。
- adstock・飽和を無視すると係数がバイアスする:広告効果は出稿した週だけでなく翌週以降にも残り(残存効果=adstock)、出稿を増やすほど効きが鈍る(飽和=収穫逓減)のが普通です。これらを入れずに素の線形で当てると係数が歪み、「もっと出せば線形に売上が伸びる」と過大に外挿してしまいます。非線形変換を入れる拡張は 04-04 で扱います。
- ベースを広告の手柄にしない/予算配分は限界で考える:貢献分解のベース は広告ゼロでも売れる分で、これを施策の成果に数えてはいけません。また配分の最適化は平均ではなく限界(追加1単位の効果)で考えるべきで、飽和があるなら大きいチャネルでも限界効果は逓減します。事前情報(過去の弾力性など)を確率分布として織り込むベイズ MMMはベイズ統計テキストの階層モデルへ。
関連ノート
- 広告・販促の効果測定(前提・単一施策の増分を回帰で測る発想を複数チャネルへ拡張)
- 第4章 市場反応モデル 目次
- 価格最適化(利益最大化)(価格も売上を動かす反応変数。価格を説明変数に加える拡張へ)
- 残存効果(adstock)と飽和(収穫逓減)で線形を拡張するのは本章後半 04-04/係数の安定性・多重共線性・標準誤差は統計テキストの回帰論/事前情報を入れるベイズ MMM はベイズ統計テキストの階層モデルへ/実験での較正は第7章 実験と因果推論
- マーケティング・サイエンス 全体目次