Awesome Marketing Science Guide

reason-machines/marketing-skills/skills/awesome-marketing-science-guide

by reason-machines97301f4188a7152bed5a121cbd4763ba9ac21afeNo licenseListed Oct 9, 2026Updated Oct 9, 2026

Guide to curated marketing science resources including MMM, geo experiments, causal inference, attribution, and Bayesian methods

AI-generated overview

Guides users through curated marketing science resources and libraries for MMM, geo experiments, attribution and causal inference.

What it does
This skill explains the Awesome Marketing Science resource collection, a curated list of open-source libraries, papers and methodologies for marketing measurement. It walks through categories such as media mix modeling, geo experimentation, multi-touch attribution, causal inference and A/B testing, showing example code for tools like Robyn, LightweightMMM, PyMC-Marketing, GeoLift, CausalPy and DoWhy. It also outlines common workflows, configuration practices and troubleshooting steps for building marketing measurement systems.
When to use it
Use it when you need orientation on marketing measurement resources, such as finding MMM libraries, planning geo incrementality tests, or choosing attribution and causal inference tools. It suits questions about incrementality testing, Bayesian marketing measurement frameworks and related methodology.
Requirements
No scripts are included; it is instructions only. The examples reference R and Python packages (for example Robyn, LightweightMMM, PyMC-Marketing, GeoLift, CausalPy, DoWhy, EconML, pychattr, ChannelAttribution) and optional cloud credentials such as GOOGLE_APPLICATION_CREDENTIALS, GCP_PROJECT_ID and GCP_BUCKET, but none are required to read the guidance.

Awesome Marketing Science Guide

Skill by ara.so — Marketing Skills collection.

This skill provides guidance on the Awesome Marketing Science resource collection — a curated list of open-source libraries, papers, and methodologies for marketing measurement, including Media Mix Models (MMM), geo incrementality testing, multi-touch attribution (MTA), causal inference, and Bayesian approaches.

Overview

Awesome Marketing Science is a comprehensive resource hub covering:

  • Media Mix Modeling (MMM): Measure channel-level ROI and optimize budget allocation
  • Geo Experimentation: Test incrementality through matched markets and geo lift
  • Attribution: Multi-touch and algorithmic attribution models
  • Causal Inference: Difference-in-differences, synthetic control, uplift modeling
  • A/B Testing & Experimentation: Online controlled experiments and variance reduction
  • Ghost Ads & Platform Incrementality: Measure true ad effectiveness

Repository Structure

The collection is organized into:

  1. Start Here / Must Read: Essential papers and packages for building measurement foundations
  2. Open Source Libraries: Production-ready code organized by discipline
  3. Papers & Research: Academic and industry research
  4. Blogs & Tutorials: Practical implementation guides

Key Libraries by Category

Media Mix Modeling (MMM)

Robyn (Meta)
r
# Installinstall.packages("Robyn")# Or from sourceremotes::install_github("facebookexperimental/Robyn/R")
library(Robyn)
# Load datadata("dt_simulated_weekly")data("dt_prophet_holidays")
# Set hyperparametershyperparameters <- list(  adstock = c("geometric"),  prophet_vars = c("trend", "season", "holiday"),  prophet_country = "US")
# Run modelrobyn_object <- robyn_run(  dt_input = dt_simulated_weekly,  dt_holidays = dt_prophet_holidays,  hyperparameters = hyperparameters,  cores = 4)
# Get budget allocationallocator <- robyn_allocator(  robyn_object = robyn_object,  scenario = "max_response_expected_spend")
LightweightMMM (Google)
python
# Install# pip install lightweight-mmm
from lightweight_mmm import lightweight_mmmfrom lightweight_mmm import preprocessingimport jax.numpy as jnp
# Prepare datamedia_data = jnp.array(media_data_raw)  # Shape: (n_time_periods, n_media_channels)target = jnp.array(revenue_data)  # Shape: (n_time_periods,)
# Scale datamedia_data_scaled, scaler = preprocessing.CustomScaler().fit_transform(media_data)
# Initialize and train modelmmm = lightweight_mmm.LightweightMMM()mmm.fit(    media=media_data_scaled,    target=target,    number_warmup=1000,    number_samples=1000,    media_prior=jnp.ones(n_channels))
# Get channel contributionscontribution = mmm.get_posterior_metrics()["contribution_per_channel"]
PyMC-Marketing
python
# Install# pip install pymc-marketing
import pymc as pmfrom pymc_marketing.mmm import MMM
# Initialize modelmmm = MMM(    date_column="date",    channel_columns=["tv", "radio", "digital"],    adstock_max_lag=8,    validate_data=True)
# Fit modelmmm.fit(    data=marketing_df,    target_column="sales",    tune=1000,    draws=1000)
# Optimize budgetoptimized = mmm.optimize_budget(    total_budget=100000,    budget_bounds={"tv": (10000, 50000), "radio": (5000, 30000)})
# Get ROI curvesroi_curves = mmm.compute_channel_roi()

Geo Experimentation

GeoLift (Meta)
r
# Installremotes::install_github("facebookincubator/GeoLift")
library(GeoLift)
# Load datadata(GeoLift_Test)
# Find best test marketsbest_markets <- GeoLiftMarketSelection(  data = GeoLift_Test,  treatment_locations = c("chicago"),  N = 5,  # Number of control markets  Y_id = "Y",  location_id = "location",  time_id = "time")
# Power analysispower <- GeoLiftPower(  data = GeoLift_Test,  locations = best_markets$BestControl,  treatment_locations = c("chicago"),  effect_size = seq(0, 0.25, 0.05),  lookback_window = 52)
# After test runs, analyze resultsresults <- GeoLift(  Y_id = "Y",  data = GeoLift_Test,  locations = best_markets$BestControl,  treatment_locations = c("chicago"),  treatment_start_time = 90,  treatment_end_time = 105)
# Plot resultsplot(results)
Matched Markets (Google)
python
# Install# pip install matched-markets
from matched_markets.methodology import trimmed_match
# Prepare datageo_data = {    'geos': ['geo1', 'geo2', 'geo3', ...],    'response': [100, 150, 120, ...],    'spend': [50, 75, 60, ...]}
# Design testdesign = trimmed_match.TrimmedMatch(    data=geo_data,    treatment_geos=['geo1', 'geo2'],    exclude_cooldown_period=7)
# Run matchingdesign.fit()
# Estimate treatment effectiroas = design.estimate_treatment_effect(    post_treatment_response=post_data['response'],    post_treatment_spend=post_data['spend'])
print(f"Incremental ROAS: {iroas.point_estimate:.2f}")print(f"95% CI: [{iroas.ci_lower:.2f}, {iroas.ci_upper:.2f}]")

Multi-Touch Attribution

PyChattr
python
# Install# pip install pychattr
from pychattr.channel_attribution import MarkovModel
# Journey data format: each row is a conversion path# format: 'channel1 > channel2 > channel3'journeys = [    'paid_search > display > direct',    'social > email > direct',    'direct',    'paid_search > direct']
conversions = [1, 1, 0, 1]revenues = [100, 150, 0, 80]
# Fit Markov modelmarkov = MarkovModel(    paths=journeys,    conversions=conversions,    revenues=revenues,    order=1  # First-order Markov chain)
markov.fit()
# Get attribution resultsattribution = markov.attribution()print(attribution)
# Compare to heuristic modelsfrom pychattr.channel_attribution import HeuristicModel
heuristic = HeuristicModel(    paths=journeys,    conversions=conversions,    revenues=revenues)
# Get last-touch, first-touch, linearresults = heuristic.attribution(    heuristic_type=['last_touch', 'first_touch', 'linear'])
ChannelAttribution (R)
r
# Installinstall.packages("ChannelAttribution")
library(ChannelAttribution)
# Prepare datadata <- data.frame(  path = c("c1 > c2 > c3", "c1 > c3", "c2 > c3"),  conversions = c(2, 1, 3),  revenue = c(200, 100, 300))
# Markov model attributionmarkov_model <- markov_model(  data,  var_path = "path",  var_conv = "conversions",  var_value = "revenue",  order = 1,  sep = " > ")
print(markov_model)
# Heuristic modelsheuristic <- heuristic_models(  data,  var_path = "path",  var_conv = "conversions",  var_value = "revenue",  sep = " > ")
# Compare removal effectsremoval <- markov_model(  data,  var_path = "path",  var_conv = "conversions",  var_value = "revenue",  order = 1,  out_more = TRUE)

Causal Inference

CausalPy
python
# Install# pip install CausalPy
import pandas as pdimport causalpy as cp
# Synthetic control examplesc = cp.pymc_models.SyntheticControl(    data=data,    treatment_time=70,    formula="actual ~ 0 + a + b + c + d + e + f + g",    model=cp.pymc_models.WeightedSumFitter(        sample_kwargs={"draws": 2000, "target_accept": 0.95}    ))
# Get resultsresult = sc.fit()
# Plotresult.plot()
# Get causal impactimpact = result.summary()print(f"Average Treatment Effect: {impact['causal_impact'].mean():.2f}")
DoWhy
python
# Install# pip install dowhy
import dowhyfrom dowhy import CausalModel
# Define causal modelmodel = CausalModel(    data=df,    treatment='ad_spend',    outcome='revenue',    common_causes=['seasonality', 'competitor_activity'],    instruments=['budget_shock'])
# Identify causal effectidentified_estimand = model.identify_effect(proceed_when_unidentifiable=True)
# Estimate causal effectestimate = model.estimate_effect(    identified_estimand,    method_name="backdoor.propensity_score_matching")
print(f"Causal Effect: {estimate.value}")
# Refute resultsrefute = model.refute_estimate(    identified_estimand,    estimate,    method_name="random_common_cause")
EconML (Microsoft)
python
# Install# pip install econml
from econml.dml import LinearDMLfrom sklearn.ensemble import GradientBoostingRegressor
# Double ML for heterogeneous treatment effectsdml = LinearDML(    model_y=GradientBoostingRegressor(),    model_t=GradientBoostingRegressor())
# Fit modeldml.fit(    Y=outcomes,  # Revenue    T=treatment,  # Ad spend    X=features   # Customer features, context)
# Get treatment effect for new datatreatment_effect = dml.effect(X_test)
# Get confidence intervalslb, ub = dml.effect_interval(X_test, alpha=0.05)

A/B Testing & Experimentation

CUPED Implementation
python
import numpy as npfrom scipy import stats
def cuped_variance_reduction(    pre_experiment_metric,    experiment_metric_control,    experiment_metric_treatment):    """    Implement CUPED variance reduction using pre-experiment data    Based on: https://exp-platform.com/Documents/2013-02-CUPED-ImprovingSensitivityOfControlledExperiments.pdf    """        # Combine control and treatment    Y = np.concatenate([experiment_metric_control, experiment_metric_treatment])    X = np.concatenate([pre_experiment_metric[:len(experiment_metric_control)],                        pre_experiment_metric[-len(experiment_metric_treatment):]])        # Compute theta (optimal coefficient)    theta = np.cov(X, Y)[0, 1] / np.var(X)        # Adjust metrics    Y_adjusted = Y - theta * (X - np.mean(X))        # Split back    n_control = len(experiment_metric_control)    Y_adj_control = Y_adjusted[:n_control]    Y_adj_treatment = Y_adjusted[n_control:]        # Compute variance reduction    original_var = np.var(Y)    adjusted_var = np.var(Y_adjusted)    variance_reduction = 1 - (adjusted_var / original_var)        # Run t-test    t_stat, p_value = stats.ttest_ind(Y_adj_treatment, Y_adj_control)        return {        'treatment_effect': np.mean(Y_adj_treatment) - np.mean(Y_adj_control),        'p_value': p_value,        'variance_reduction': variance_reduction,        'theta': theta    }

Common Workflows

Building a Marketing Measurement Stack

python
"""Unified measurement approach combining MMM, attribution, and experiments"""import pandas as pdfrom pymc_marketing.mmm import MMMfrom lightweight_mmm import preprocessing
class UnifiedMeasurement:    def __init__(self):        self.mmm = None        self.attribution_model = None        self.geo_test_results = {}        def fit_mmm(self, aggregate_data):        """Fit media mix model on aggregate time series"""        self.mmm = MMM(            date_column="date",            channel_columns=["tv", "radio", "digital", "social"],            adstock_max_lag=8        )                self.mmm.fit(            data=aggregate_data,            target_column="revenue",            tune=1000,            draws=1000        )                return self.mmm        def calibrate_with_experiments(self, experiment_results):        """        Use geo experiment results to calibrate MMM        Implements approach from Meta's Robyn        """        for channel, result in experiment_results.items():            # Get experiment lift            exp_lift = result['incremental_revenue'] / result['spend']                        # Get MMM prediction for same period            mmm_lift = self.mmm.compute_channel_roi()[channel]                        # Calculate calibration factor            calibration = exp_lift / mmm_lift                        # Store for adjustment            self.geo_test_results[channel] = {                'experiment_roi': exp_lift,                'model_roi': mmm_lift,                'calibration_factor': calibration            }        def optimize_budget(self, total_budget, constraints):        """Optimize budget allocation using calibrated model"""                # Get base optimization        base_opt = self.mmm.optimize_budget(            total_budget=total_budget,            budget_bounds=constraints        )                # Adjust with experimental calibration        calibrated_allocation = {}        for channel, amount in base_opt.items():            if channel in self.geo_test_results:                factor = self.geo_test_results[channel]['calibration_factor']                calibrated_allocation[channel] = amount * factor            else:                calibrated_allocation[channel] = amount                # Normalize to budget        total = sum(calibrated_allocation.values())        calibrated_allocation = {            k: v * (total_budget / total)             for k, v in calibrated_allocation.items()        }                return calibrated_allocation

Running Geo Experiments End-to-End

python
"""Complete geo experiment workflow"""
def design_geo_test(historical_data, treatment_geos, test_duration_weeks):    """Design matched-market test"""    from matched_markets.methodology import trimmed_match        # Find best control geos    design = trimmed_match.TrimmedMatch(        data=historical_data,        treatment_geos=treatment_geos,        pre_treatment_periods=52  # 1 year lookback    )        design.fit()        # Power analysis    power_results = design.power_analysis(        effect_sizes=np.arange(0.05, 0.30, 0.05),        test_duration=test_duration_weeks    )        return design, power_results
def analyze_geo_test(design, post_treatment_data):    """Analyze completed test"""        # Estimate treatment effect    result = design.estimate_treatment_effect(        post_treatment_response=post_treatment_data['revenue'],        post_treatment_spend=post_treatment_data['spend']    )        # Calculate iROAS    iroas = result.incremental_revenue / result.incremental_spend        # Check statistical significance    is_significant = result.p_value < 0.05        return {        'iroas': iroas,        'incremental_revenue': result.incremental_revenue,        'p_value': result.p_value,        'confidence_interval': (result.ci_lower, result.ci_upper),        'is_significant': is_significant    }

Configuration & Best Practices

MMM Hyperparameter Tuning

python
# Example: Grid search for adstock and saturation parametersfrom lightweight_mmm import optimize_media
# Define parameter rangesadstock_range = {    'tv': (0.3, 0.8),    'digital': (0.1, 0.5),    'radio': (0.2, 0.6)}
saturation_range = {    'tv': (0.5, 1.5),    'digital': (0.3, 1.0),    'radio': (0.4, 1.2)}
# Run optimizationbest_params = optimize_media.optimize_hyperparameters(    media_data=media_data,    target=target,    adstock_ranges=adstock_range,    saturation_ranges=saturation_range,    n_iter=100)

Prior Elicitation for Bayesian MMM

python
"""Prior elicitation using domain expertiseBased on: https://github.com/louismagowan/mmm-prior-elicitation"""
def elicit_channel_priors(historical_roas_estimates):    """    Convert historical ROAS ranges into prior distributions    """    priors = {}        for channel, roas_range in historical_roas_estimates.items():        # Convert ROAS range to lognormal parameters        lower, upper = roas_range        mean_log = (np.log(lower) + np.log(upper)) / 2        std_log = (np.log(upper) - np.log(lower)) / 4  # ~95% in range                priors[channel] = {            'distribution': 'lognormal',            'mu': mean_log,            'sigma': std_log        }        return priors
# Example usageexpert_estimates = {    'tv': (1.5, 3.0),      # TV ROAS between 1.5x and 3.0x    'digital': (2.0, 5.0),    'radio': (1.0, 2.5)}
priors = elicit_channel_priors(expert_estimates)

Troubleshooting

MMM Model Not Converging

python
# Check for common issuesimport arviz as az
# 1. Check R-hat (should be < 1.01)def check_convergence(mmm_trace):    summary = az.summary(mmm_trace)    problematic = summary[summary['r_hat'] > 1.01]    if len(problematic) > 0:        print("Convergence issues for:", problematic.index.tolist())        print("Solution: Increase warmup/draws or reparameterize")    return problematic
# 2. Check for scaling issuesdef check_data_scaling(media_data, target):    for i, channel in enumerate(media_data.columns):        ratio = target.std() / media_data.iloc[:, i].std()        if ratio > 100 or ratio < 0.01:            print(f"{channel}: scaling issue (ratio: {ratio:.2f})")            print("Solution: Normalize or standardize data")
# 3. Increase samplingmmm.fit(    media=media_data,    target=target,    number_warmup=2000,  # Increased from 1000    number_samples=2000,    number_chains=4)

Geo Test Power Issues

python
def diagnose_low_power(design, target_power=0.8):    """    Diagnose why geo test has low power    """    diagnostics = {}        # Check match quality    diagnostics['match_quality'] = design.match_quality_score    if diagnostics['match_quality'] < 0.7:        print("Poor match quality. Consider:")        print("- Increasing lookback window")        print("- Adding more control geos")        print("- Using time-based regression instead of matching")        # Check historical variance    diagnostics['outcome_cv'] = design.historical_cv    if diagnostics['outcome_cv'] > 0.3:        print("High outcome variance. Consider:")        print("- Longer test duration")        print("- Larger treatment effect")        print("- CUPED-style variance reduction")        # Check sample size    diagnostics['n_geos'] = len(design.treatment_geos) + len(design.control_geos)    if diagnostics['n_geos'] < 10:        print("Small sample size. Consider:")        print("- Aggregating smaller geos")        print("- Using synthetic control instead")        return diagnostics

Attribution Model Interpretation

python
def compare_attribution_models(journey_data):    """    Compare multiple attribution approaches    """    from pychattr.channel_attribution import MarkovModel, HeuristicModel        results = {}        # Heuristic models    heuristic = HeuristicModel(        paths=journey_data['paths'],        conversions=journey_data['conversions'],        revenues=journey_data['revenues']    )        results['last_touch'] = heuristic.attribution(heuristic_type='last_touch')    results['first_touch'] = heuristic.attribution(heuristic_type='first_touch')    results['linear'] = heuristic.attribution(heuristic_type='linear')        # Markov models    for order in [1, 2]:        markov = MarkovModel(            paths=journey_data['paths'],            conversions=journey_data['conversions'],            revenues=journey_data['revenues'],            order=order        )        markov.fit()        results[f'markov_order_{order}'] = markov.attribution()        # Compare    comparison = pd.DataFrame(results)    print("\nAttribution Comparison:")    print(comparison)        # Flag large discrepancies    for channel in comparison.index:        channel_results = comparison.loc[channel]        if channel_results.std() / channel_results.mean() > 0.5:            print(f"\nWarning: Large variation for {channel}")            print("Consider: combining with MMM or incrementality tests")        return comparison

Environment Variables

For projects using external APIs or cloud resources:

bash
# BigQuery for data accessexport GOOGLE_APPLICATION_CREDENTIALS="/path/to/credentials.json"
# For cloud-based MMM runsexport GCP_PROJECT_ID="your-project-id"export GCP_BUCKET="your-bucket-name"
# For distributed computingexport STAN_NUM_THREADS=4export PYMC_NUM_CHAINS=4

Additional Resources

  • Documentation: Check each library's GitHub repository for detailed docs
  • Community: Join marketing science Slack communities and subreddits
  • Papers: Start with the "Must Read" section for foundational understanding
  • Tutorials: PyMC, Google, and Meta publish extensive tutorials on their blogs

Integration Examples

Connecting MMM with Data Warehouse

python
from google.cloud import bigqueryimport pandas as pd
def load_marketing_data_for_mmm(project_id, lookback_days=730):    """    Load aggregated marketing data from BigQuery    """    client = bigquery.Client(project=project_id)        query = f"""    SELECT        DATE(timestamp) as date,        SUM(CASE WHEN channel = 'TV' THEN spend ELSE 0 END) as tv_spend,        SUM(CASE WHEN channel = 'Digital' THEN spend ELSE 0 END) as digital_spend,        SUM(CASE WHEN channel = 'Radio' THEN spend ELSE 0 END) as radio_spend,        SUM(revenue) as revenue,        SUM(conversions) as conversions    FROM `{project_id}.marketing.daily_summary`    WHERE DATE(timestamp) >= DATE_SUB(CURRENT_DATE(), INTERVAL {lookback_days} DAY)    GROUP BY date    ORDER BY date    """        df = client.query(query).to_dataframe()    return df

This skill provides comprehensive guidance for using the Awesome Marketing Science collection to implement production marketing measurement systems.

Source and attribution

Source:reason-machines/marketing-skillsinskills/awesome-marketing-science-guideat commit97301f4

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal