Skip to main content

marketing-mix-modeling-end-to-end-pipeline

Build end-to-end Marketing Mix Models with adstock, saturation, OLS regression, and budget optimization in Python

설치로 이동

소스 정보

저장소
reason-machines/marketing-skills
최근 소스 활동
2026년 6월 26일 22:30
감지된 SKILL.md 언어
영어
스타
10
포크
1

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
marketing-mix-modeling-end-to-end-pipeline
description
Build end-to-end Marketing Mix Models with adstock, saturation, OLS regression, and budget optimization in Python
triggers
["build a marketing mix model","implement MMM with adstock and saturation","optimize marketing budget allocation","calculate channel ROI for marketing spend","apply geometric adstock transformation","run marketing attribution analysis","create budget optimizer for marketing channels","decompose revenue by marketing channel"]
# Marketing Mix Modeling End-to-End Pipeline > Skill by [ara.so](https://ara.so) — Marketing Skills collection. This project provides a complete Marketing Mix Modeling (MMM) pipeline in Python that transforms raw marketing spend data into actionable channel insights and optimized budget allocations. It includes geometric adstock transformation, Hill saturation curves, OLS regression modeling, and constrained budget optimization. ## Installation ```bash # Clone the repository git clone https://github.com/francescaetnom-wq/Marketing-Mix-Modeling-End-to-End-Pipeline.git cd Marketing-Mix-Modeling-End-to-End-Pipeline # Create virtual environment python3 -m venv venv source venv/bin/activate # On Windows: venv\Scripts\activate # Install dependencies pip install -r requirements.txt ``` **Key Dependencies:** - `pandas` — data manipulation - `numpy` — numerical operations - `statsmodels` — OLS regression - `scipy` — optimization algorithms - `matplotlib` / `seaborn` — visualization ## Project Structure ``` mmm_project/ ├── data/ │ ├── dt_simulated_weekly.csv # Input: weekly spend & revenue │ ├── dt_transformed.csv # Output: with adstock/saturation │ ├── coefficients.csv # Output: model coefficients │ └── optimized_budget.csv # Output: budget recommendations ├── src/ │ ├── adstock.py # Geometric adstock │ ├── saturation.py # Hill saturation │ ├── model.py # OLS wrapper │ └── optimizer.py # Budget allocation └── notebooks/ ├── 01_exploration.ipynb ├── 02_transformations.ipynb ├── 03_model.ipynb └── 04_optimizer.ipynb ``` ## Core Transformations ### 1. Geometric Adstock Models the carryover effect of advertising — this week's exposure includes decayed contributions from previous weeks. **Implementation (`src/adstock.py`):** ```python import numpy as np import pandas as pd def geometric_adstock(x, theta, L_max=8): """ Apply geometric adstock transformation. Parameters: ----------- x : array-like Raw media spend or impressions theta : float Decay rate (0 to 1). Higher = longer carryover L_max : int Maximum lag window Returns: -------- array : Adstocked signal """ x = np.array(x) adstocked = np.zeros_like(x, dtype=float) for t in range(len(x)): for lag in range(min(t + 1, L_max)): adstocked[t] += x[t - lag] * (theta ** lag) return adstocked # Example usage spend = [100, 150, 120, 90, 110] adstocked_spend = geometric_adstock(spend, theta=0.5, L_max=4) # adstocked_spend accounts for carryover from previous weeks ``` **Apply to DataFrame:** ```python import pandas as pd df = pd.read_csv('data/dt_simulated_weekly.csv') # Define adstock parameters per channel adstock_params = { 'tv_S': 0.7, # High carryover 'search_S': 0.3, # Low carryover 'facebook_S': 0.5, 'print_S': 0.6, 'ooh_S': 0.4 } # Apply adstock to each channel for channel, theta in adstock_params.items(): df[f'{channel}_adstocked'] = geometric_adstock( df[channel].values, theta=theta, L_max=8 ) ``` ### 2. Hill Saturation Models diminishing returns — the first dollar spent is more effective than the millionth. **Implementation (`src/saturation.py`):** ```python import numpy as np def hill_saturation(x, alpha, gamma): """ Apply Hill saturation transformation. Parameters: ----------- x : array-like Input signal (usually adstocked spend) alpha : float Half-saturation point (inflection) gamma : float Shape parameter (> 1 for S-curve) Returns: -------- array : Saturated signal """ x = np.array(x) return (x ** gamma) / (alpha ** gamma + x ** gamma) # Example usage adstocked = np.array([50, 100, 150, 200, 250]) saturated = hill_saturation(adstocked, alpha=100, gamma=2.0) # saturated shows diminishing marginal returns ``` **Apply After Adstock (Order Matters!):** ```python # Saturation parameters per channel saturation_params = { 'tv_S': {'alpha': 150, 'gamma': 2.0}, 'search_S': {'alpha': 80, 'gamma': 1.8}, 'facebook_S': {'alpha': 100, 'gamma': 1.9}, 'print_S': {'alpha': 120, 'gamma': 2.1}, 'ooh_S': {'alpha': 90, 'gamma': 2.0} } # Apply saturation to adstocked columns for channel, params in saturation_params.items(): adstocked_col = f'{channel}_adstocked' df[f'{channel}_transformed'] = hill_saturation( df[adstocked_col].values, alpha=params['alpha'], gamma=params['gamma'] ) # Save transformed data df.to_csv('data/dt_transformed.csv', index=False) ``` ## OLS Regression Model ### Training the Model **Implementation (`src/model.py`):** ```python import pandas as pd import statsmodels.api as sm def fit_mmm_model(df, transformed_channels, controls, target='revenue'): """ Fit OLS regression for MMM. Parameters: ----------- df : DataFrame Input data with transformed channels transformed_channels : list Column names of transformed media variables controls : list Control variables (e.g., competitor_sales_B, events) target : str Revenue or sales column Returns: -------- model : statsmodels RegressionResults """ # Prepare feature matrix X = df[transformed_channels + controls].copy() # Handle categorical controls (one-hot encode events) if 'events' in controls: X = pd.get_dummies(X, columns=['events'], drop_first=True) # Add constant X = sm.add_constant(X) # Target variable y = df[target] # Fit OLS model = sm.OLS(y, X).fit() return model # Example usage df = pd.read_csv('data/dt_transformed.csv') transformed_channels = [ 'tv_S_transformed', 'search_S_transformed', 'facebook_S_transformed', 'print_S_transformed', 'ooh_S_transformed' ] controls = ['competitor_sales_B', 'events'] model = fit_mmm_model(df, transformed_channels, controls, target='revenue') print(model.summary()) print(f"\nR-squared: {model.rsquared:.3f}") ``` ### Extract Coefficients & ROI ```python import pandas as pd def extract_coefficients(model, channel_names): """Extract coefficients and calculate proxy ROI.""" coefs = model.params[channel_names] pvalues = model.pvalues[channel_names] results = pd.DataFrame({ 'channel': channel_names, 'coefficient': coefs.values, 'p_value': pvalues.values }) return results # Get coefficients channel_names = [col for col in model.params.index if '_transformed' in col] coef_df = extract_coefficients(model, channel_names) # Calculate proxy ROI (revenue per unit spend) # Match transformed columns to original spend columns spend_mapping = { 'tv_S_transformed': 'tv_S', 'search_S_transformed': 'search_S', 'facebook_S_transformed': 'facebook_S', 'print_S_transformed': 'print_S', 'ooh_S_transformed': 'ooh_S' } for idx, row in coef_df.iterrows(): channel_transformed = row['channel'] channel_raw = spend_mapping[channel_transformed] # Average contribution / average spend avg_spend = df[channel_raw].mean() avg_contribution = row['coefficient'] * df[channel_transformed].mean() coef_df.loc[idx, 'avg_spend'] = avg_spend coef_df.loc[idx, 'avg_contribution'] = avg_contribution coef_df.loc[idx, 'proxy_roi'] = avg_contribution / avg_spend if avg_spend > 0 else 0 coef_df.to_csv('data/coefficients.csv', index=False) print(coef_df) ``` ## Budget Optimization Find the optimal budget allocation to maximize revenue under a fixed total budget constraint. **Implementation (`src/optimizer.py`):** ```python import numpy as np from scipy.optimize import minimize def optimize_budget(model, df, channel_mapping, total_budget, adstock_params, saturation_params): """ Optimize budget allocation across channels. Parameters: ----------- model : statsmodels RegressionResults Fitted OLS model df : DataFrame Historical data (for baseline calculation) channel_mapping : dict Maps transformed column names to raw spend columns total_budget : float Total budget constraint adstock_params : dict Adstock theta per channel saturation_params : dict Saturation alpha/gamma per channel Returns: -------- dict : Optimized budget allocation """ from adstock import geometric_adstock from saturation import hill_saturation channels = list(channel_mapping.keys()) n_channels = len(channels) # Extract coefficients coefs = {col: model.params[col] for col in channels} def predict_revenue(budget_allocation): """Predict revenue given budget allocation.""" revenue = 0 for i, channel_raw in enumerate(channels): spend = budget_allocation[i] channel_transformed = channel_mapping[channel_raw] # Apply transformations adstocked = geometric_adstock( [spend], theta=adstock_params[channel_raw], L_max=1 # Single period optimization )[0] saturated = hill_saturation( [adstocked], alpha=saturation_params[channel_raw]['alpha'], gamma=saturation_params[channel_raw]['gamma'] )[0] # Multiply by coefficient revenue += saturated * coefs[channel_transformed] return revenue # Objective: negative revenue (minimize = maximize revenue) def objective(budget_allocation): return -predict_revenue(budget_allocation) # Constraints constraints = [ {'type': 'eq', 'fun': lambda x: np.sum(x) - total_budget} # Sum = total ] # Bounds: non-negative spend bounds = [(0, total_budget) for _ in range(n_channels)] # Initial guess: equal split x0 = np.array([total_budget / n_channels] * n_channels) # Optimize result = minimize( objective, x0, method='SLSQP', bounds=bounds, constraints=constraints ) # Format results optimized_budget = { channel: result.x[i] for i, channel in enumerate(channels) } return optimized_budget, -result.fun # Return revenue (negate objective) # Example usage channel_mapping = { 'tv_S': 'tv_S_transformed', 'search_S': 'search_S_transformed', 'facebook_S': 'facebook_S_transformed', 'print_S': 'print_S_transformed', 'ooh_S': 'ooh_S_transformed' } # Calculate current total budget current_budget = df[list(channel_mapping.keys())].sum(axis=1).mean() # Optimize optimized, projected_revenue = optimize_budget( model=model, df=df, channel_mapping=channel_mapping, total_budget=current_budget, adstock_params=adstock_params, saturation_params=saturation_params ) # Compare current vs optimized current_allocation = {ch: df[ch].mean() for ch in channel_mapping.keys()} comparison = pd.DataFrame({ 'channel': list(channel_mapping.keys()), 'current_budget': [current_allocation[ch] for ch in channel_mapping.keys()], 'optimized_budget': [optimized[ch] for ch in channel_mapping.keys()] }) comparison['change_pct'] = ( (comparison['optimized_budget'] - comparison['current_budget']) / comparison['current_budget'] * 100 ) comparison.to_csv('data/optimized_budget.csv', index=False) print(comparison) ``` ## Complete Workflow ```python import pandas as pd import numpy as np from src.adstock import geometric_adstock from src.saturation import hill_saturation from src.model import fit_mmm_model from src.optimizer import optimize_budget # 1. Load data df = pd.read_csv('data/dt_simulated_weekly.csv') df['DATE'] = pd.to_datetime(df['DATE']) # Clean events column (handle literal "na" string) df['events'] = df['events'].replace('na', np.nan) # 2. Define transformation parameters
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기