と。

Github: https://github.com/8-u8

Marketing Mixed ModelとRobyn with R

この記事は

R Advent Calendar 202211日目の記事です。 10日目の記事にはしょこさんの記事ですね。
Twitterでは個人的に「動くグラフ」をたくさん作っているすごい人、という認識でいるのですが、今回は rtweet パッケージを使ってTwitterの画像を集めて、
それをアニメーションにしているようです。すごい。
まだ読んでいない?ぜひ行ってきてください。この記事はとても長いので。

お久しぶりです

結局月1投稿すらままならなかったですが、元気にやっています。
アドカレの記事でいうこともないのですが、以下のご報告をします。

  • 結婚しました
  • PC買いました
  • 統計検定はダメでした
  • CTF始めました
  • 転職します
  • 来年もよろしくお願いします

内容

仕事と個人的な道楽を兼ねて一時期勉強していたMarketing Mixed Modelについて、
そのコンセプトとMeta社の実装であるRobynをWalkthroughしようと思います。

実行環境

以下のgithubにここで記述したコードは置いてあります。

github.com

以下の環境で動作をさせています。
Rは優しいので、バージョン4.0くらいなら動作すると思います。ただし、WSL2上のUbuntuでは挙動不審でした。
Robynはreticulateパッケージを通して、裏でPythonを使っているのですが、
僕の環境ではWSL2にconda系を入れたくなくて、pipenvの仮想環境を使いたかったことが、あまり良い結果に結びつかなかったのかもしれないです。
reticulateの挙動はよくわからないですが、セッション単位でPythonの仮想環境が作れるようなので、よほどのことがなければ、ドキュメント通りに仮想環境を立てて良いと思います。

  • PC: Thinkpad T14 Gen3
  • OS: windows 11
  • RAM: 32GiB
  • CPU: 12th Gen Intel(R) Core(TM) i7-1260P
  • Rのバージョン: 以下。
> version
               _                                
platform       x86_64-w64-mingw32               
arch           x86_64                           
os             mingw32                          
crt            ucrt                             
system         x86_64, mingw32                  
status                                          
major          4                                
minor          2.2                              
year           2022                             
month          10                               
day            31                               
svn rev        83211                            
language       R                                
version.string R version 4.2.2 (2022-10-31 ucrt)
nickname       Innocent and Trusting     

対象読者

  • Rは触れるけど統計理論はよく知らない
  • MMMに興味はあるけど理論の詳細にまで踏み込んだ議論は一旦脇においてゴールを見たい

逆に言うとMMMのプロや統計理論のプロの皆さんは対象読者ではないんですけど、
「この伝え方はダメだろ」というのがあればぜひとも教えてください!!!!

※ 「理論からトップダウン的に理解するべきだ」とか、そういう哲学的な批判はうけつけないです。

MMMとは

先駆者の記述

saltcockeyさんの記事やTJOさんの記事では、 MMMが何を表現しているモデルであるかを所与として、広告効果の時系列上の波及をよく表現するモデルとはなにか、丁寧に記述されています。
本記事を読む人はMarketing Mixed Modelについてよくわかっている人かも知れないし、そうではないかもしれないので、とりあえずどんなコンセプトのモデルなのか、どういう問題に答えるモデルなのかについて記述していきます。
逆に言うとMMMの持つ統計モデルとしての問題点や批判については議論しません。たとえば以下。

  • モデルの内生性について
  • 広告と利益指標との因果関係について
  • 時系列モデルのパラメータ推定の妥当性について  

など。

あくまで個人の見解なので、間違った理解があったら教えてください。

MMMのコンセプト

Marketing Mixed Model(面倒なのでMMMと略します)は、広告の予算及び広告の反応を用いて、売上金額や来店客数、資料請求など、企業の利益に直結する成果を表現することで「広告が利益にどれだけ効果を与えたか」を説明・評価するモデルです。
Cockieの廃止など、個人を追いかけにくくなるWeb広告や、そもそも個人を追いかけるのが困難なマスメディアの広告を総合的に評価できるという点で、2022年はホットな話題が多かったかなと個人的には感じています。
どのように広告の効果で利益指標を表現するかというと、回帰モデルが基本フレームワーク担っています。具体的にはRobynもLightWeight MMMも、以下のようなモデル構造は共通しています。

 \displaystyle
利益指標_t = 切片 + トレンド_t + 季節性_t + f(広告予算orコスト_t) + その他要因_t

ここで、$t$は時系列に関するインデックス、$f(.)$は広告予算・コストのラグ効果を考慮するための関数を表現しています。この点は後述します。

「その他要因」については、たとえば競合のプロモーションなど、自社の広告以外に利益指標に影響を与えそうな要因を指します。
MMMは、これら各項の影響度を推定することで、広告の利益指標に対する効果を記述・説明します。
パラメータが推定できれば、上記のモデル式を援用することで、広告の投資収益率(ROI)を算出することも可能です。

想定入力データ

入力データは関係各所が様々に定式化していますが、基本は以下の変数が時系列で必要です。

  • 利益指標(売上、来店客数、資料請求数など)
  • 広告インプレッション(メディア別)
  • 広告コスト(投下予算・メディア別)
  • その他要因に関連する変数
    • 天気や気温
    • 競合のプロモーション
    • ニュース

目的変数となる利益指標と、説明変数である広告インプレッションまたは予算は必須ですが、
その他要因についてはあってもなくても良いです。スモールスタートを志向するなら、まずはその他要因を考えずにスタートしても良いかもしれません。
データの量ですが、日次データ、週次データで2~3年分あることが望ましいと言われる気がします。
もちろん、メディアの施策や、MMMで解決したい問題の規模によるのですが「来期の広告の予算を算定するのに使いたい」といった場合は、数年のデータ蓄積が必要になるのではないかなと思います。
その場合、日次であれば365×2=730レコード~ですし、週次であれば52×2=104レコード~です。データサイエンス、ディープラーニングが騒がれる昨今のデータ事情の中ではとても規模の小さいデータになると思います。
そのため、少ないデータでも「良いデータ」を集めて、使える情報を最大限使うようなアプローチが重要になってきそうです。

MMMにおけるパラメータ推定

MMMは時系列データを用います。そのため目的変数は自己相関(自己共分散)を持ち、
普通の回帰モデルが要請する誤差項の独立性が保証されません。
そのため通常の推定方法では推定値の標準誤差が一致性を持たず、パラメータの仮説検定や信頼区間を正しく計算できません*1
また、時系列データの特徴をコントロールできても、広告のインプレッションやコストは見かけ上連動しやすく、重回帰モデルにおける多重共線性も懸念されます*2。多重共線性が存在すると、こちらも推定値の標準誤差が大きくなってしまうため、検定や信頼区間に対する信頼性がゆらぎます。
こうした問題をみなかったこと回避する必要があり、関係各所色々に努力を重ねているところです。
たとえばLightWeight MMMは階層ベイズモデルを導入してこれらを克服しようとしていますし、後述のようにRobynではRidge回帰によって多重共線性の影響を軽減させています。

広告の「ラグ」効果

日常生活を振り返ると、広告を見た「瞬間」に「あ、この商品を買おう」とか「あ、このお店行くか」と思い至ることもあるでしょうが、広告を見て、数日~数週間経た後、日常のお買い物やお散歩などで「この商品テレビでみたわ」とか「あー、あのタレントが出てたCMのお店ここにあったのか」という発見をして、商品を手に取ったり、お店に入ったりすることも大いに考えられます。
こうした「ラグ」効果をAdstock効果とかCarryover効果とか呼びます。
Robynではgeometricweibullの2つの分布を仮定できます。これらは共通して「当週の広告の効果は、ある減衰率$\theta$によって減衰されつつも次週に繰り越される」という仮定を通して、広告の効果を表現しています。

MMMの出力

基本的に、MMMは重回帰モデルで表現され、そのパラメータ推定結果から様々な結果指標を評価します。

1. 精度

予測値と実測値の乖離がないことが望ましいことはMMMも例外ではありません。Robyn、LightWeight MMM両方とも、予測と実測の乖離度を評価します。
また、ビジネス課題によっては過剰適合(過学習)も望ましくない場合があるので、
クロスバリデーションを設計することも有効だと思います*3

2. パラメータ推定値

いわゆる「回帰係数」のようなものです。後述の「要因分解」に用います。

3. 要因分解

「売上が広告メディアで説明されるとして、どのメディアがどの程度売上を構成しているか?」を表現します。たとえばテレビCMが売上の40%を占めていて、競合のプロモーションが10%程度売上を「押し下げ」ている、というような解釈を通して意思決定が出来ます。
この要因分解に基づくことで、次の期の広告予算を、どのメディアにどの程度割り当てることで、売上より望めるかというシミュレーション(予算アロケーション)も可能です。

4. 反応曲線

広告の予算投資とその反応がどういった関係性を持つのかをモデル化します。 たとえば「Web広告に投下した予算はどれくらい効率的に使われているのか?」に答えます。
ここでは「一定量の広告投下は必要であるが、投下のし過ぎはむしろ効率が悪い」というような投資効率の変化を表現する事が多いと思います。この仮説を表現するために「S字」の曲線であったり、対数曲線であったりで反応曲線をモデル化し、パラメータを推定します。
Robynではhill関数、LightWeight MMMではcarryoverモデル、Adstockモデル、hill-Adstockモデルの3つから選べるようになっています。

つかれた

ここまでMMMのコンセプト部分について記述しました。

  • RobynもLightWeight MMMも基本的には「回帰モデル」を仮定している
  • 広告投資効果には「投資効果逓減」を仮定している
  • 仮定に基づいて推論し、そこから複数の結果を得ることで、総合的に意思決定を行う
    • 場合によっては将来的な広告予算配分のシミュレーションも可能

重要であるのは、MMMは広告と利益指標との間にある関係性についていくつかの仮定のもとでモデル化し、表現しているという点にあります。
このモデル自体はシンプルでありながら汎用的な「良いモデル」だと思っていて、
広告と利益指標との関係性は業種や商材によって一定程度共通する構造だとは思います。
一方で、パラメータの大小ではなく、モデルに用いている関数形が大きく異なることが想定される商品・サービスである場合、その構造をオリジナルで構築する必要があると思います。
RobynでMMMを構築する前に、基本的な集計や探索的な分析を行って、
Robynが想定するモデルで表現できそうか、少し検討したほうが良いかもしれません。

実装

Robynとは

RobynはMeta社がFacebookだった頃から開発しているMMMを楽に実行するためのOSSです。

github.com

後述するように前処理や結果の可視化に至るまで「結果を出す」までがとても気楽にできるパッケージです。そしてRで実装されているという点が素晴らしい。
ドキュメントは関数の説明だけでなく「アナリスト向けMMMガイド」という、上記のMMMに関する説明を数式を使わずに簡易に説明していて、モデリングを始める前の、データを収集するプロセスで気をつけるべきこととは何か、データのレビューをして確認するべきポイントは何かなど、大変丁寧な記載があるので、みんな読んだほうがいいと思います。
数式を理解しなくても誰でもMMMができるようにするという意味で、Robynはとても良いと思います。

データ

kaggleにMMMをしやすそうなデータセットがあったので、こちらを援用します。 www.kaggle.com

日次時系列で、どこでどんな広告をどの程度のコストで打って、売上、インプレッションなどがどれだけ得られたのか、というデータが一通り揃っています。
今回はRobynでMMMを行う上で必要なデータに絞ってシンプルに実行するために

  • 売上(Sales)
  • 広告インプレッション(Impressions)
  • 投資量(Spend)

に絞って議論します。

参考:How To Create A Marketing Mix Model With LightweightMMM

基本ステップ

Robynは基本的に3つのステップを踏めば良い設計です。

  • robyn_inputs: データセットを読み込み、前処理を行う
    • この段階で再度確認した方が良い変数などについてはwarningsが出てくれます。
    • 前処理を行った上で、各種パラメータを推定する上でのハイパーパラメータを設定できます。
  • robyn_run: モデルの実行・パラメータの推定
    • デフォルトでは5Chainくらいを回してくれます。そこそこ時間がかかる
  • robyn_outputs: 分析結果の出力
    • これがすごい。モデルの分析結果についてフォルダが自動生成される。
    • その中にpng形式でのグラフと、csv形式での数表として出力される。
    • 結果のグラフが散逸することなく、プロジェクトフォルダがきれいに使える点はとてもいい
  • robyn_allocator(オプション):シナリオを置いて、広告予算の最適化シミュレーションを行う。
    • 大きく以下の2つのシナリオでシミュレーションができる
    • 同じ広告予算であった場合により利益を上げることが期待できるようなメディア配分バランス
    • 広告予算を与えた上で、利益を最大化するようなメディア配分バランス

その他、新しくデータが入ったときのモデルのチューニングも割と簡単にできますが、今回は省略。

step1: robyn_inputs

データの前処理部分から入ります。

# loading packages
library(reticulate)
library(Robyn)
library(tidyverse)
library(readxl)

# setup clean python virtual environment
virtualenv_create("r-reticulate")
py_install("nevergrad", pip = TRUE)
use_virtualenv("r-reticulate", required = TRUE)

# load data
usedata <- readxl::read_excel("./input/kaggle_ad_data.xlsx")

##### 省略!詳細はgithub側で確認ください #####

robyn_usedata  <- media_data %>% 
  dplyr::left_join(costs_data, by = "Date") %>% 
  dplyr::left_join(sales_target, by = "Date")

Rがわかれば大体わかってくれると思います。本命は以下のコード。
Robyn::robyn_inputs()を使って、上記データをRobynに渡します。

# make input data
InputCollect  <- Robyn::robyn_inputs(
  dt_input = robyn_usedata, # 使うデータフレーム
  date_var = "Date",        # 日付の変数名
  dep_var = "Sales",        # 目的変数
  dep_var_type = "revenue", # 目的変数の属性(売上かコンバージョンか)
  # prophetを用いた時系列のデコンポジション。
  # holidayは省略しているが、日本の祝日はprophetから持ってくるために
  # 少しPythonでスクリプトを組む必要がある
  prophet_vars = c("trend", "season", "weekday"),
  prophet_country = "US",
  paid_media_spends = costs_col_names, # メディア投資変数
  paid_media_vars = media_col_names,   # メディアのインプレッション
  # メディア変数の係数の正負。基本は「正」で良いが、
  # たとえば競合プロモーションや「炎上」があった項目は「負」扱い。
  paid_media_signs = rep("positive", length(media_col_names)),
  # 時系列の範囲を設定する。train-test splitなどはここでセッティングできそう
  window_start = "2021-10-17",
  window_end = "2022-01-11",
  # 広告の「ラグ」のモデル指定。今回は計算スピードも兼ねてgeometric。
  adstock = "geometric"
)

ハイパーパラメータの調整も、一定程度ガイドがあります。
繰り返しのあるコードになっているのできれいではないのですが、
各ハイパーパラメータの探索範囲を定めています。

# hyperparameter setup
hyperparameter_names  <- Robyn::hyper_names(
  adstock = InputCollect$adstock,
  all_media = InputCollect$all_media
)

##### 省略!詳細はgithub側で確認ください #####

hyper_params <- cbind(
  hyper_params_from, 
  hyper_params_to
) %>% t %>% 
  as.data.frame

colnames(hyper_params) <- hyper_params_names
hyper_params <- as.list(hyper_params)

# error not found, but not defined the hyperparameter.
InputCollect <- Robyn::robyn_inputs(
  InputCollect = InputCollect,
  hyperparameters = hyper_params
)

今回は以下のようなwarningsが出ています。
データのチェック、前処理の段階でフィッティングを向上させるためにデータをsplitするか、
ImpressionではなくSpendを使うとかすることをオススメされます。
Robynに突っ込む前にデータレビューを行うことは前提ですが、その上でもこのようにレコメンドを返してくれるのはとても優しいですね。
これを出すために今回は悪いデータを入れている節もあります……。

>> Running feature engineering...
NOTE: potential improvement on splitting channels for better exposure fitting. Threshold (Minimum R2) = 0.8 
  Check: InputCollect$plotNLSCollect outputs
  Check data on: "Impressions_Brand_1_Ad_Group_10", "Impressions_Brand_1_Ad_Group_11", "Impressions_Brand_1_Ad_Group_3", "Impressions_Brand_1_Ad_Group_5", "Impressions_Brand_1_Ad_Group_7", "Impressions_Brand_1_Ad_Group_8", "Impressions_Brand_2_Ad_Group_5", "Impressions_Brand_2_Ad_Group_6"
Warning messages:
1: In .font_global(font, quiet = FALSE) :
  Font 'Arial Narrow' is not installed, has other name, or can't be found
2: In fit_spend_exposure(dt_spendModInput, mediaCostFactor[i], paid_media_vars[i]) :
  Spend-exposure fitting for Impressions_Brand_1_Ad_Group_3 has rsq =  0.6488 To increase the fit, try splitting the variable. Otherwise consider using spend instead.
3: In fit_spend_exposure(dt_spendModInput, mediaCostFactor[i], paid_media_vars[i]) :
  Spend-exposure fitting for Impressions_Brand_1_Ad_Group_5 has rsq =  0.3885 To increase the fit, try splitting the variable. Otherwise consider using spend instead.
4: In fit_spend_exposure(dt_spendModInput, mediaCostFactor[i], paid_media_vars[i]) :
  Spend-exposure fitting for Impressions_Brand_1_Ad_Group_7 has rsq =  0.6573 To increase the fit, try splitting the variable. Otherwise consider using spend instead.
5: In fit_spend_exposure(dt_spendModInput, mediaCostFactor[i], paid_media_vars[i]) :
  Spend-exposure fitting for Impressions_Brand_2_Ad_Group_6 has rsq =  0.6145 To increase the fit, try splitting the variable. Otherwise consider using spend instead.
6: In robyn_engineering(InputCollect, ...) :
  R2 (nls): weak relationship for "Impressions_Brand_1_Ad_Group_1", "Impressions_Brand_1_Ad_Group_10", "Impressions_Brand_1_Ad_Group_11", "Impressions_Brand_1_Ad_Group_13", "Impressions_Brand_1_Ad_Group_2", "Impressions_Brand_1_Ad_Group_3", "Impressions_Brand_1_Ad_Group_5", "Impressions_Brand_1_Ad_Group_6", "Impressions_Brand_1_Ad_Group_7", "Impressions_Brand_1_Ad_Group_8", "Impressions_Brand_2_Ad_Group_1", "Impressions_Brand_2_Ad_Group_2", "Impressions_Brand_2_Ad_Group_3", "Impressions_Brand_2_Ad_Group_4", "Impressions_Brand_2_Ad_Group_5", "and Impressions_Brand_2_Ad_Group_6" and their spend
7: In robyn_engineering(InputCollect, ...) :
  R2 (lm): weak relationship for "Impressions_Brand_1_Ad_Group_10", "Impressions_Brand_1_Ad_Group_11", "Impressions_Brand_1_Ad_Group_3", "Impressions_Brand_1_Ad_Group_5", "Impressions_Brand_1_Ad_Group_7", "Impressions_Brand_1_Ad_Group_8", "Impressions_Brand_2_Ad_Group_5", "and Impressions_Brand_2_Ad_Group_6" and their spend

step2: robyn_run

Robynでの分析の実行はRobyn::robyn_runで出来ます。
上記の大量のオブジェクトを含むInputCollectを入れて、いくつかの設定を記載することで、
勝手に実行が可能です。
注意として、robyn_runでは背後にnevergradを使っているので、あらかじめreticulateを通して仮想環境上にインストールしておく必要があるかもしれません。

OutputModels <- Robyn::robyn_run(
  InputCollect = InputCollect,
  iterations = 15000,
  trials = 5,
  outputs = FALSE
)

OutputModels$convergence$moo_distrb_plot
OutputModels$convergence$moo_cloud_plot

step3: robyn_outputs

Robyn::robyn_outputsを使うと、任意のフォルダに結果が一通り格納されます。
この点が非常にありがたく、robyn_outputsを実行すればフォルダが勝手に作られ、
そこにすべての結果がアウトプットされます。今回は紹介しませんが、csvファイルも出力されるので、 「Excelでよこせ」と言われても平気ですね。 デフォルトでは膨大なモデルから3つ選択されます*4
この3つのモデルからどのモデルを採用するかは、分析者に委ねられます。

output_path <- "./output"
OutputCollect <- Robyn::robyn_outputs(
  InputCollect = InputCollect,
  OutputModels = OutputModels,
  csv_out = "pareto",
  clusters = TRUE,
  plot_pareto = TRUE,
  plot_folder = output_path
)
print(OutputCollect)

結果のグラフ

出力結果については、Robyn側で「良い」と判定したモデルが2~3出てきますので、
その結果を見ながら、精度と実務上解釈できるかどうかを判断しながらモデルを選択します。
その意味で最終候補の3つからモデルを1つに絞るための「指標」は明確には出ていません。
「精度はいいけど要因分解のバランスがヘン」「要因分解のバランスはいいけど精度がなあ」という部分で、
人間として落とし所を探してくれ、というスタンスだと、現状の僕は捉えています。
今回は特定のモデルを選び、結果を眺めてみましょう。

まず右上のグラフを確認すると、波形は概ねフィットしているように見えます。
一部のスパイクには対処しきれていないので要調整かもしれませんね。
ついで右下の残差についても、ほぼほぼ無相関のようです。追加で制御するべき変数は考えなくても良さそうです。
左上のウォーターフォールグラフを見ることで、どのメディアがどの程度売上に寄与しているかがわかります。
どうもSpend_Brand_2_Ad_Group_2が貢献している様子。このデータでは、具体的にどういったSpendなのかはわからないのですが、たとえばここがTVCMであれば「テレビ離れが進んでいるとは言え、TV広告の縮小はまだ難しいかも」と解釈する事もできるかもしれません。 左側2番めのグラフは、各メディアのROIが算出されています。Spend_Brand_1_Ad_Group_6が非常に高い(高すぎる?)ROIをはじき出しています。これが高すぎるかどうかはドメインにもよるかもしれませんので、数値と、そのメディアが本当に効率の良いメディアであるかは確認の余地があります。
その右のグラフが投資曲線です。曲線と、その平均的な投資額が算出されています。
左下はAdstock効果の推計です。どの程度Adstock効果があるかが把握出来ます。

こちらのとりまとめグラフはそれぞれ別で関数が設計されているので、
独立に実行することができるので、細かく見たい場合はそれらを使うと良いと思います。

additional: robyn_allocator

ここまでで実行は終わりなんですけど、MMMの強みは「広告予算を設定した上で、それをメディアに適切に配分するには?」という問題にも一定の答えを出してくれます。
「メディア アロケーション」と呼ばれることがあります。
こちらは実務ににおいても統計的な解析と言うよりは数理最適化問題を解いている場面が多いかなと思います。

# allocation
best_model <- "1_694_1"


all_spend <- robyn_usedata %>% 
  dplyr::ungroup() %>% 
  dplyr::select(-Date, -Sales, -contains("Impressions")) %>% 
  apply(., 1, sum) %>% sum

AllocationCollect_01 <- Robyn::robyn_allocator(
  InputCollect = InputCollect,
  OutputCollect = OutputCollect,
  select_model = best_model,
  scenario = "max_historical_response",
  channel_constr_low = 0.7,
  export=TRUE,
  date_min="2022-01-01",
  date_max="2022-01-11"
  
)

今回は「過去の予算と同じ金額を広告に投資したとして、 最適なメディアへの配分比率はいくらになるか?」に答える設定( max_historical_response )で計算し、
結果を表示します。

見るに、Spend_Brand_2_Ad_Group_1の比率を上げて、一方でGroup2の比率を下げると良い、的なシミュレーション結果が出ているようです。
たとえばTVCMに予算を割きすぎていたところを、同じように予算が得られる場合、Webにもう少し比率を割いてもいいのかな?というような結果を議論できそうです。おもしれー。

感想・所感

「とりあえずMMMをやってみよう」というスタートの場合、Robynの存在はとても良いと思います。
Rで分析のためのデータを準備することができる程度のコーディングができれば、ベンチマークモデルやプロトタイピングが十分できるように整備されている、とても使いやすいパッケージです。
一方で細かなカスタマイズ(たとえばAdstockに独自の関数を定義するとか、階層構造のモデルとか)は、Robynで閉じた実装は難しいようです。
拡張性はないわけではないようです。たとえば構造方程式モデリングを援用したモデルの拡張の例はすでに実装例があります。 devpost.com

個人的にはLightWeight MMMに近いアプローチでMMMの構築をすることが多かったので、Robynの思想には新しさを感じています。特に膨大なモデルを生成してその中で「良い」モデルを3つまで選び、そこから人の意志で決めるという部分は、良い意味で人の推論能力を信頼しているなと思います。
でもLightWeight MMMの理論的に素直に積み上げた設計も好きなので、たとえばRobynで上手くいかない場合にはLightWeight MMMの力を借りると言うのもありかもしれません。

ちなみに: LightWeight MMMやMaMiMo

Robyn以外にMMMを実現するOSSとして、Googleの開発しているLightWeight MMMMがあります。

github.com

GoogleはMMMに対して、いくつか論文として発信しています。

LightWeight MMMは基本的にPythonで実装されていて、裏でjaxが動いている都合上、Windowsでの実装はサポートされていません*5
R Advent Calendarであるこの記事では実装含めた解説をしません。
ただ、Walkthrough自体はしているので、体力があればこちらも記事で紹介します。

github.com

Rで実装できるMMMは他にもMaMiMoがあります。詳しくは僕もわかっていませんので、近く使ってみようと思っています。

github.com

先駆者記事(in Japanese)

MMMは昨今注目されるモデルなので、いろいろな個人、法人が紹介記事を書いています。

直近だと博報堂がレビュー論文形式でMMMに言及していて、RobynとLMMMMに関しての比較を(簡単な記述ですが)しています。実装にあたっての思想が違うという点はまさにそう思っていて、個人的にはMMMといえばLMMMの思想が自然に思い浮かばれます。ただし、このアプローチは現実問題との乖離について、合理的な仮定を置いた調整がシビアな印象があり、使うのが難しいように思います。
その意味でRobynは、モデルとしての完成度、精度、説明可能性の「妥協点」を探すアプローチを取っているという点で、実務に寄り添ったMMMができる、というのは面白いところだなと思います。

*1:推定値自体の一致性はあったはず

*2:たとえば複数メディアで広告を打つことで認知や売上を上げようという戦略は十分考えられると思います

*3:そこまでガチる必要はなくtrain-test splitを行うだけでもいいと思います。でもガチるならちゃんとやった方がいい。

*4:詳細はこちら

*5:WSL2を使うことによる実装は可能