{"id":13710288,"url":"https://github.com/sibylhe/mmm_stan","last_synced_at":"2025-05-06T18:34:59.428Z","repository":{"id":39670322,"uuid":"317177499","full_name":"sibylhe/mmm_stan","owner":"sibylhe","description":"Python/STAN Implementation of Multiplicative Marketing Mix Model, with deep dive into Adstock (carry-over effect), ROAS, and mROAS","archived":false,"fork":false,"pushed_at":"2022-05-28T05:45:02.000Z","size":2580,"stargazers_count":338,"open_issues_count":5,"forks_count":158,"subscribers_count":12,"default_branch":"main","last_synced_at":"2024-08-03T23:18:22.361Z","etag":null,"topics":["bayesian-regression","constrained-regression","marketing-mix-modeling","media-mix-modeling","pystan","roas","stan"],"latest_commit_sha":null,"homepage":"","language":"Jupyter Notebook","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sibylhe.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-11-30T09:46:54.000Z","updated_at":"2024-07-31T12:36:19.000Z","dependencies_parsed_at":"2022-08-09T15:22:55.531Z","dependency_job_id":null,"html_url":"https://github.com/sibylhe/mmm_stan","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sibylhe%2Fmmm_stan","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sibylhe%2Fmmm_stan/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sibylhe%2Fmmm_stan/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sibylhe%2Fmmm_stan/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sibylhe","download_url":"https://codeload.github.com/sibylhe/mmm_stan/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224521715,"owners_count":17325285,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bayesian-regression","constrained-regression","marketing-mix-modeling","media-mix-modeling","pystan","roas","stan"],"created_at":"2024-08-02T23:00:54.072Z","updated_at":"2024-11-13T20:31:40.329Z","avatar_url":"https://github.com/sibylhe.png","language":"Jupyter Notebook","funding_links":[],"categories":["Media / Marketing Mix Models"],"sub_categories":[],"readme":"# Python/STAN Implementation of Multiplicative Marketing Mix Model\nThe methodology of this project is based on [this paper](https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/46001.pdf) by Google, but is applied to a more complicated, real-world setting, where 1) there are 13 media channels and 46 control variables; 2) models are built in a stacked way.    \n     \n# 1. Introduction\nMarketing Mix Model,  or  Media Mix Model (MMM) is used by advertisers to measure how their media spending contributes to sales, so as to optimize future budget allocation. **ROAS** (return on ad spend) and **mROAS** (marginal ROAS) are the key metrics to look at. High ROAS indicates the channel is efficient, high mROAS means increasing spend in the channel will yield a high return based on current spending level.   \n    \n**Procedures**        \n\n1. Fit a regression model with priors on coefficients, using media channels' impressions (or spending) and control variables to predict sales;\n\n2. Decompose sales to each media channel's contribution. Channel contribution is calculated by comparing original sales and predicted sales upon removal of the channel;\n\n3. Compute ROAS and mROAS using channel contribution and spending. \n  \n  ​    \n\n**Intuition of MMM**    \n- Offline channel's influence is hard to track. E.g., a customer saw a TV ad, and made a purchase at store.\n- Media channels' influences are intertwined.    \n\n**Actual Customer Journey: Multiple Touchpoints**    \nA customer saw a product on TV \u003e clicked on a display ad \u003e clicked on a paid seach ad \u003e made a purchase of $30. In this case, 3 touchpoints contributed to the conversion, and they should all get credits for this conversion.    \n![actual customer journey - multiple touchpoints](https://tva1.sinaimg.cn/large/0081Kckwly1gl7xxyq508j30fw04smxe.jpg)    \n\n​    \n\n**What's trackable: Last Digital Touchpoint**    \nUsually, only the last digital touchpoint can be tracked. In this case, SEM, and it will get all credits for this conversion.    \n![what can be tracked - last touchpoint](https://tva1.sinaimg.cn/large/0081Kckwly1gl7xye27aaj307k04imx6.jpg)    \nSo, a good attribution model should take into account all the relevant variables leading to conversion.    \n\n​    \n\n## 1.1 Multiplicative MMM\nSince media channels work interactively, a multiplicative model structure is adopted:    \n![multiplicative MMM](https://tva1.sinaimg.cn/large/0081Kckwly1gl7wm7182rj309s02y0sm.jpg)    \nTake log of both sides, we get the linear form (log-log model):    \n![multiplicative MMM - linear form](https://tva1.sinaimg.cn/large/0081Kckwly1gl7wm7bxfyj30iz02wjrb.jpg)    \n\n**Constraints on Coefficients**\n\n1. Media coefficients are positive.\n\n2. Control variables like discount, macro economy, event/retail holiday are expected to have positive impact on sales, their coefficients should also be positive.\n\n   ​    \n\n## 1.2 Adstock\nMedia effect on sales may lag behind the original exposure and extend several weeks. The carry-over effect is modeled by Adstock:    \n![adstock transformation](https://tva1.sinaimg.cn/large/0081Kckwly1gl7wm86xyuj30hd04smx1.jpg)    \nL: length of the media effect    \nP: peak/delay of the media effect, how many weeks it's lagging behind first exposure    \nD: decay/retention rate of the media channel, concentration of the effect    \nThe media effect of current weeks is a weighted average of current week and previous (L− 1) weeks.    \n    \n**Adstock Example**    \n![adstock example](https://tva1.sinaimg.cn/large/0081Kckwly1gl7wmbuc9bj30gu085mx3.jpg)    \n\n​    \n\n**Adstock with Varying Decay**    \nThe larger the decay, the more scattered the effect.    \n![adstock parameter - decay](https://tva1.sinaimg.cn/large/0081Kckwly1gl7wmcleayj30o808wmxy.jpg)    \n**Adstock with Varying Length**    \nThe impact of length is relatively minor. In model training, length could be fixed to 8 weeks or a period long enough for the media effect to finish.    \n![adstock parameter - length](https://tva1.sinaimg.cn/large/0081Kckwly1gl7wmbj2d9j30o808wt9e.jpg)   \n      \n\n\n```python\nimport numpy as np\nimport pandas as pd\n\ndef apply_adstock(x, L, P, D):\n    '''\n    params:\n    x: original media variable, array\n    L: length\n    P: peak, delay in effect\n    D: decay, retain rate\n    returns:\n    array, adstocked media variable\n    '''\n    x = np.append(np.zeros(L-1), x)\n    \n    weights = np.zeros(L)\n    for l in range(L):\n        weight = D**((l-P)**2)\n        weights[L-1-l] = weight\n    \n    adstocked_x = []\n    for i in range(L-1, len(x)):\n        x_array = x[i-L+1:i+1]\n        xi = sum(x_array * weights)/sum(weights)\n        adstocked_x.append(xi)\n    adstocked_x = np.array(adstocked_x)\n    return adstocked_x\n```\n\n## 1.3 Diminishing Return    \nAfter a certain saturation point, increasing spend will yield diminishing marginal return, the channel will be losing efficiency as you keep overspending on it. The diminishing return is modeled by Hill function:    \n![Hill function](https://tva1.sinaimg.cn/large/0081Kckwly1gl7wm7xn1rj3081034742.jpg)    \nK: half saturation point    \nS: slope    \n    \n**Hill function with varying K and S**    \n![Hill function with varying K and S](https://tva1.sinaimg.cn/large/0081Kckwly1gl7wm6l26vj30ex0aeq3b.jpg)    \n\n​    \n\n```python\ndef hill_transform(x, ec, slope):\n    return 1 / (1 + (x / ec)**(-slope))\n```\n\n\n\n# 2. Model Specification \u0026 Implementation\n\n## Data    \nFour years' (209 weeks) records of sales, media impression and media spending at weekly level.   \n    \n**1. Media Variables**\n- Media Impression (prefix='mdip_'): impressions of 13 media channels: direct mail, insert, newspaper, digital audio, radio, TV, digital video, social media, online display, email, SMS, affiliates, SEM.\n- Media Spending (prefix='mdsp_'): spending of media channels.\n  \n\n**2. Control Variables**    \n- Macro Economy (prefix='me_'): CPI, gas price.\n- Markdown (prefix='mrkdn_'): markdown/discount.\n- Store Count ('st_ct')\n- Retail Holidays (prefix='hldy_'): one-hot encoded.\n- Seasonality (prefix='seas_'): month, with Nov and Dec further broken into to weeks. One-hot encoded.\n  \n\n**3. Sales Variable** ('sales')\n\n\n```python\ndf = pd.read_csv('data.csv')\n\n# 1. media variables\n# media impression\nmdip_cols=[col for col in df.columns if 'mdip_' in col]\n# media spending\nmdsp_cols=[col for col in df.columns if 'mdsp_' in col]\n\n# 2. control variables\n# macro economics variables\nme_cols = [col for col in df.columns if 'me_' in col]\n# store count variables\nst_cols = ['st_ct']\n# markdown/discount variables\nmrkdn_cols = [col for col in df.columns if 'mrkdn_' in col]\n# holiday variables\nhldy_cols = [col for col in df.columns if 'hldy_' in col]\n# seasonality variables\nseas_cols = [col for col in df.columns if 'seas_' in col]\nbase_vars = me_cols+st_cols+mrkdn_cols+va_cols+hldy_cols+seas_cols\n\n# 3. sales variables\nsales_cols =['sales']\n```\n\n## Model Architecture\nThe model is built in a stacked way. Three models are trained:   \n- Control Model\n- Marketing Mix Model\n- Diminishing Return Model    \n![mmm_stan_model_architecture](https://tva1.sinaimg.cn/large/0081Kckwly1gl7xsjhi8ej31150g7q59.jpg)\n\n​    \n\n## 2.1 Control Model / Base Sales Model    \n\n**Goal**: predict base sales (X_ctrl) as an input variable to MMM, this represents the baseline sales trend without any marketing activities.    \n![control model formular](https://tva1.sinaimg.cn/large/0081Kckwly1gl7xtspsg6j30bk055q2w.jpg)    \nX1: control variables positively related with sales, including macro economy, store count, markdown, holiday.    \nX2: control variables that may have either positive or negtive impact on sales: seasonality.    \nTarget variable: ln(sales).    \nThe variables are centralized by mean.\n    \n**Priors**    \n![control model priors](https://tva1.sinaimg.cn/large/0081Kckwly1gl7xub4ploj30ns07tglw.jpg)    \n\n​    \n\n\n```python\nimport pystan\nimport os\nos.environ['CC'] = 'gcc-10'\nos.environ['CXX'] = 'g++-10'\n\n# mean-centralize: sales, numeric base_vars\ndf_ctrl, sc_ctrl = mean_center_trandform(df, ['sales']+me_cols+st_cols+mrkdn_cols)\ndf_ctrl = pd.concat([df_ctrl, df[hldy_cols+seas_cols]], axis=1)\n\n# variables positively related to sales: macro economy, store count, markdown, holiday\npos_vars = [col for col in base_vars if col not in seas_cols]\nX1 = df_ctrl[pos_vars].values\n\n# variables may have either positive or negtive impact on sales: seasonality\npn_vars = seas_cols\nX2 = df_ctrl[pn_vars].values\n\nctrl_data = {\n    'N': len(df_ctrl),\n    'K1': len(pos_vars), \n    'K2': len(pn_vars), \n    'X1': X1,\n    'X2': X2, \n    'y': df_ctrl['sales'].values,\n    'max_intercept': min(df_ctrl['sales'])\n}\n\nctrl_code1 = '''\ndata {\n  int N; // number of observations\n  int K1; // number of positive predictors\n  int K2; // number of positive/negative predictors\n  real max_intercept; // restrict the intercept to be less than the minimum y\n  matrix[N, K1] X1;\n  matrix[N, K2] X2;\n  vector[N] y; \n}\n\nparameters {\n  vector\u003clower=0\u003e[K1] beta1; // regression coefficients for X1 (positive)\n  vector[K2] beta2; // regression coefficients for X2\n  real\u003clower=0, upper=max_intercept\u003e alpha; // intercept\n  real\u003clower=0\u003e noise_var; // residual variance\n}\n\nmodel {\n  // Define the priors\n  beta1 ~ normal(0, 1); \n  beta2 ~ normal(0, 1); \n  noise_var ~ inv_gamma(0.05, 0.05 * 0.01);\n  // The likelihood\n  y ~ normal(X1*beta1 + X2*beta2 + alpha, sqrt(noise_var));\n}\n'''\n\nsm1 = pystan.StanModel(model_code=ctrl_code1, verbose=True)\nfit1 = sm1.sampling(data=ctrl_data, iter=2000, chains=4)\nfit1_result = fit1.extract()\n```\n    \nMAPE of control model: 8.63%    \nExtract control model parameters from the fit object and predict base sales -\u003e df['base_sales']    \n\n## 2.2 Marketing Mix Model\n\n**Goal**:\n\n- Find appropriate adstock parameters for media channels;\n- Decompose sales to media channels' contribution (and non-marketing contribution).\n\n![marketing mix model formular](https://tva1.sinaimg.cn/large/0081Kckwly1gl7xuxgp98j30l206ddfz.jpg)    \nL: length of media impact    \nP: peak of media impact    \nD: decay of media impact    \nX: adstocked media impression variables and base sales    \nTarget variable: ln(sales)    \nVariables are centralized by mean.\n    \n**Priors**    \n![marketing mix model priors](https://tva1.sinaimg.cn/large/0081Kckwly1gl7xvel601j30ns09ddg7.jpg) \n     \n```python\ndf_mmm, sc_mmm = mean_log1p_trandform(df, ['sales', 'base_sales'])\nmu_mdip = df[mdip_cols].apply(np.mean, axis=0).values\nmax_lag = 8\nnum_media = len(mdip_cols)\n# padding zero * (max_lag-1) rows\nX_media = np.concatenate((np.zeros((max_lag-1, num_media)), df[mdip_cols].values), axis=0)\nX_ctrl = df_mmm['base_sales'].values.reshape(len(df),1)\nmodel_data2 = {\n    'N': len(df),\n    'max_lag': max_lag, \n    'num_media': num_media,\n    'X_media': X_media, \n    'mu_mdip': mu_mdip,\n    'num_ctrl': X_ctrl.shape[1],\n    'X_ctrl': X_ctrl, \n    'y': df_mmm['sales'].values\n}\n\nmodel_code2 = '''\nfunctions {\n  // the adstock transformation with a vector of weights\n  real Adstock(vector t, row_vector weights) {\n    return dot_product(t, weights) / sum(weights);\n  }\n}\ndata {\n  // the total number of observations\n  int\u003clower=1\u003e N;\n  // the vector of sales\n  real y[N];\n  // the maximum duration of lag effect, in weeks\n  int\u003clower=1\u003e max_lag;\n  // the number of media channels\n  int\u003clower=1\u003e num_media;\n  // matrix of media variables\n  matrix[N+max_lag-1, num_media] X_media;\n  // vector of media variables' mean\n  real mu_mdip[num_media];\n  // the number of other control variables\n  int\u003clower=1\u003e num_ctrl;\n  // a matrix of control variables\n  matrix[N, num_ctrl] X_ctrl;\n}\nparameters {\n  // residual variance\n  real\u003clower=0\u003e noise_var;\n  // the intercept\n  real tau;\n  // the coefficients for media variables and base sales\n  vector\u003clower=0\u003e[num_media+num_ctrl] beta;\n  // the decay and peak parameter for the adstock transformation of\n  // each media\n  vector\u003clower=0,upper=1\u003e[num_media] decay;\n  vector\u003clower=0,upper=ceil(max_lag/2)\u003e[num_media] peak;\n}\ntransformed parameters {\n  // the cumulative media effect after adstock\n  real cum_effect;\n  // matrix of media variables after adstock\n  matrix[N, num_media] X_media_adstocked;\n  // matrix of all predictors\n  matrix[N, num_media+num_ctrl] X;\n  \n  // adstock, mean-center, log1p transformation\n  row_vector[max_lag] lag_weights;\n  for (nn in 1:N) {\n    for (media in 1 : num_media) {\n      for (lag in 1 : max_lag) {\n        lag_weights[max_lag-lag+1] \u003c- pow(decay[media], (lag - 1 - peak[media]) ^ 2);\n      }\n     cum_effect \u003c- Adstock(sub_col(X_media, nn, media, max_lag), lag_weights);\n     X_media_adstocked[nn, media] \u003c- log1p(cum_effect/mu_mdip[media]);\n    }\n  X \u003c- append_col(X_media_adstocked, X_ctrl);\n  } \n}\nmodel {\n  decay ~ beta(3,3);\n  peak ~ uniform(0, ceil(max_lag/2));\n  tau ~ normal(0, 5);\n  for (i in 1 : num_media+num_ctrl) {\n    beta[i] ~ normal(0, 1);\n  }\n  noise_var ~ inv_gamma(0.05, 0.05 * 0.01);\n  y ~ normal(tau + X * beta, sqrt(noise_var));\n}\n'''\n\nsm2 = pystan.StanModel(model_code=model_code2, verbose=True)\nfit2 = sm2.sampling(data=model_data2, iter=1000, chains=3)\nfit2_result = fit2.extract()\n```\n    \n**Distribution of Media Coefficients**    \nred line: mean, green line: median    \n![media coefficients distribution](https://tva1.sinaimg.cn/large/0081Kckwly1gl7xptfcjhj30tk0nvaby.jpg)\n\n### Decompose sales to media channels' contribution\n\nEach media channel's contribution = total sales - sales upon removal of the channel    \nIn the previous model fitting step, parameters of the log-log model have been found:    \n![mmm_stan_decompose_contrib1](https://tva1.sinaimg.cn/large/0081Kckwly1gl7wmb2h4xj30f502ymx2.jpg)    \nPlug them into the multiplicative model:    \n![mmm_stan_decompose_contrib2](https://tva1.sinaimg.cn/large/0081Kckwly1gl7wmang1vj30b403ajr9.jpg)    \n![mmm_stan_decompose_contrib3](https://tva1.sinaimg.cn/large/0081Kckwly1gl7wmabfp4j30j309wwem.jpg)    \n\n\n```python\n# decompose sales to media contribution\nmc_df = mmm_decompose_media_contrib(mmm, df, y_true=df['sales_ln'])\nadstock_params = mmm['adstock_params']\nmc_pct, mc_pct2 = calc_media_contrib_pct(mc_df, period=52)\n```\n    \nRMSE (log-log model):  0.04977    \nMAPE (multiplicative model):  15.71%    \n    \n**Adstock Parameters**    \n```python\n{'dm': {'L': 8, 'P': 0.8147057071636012, 'D': 0.5048365638721349},\n 'inst': {'L': 8, 'P': 0.6339321363933637, 'D': 0.40532404247040194},\n 'nsp': {'L': 8, 'P': 1.1076944292039324, 'D': 0.4612905130128658},\n 'auddig': {'L': 8, 'P': 1.8834110997525702, 'D': 0.5117823761413419},\n 'audtr': {'L': 8, 'P': 1.9892680621155827, 'D': 0.5046141055524362},\n 'vidtr': {'L': 8, 'P': 0.05520253973872224, 'D': 0.0846136627657064},\n 'viddig': {'L': 8, 'P': 1.862571613911107, 'D': 0.5074553132446618},\n 'so': {'L': 8, 'P': 1.7027472358912694, 'D': 0.5046386226501091},\n 'on': {'L': 8, 'P': 1.4169662215350334, 'D': 0.4907407637366824},\n 'em': {'L': 8, 'P': 1.0590065753144235, 'D': 0.44420264450045377},\n 'sms': {'L': 8, 'P': 1.8487648735160152, 'D': 0.5090970201714644},\n 'aff': {'L': 8, 'P': 0.6018657109295106, 'D': 0.39889023002777724},\n 'sem': {'L': 8, 'P': 1.34945185610011, 'D': 0.47875793676213835}}\n```\n**Notes**:\n- For SEM, P=1.3, D=0.48 does not make a lot of sense to me, because SEM is expected to have immediate and concentrated impact (P=0, low decay). Same with online display.    \n- Try more specific priors in future model.\n\n​    \n\n## 2.3 Diminishing Return Model    \n\n**Goal**: for each channel, find the relationship (fit a Hill function) between spending and contribution, so that ROAS and marginal ROAS can be calculated.    \n![diminishing return model formular](https://tva1.sinaimg.cn/large/0081Kckwly1gl7xw5vh44j30bx04ajrc.jpg)    \nx: adstocked media channel spending   \nK: half saturation    \nS: shape    \nTarget variable: the media channel's contribution    \nVariables are centralized by mean.\n    \n**Priors**    \n![diminishing return model priors](https://tva1.sinaimg.cn/large/0081Kckwly1gl7xwpdt0vj30nu06hjrh.jpg)          \n    \n\n```python\ndef create_hill_model_data(df, mc_df, adstock_params, media):\n    y = mc_df['mdip_'+media].values\n    L, P, D = adstock_params[media]['L'], adstock_params[media]['P'], adstock_params[media]['D']\n    x = df['mdsp_'+media].values\n    x_adstocked = apply_adstock(x, L, P, D)\n    # centralize\n    mu_x, mu_y = x_adstocked.mean(), y.mean()\n    sc = {'x': mu_x, 'y': mu_y}\n    x = x_adstocked/mu_x\n    y = y/mu_y\n        \n    model_data = {\n        'N': len(y),\n        'y': y,\n        'X': x\n    }\n    return model_data, sc\n\nmodel_code3 = '''\nfunctions {\n  // the Hill function\n  real Hill(real t, real ec, real slope) {\n  return 1 / (1 + (t / ec)^(-slope));\n  }\n}\n\ndata {\n  // the total number of observations\n  int\u003clower=1\u003e N;\n  // y: vector of media contribution\n  vector[N] y;\n  // X: vector of adstocked media spending\n  vector[N] X;\n}\n\nparameters {\n  // residual variance\n  real\u003clower=0\u003e noise_var;\n  // regression coefficient\n  real\u003clower=0\u003e beta_hill;\n  // ec50 and slope for Hill function of the media\n  real\u003clower=0,upper=1\u003e ec;\n  real\u003clower=0\u003e slope;\n}\n\ntransformed parameters {\n  // a vector of the mean response\n  vector[N] mu;\n  for (i in 1:N) {\n    mu[i] \u003c- beta_hill * Hill(X[i], ec, slope);\n  }\n}\n\nmodel {\n  slope ~ gamma(3, 1);\n  ec ~ beta(2, 2);\n  beta_hill ~ normal(0, 1);\n  noise_var ~ inv_gamma(0.05, 0.05 * 0.01); \n  y ~ normal(mu, sqrt(noise_var));\n}\n'''\n\n# train hill models for all media channels\nsm3 = pystan.StanModel(model_code=model_code3, verbose=True)\nhill_models = {}\nto_train = ['dm', 'inst', 'nsp', 'auddig', 'audtr', 'vidtr', 'viddig', 'so', 'on', 'sem']\nfor media in to_train:\n    print('training for media: ', media)\n    hill_model = train_hill_model(df, mc_df, adstock_params, media, sm3)\n    hill_models[media] = hill_model\n```\n    \n**Diminishing Return Model (Fitted Hill Curve)**    \n![fitted hill](https://tva1.sinaimg.cn/large/0081Kckwly1gl7wm62suqj30sv0pe0v2.jpg)    \n\n### Calculate overall ROAS and weekly ROAS\n- Overall ROAS = total media contribution / total media spending\n- Weekly ROAS = weekly media contribution / weekly media spending\n    \n**Distribution of Weekly ROAS** (Recent 1 Year)    \nred line: mean, green line: median    \n![weekly roas](https://tva1.sinaimg.cn/large/0081Kckwly1gl7wm9x0s0j30te0jcwft.jpg)\n    \n### Calculate mROAS\nMarginal ROAS represents the return of incremental spending based on current spending. For example, I've spent $100 on SEM, how much will the next $1 bring.    \nmROAS is calculated by increasing the current spending level by 1%, the incremental channel contribution over incremental channel spending.    \n1. Current spending level ```cur_sp``` is an array of weekly spending in a given period.    \nNext spending level ```next_sp``` is increasing ```cur_sp``` by 1%.\n2. Plug ```cur_sp``` and ```next_sp``` into the Hill function:    \nCurrent media contribution ```cur_mc``` = beta * Hill(```cur_sp```)    \nNext-level media contribution ```next_mc``` = beta * Hill(```next_sp```)    \n3. **mROAS** = (sum(```next_mc```) - sum(```cur_mc```)) / sum(0.01 * ```cur_sp```)\n    \n\n# 3. Results \u0026 Marketing Budget Optimization    \n**Media Channel Contribution**    \n80% sales are contributed by non-marketing factors, marketing channels contributed 20% sales.    \n![marketing contribution plot](https://tva1.sinaimg.cn/large/0081Kckwly1gl7xrk9m6ej31f90k0tdr.jpg)    \nTop contributors: TV, affiliates, SEM    \n![media contribution percentage plot](https://tva1.sinaimg.cn/large/0081Kckwly1gl7xqzgkg1j30qy0d43yz.jpg)    \n**ROAS**    \nHigh ROAS: TV, insert, online display    \n![media channels contribution roas plot](https://tva1.sinaimg.cn/large/0081Kckwly1gl7xqf7ytqj30yn0hz0tt.jpg)    \n**mROAS**    \nHigh mROAS: TV, insert, radio, online display    \n![media channels roas mroas plot](https://tva1.sinaimg.cn/large/0081Kckwly1gl7xrzbo4bj30ys0hd3zj.jpg)    \nNote: trivial channels: newspaper, digital audio, digital video, social (spending/impression too small to be qualified, so that their results are not trustworthy).    \n\n## Q\u0026A\nPlease check this running list of [FAQ](https://github.com/sibylhe/mmm_stan/discussions/7). If you have questions, comments, suggestions, and practical problems (when applying this script to your datasets) that are unaddressed in this list, feel free to [open a discussion](https://github.com/sibylhe/mmm_stan/discussions). You may also comment on my [Medium article](https://towardsdatascience.com/python-stan-implementation-of-multiplicative-marketing-mix-model-with-deep-dive-into-adstock-a7320865b334).    \nFor bugs/errors in code, please open an issue. An issue is expected to be addressed in the following weekend.\n\n\n## References\n\n[1] Bayesian Methods for Media Mix Modeling with Carryover and Shape Effects. https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/46001.pdf    \n[2] STAN tutorials:    \nPrior Choice Recommendations. https://github.com/stan-dev/stan/wiki/Prior-Choice-Recommendations    \nPystan Documentation. https://www.cnpython.com/pypi/pystan    \nPystan Workflow. https://mc-stan.org/users/documentation/case-studies/pystan_workflow.html    \nA quick-start introduction to Stan for economists. https://nbviewer.jupyter.org/github/QuantEcon/QuantEcon.notebooks/blob/master/IntroToStan_basics_workflow.ipynb    \nHMC sampling. https://education.illinois.edu/docs/default-source/carolyn-anderson/edpsy590ca/lectures/9-hmc-and-stan/hmc_n_stan_post.pdf       \n    \n**If you like this project, please leave a :star2: for motivation:)**\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsibylhe%2Fmmm_stan","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsibylhe%2Fmmm_stan","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsibylhe%2Fmmm_stan/lists"}