Skip to main content

dxpoint

Author: Ryan Duecker

PyPI Downloads

A lightweight, differential marketing intelligence module that models media response curves, calculates marginal derivatives, and determines strategic inflection points.

Growth marketers and media buyers ask two fundamental questions:

  1. "When are we out of the inefficient learning phase?"
  2. "When should we stop scaling spend?"

By fitting performance data to continuous saturation curves and evaluating their derivatives ($f'(x)$ and $f''(x)$), dxpoint identifies the Minimal Marginal Cost Point (the inflection point where acquisition cost is lowest) and the Point of Diminishing Returns (where marginal ROAS hits your profitability hurdle rate), defining your exact Optimal Scaling Zone.

dxpoint focuses primarily on single-channel curve fitting and cross-channel portfolio planning, keeping single-channel workflows fast, lightweight, and accessible without requiring heavy econometric setup.


Core Methodology

dxpoint leverages the mathematical foundations of modern response modeling—specifically the Hill saturation and adstock formulations popularized by Google’s Meridian.

1. Media Saturation (The Hill Function)

Instead of basic linear or logarithmic approximations, this module natively models media saturation using the Hill Function.

$$Return = \beta_0 + \frac{\beta \cdot Spend_{adstocked}^\alpha}{K^\alpha + Spend_{adstocked}^\alpha}$$

  • $\beta$ (Beta - Capacity): Maximum incremental return capacity.
  • $\alpha$ (Alpha - Shape): The learning curve. $\alpha > 1$ produces an S-curve (initial warm-up phase where frequency builds momentum); $\alpha \le 1$ produces a C-curve (immediate concave diminishing returns).
  • $K$ (Half-Saturation): The spend level required to achieve 50% of maximum incremental capacity.
  • $\beta_0$ (Baseline Demand): Optional organic, non-media baseline return.

2. Adstock (Lagged Effects & Memory)

Advertising impacts persist beyond the day of exposure. dxpoint supports multiple memory decay models:

  • Geometric Adstock: Exponential memory decay parameterized by retention rate $\theta \in [0, 1)$: $$S_{t_adstocked} = S_t + \theta \cdot S_{t-1_adstocked}$$
  • Weibull Adstock: Flexible delayed response curves using Weibull PDF (lagged peak effect) or Weibull CDF (flexible S/C decay) with shape $k$ and scale $\lambda$.

During single-channel training, adstock can be set to none, fixed (explicit half-life), bounded (constrained half-life window), or free (unconstrained optimization).

3. Margin-Focused Calculus & Tipping Points

Using marginal rates of change ($dy/dx$) rather than historical blended averages, the module calculates:

  • Marginal ROAS ($f'(x)$): The efficiency of the next dollar spent.
  • Peak Efficiency Point ($f''(x) = 0$): The inflection point. Spend at least this much to exit the warm-up phase.
  • Stop Scaling Point ($f'(x) = \text{Target mROAS}$): The exact spend level where marginal return drops below your baseline unit economics.
  • Optimal Scaling Zone: The high-velocity growth window between the Peak Efficiency Point and the Stop Scaling Point.

Installation

pip install dxpoint

This module uses tinygrad for ultra-lightweight GPU-accelerated gradient descent, scipy for portfolio optimization, and plotly/streamlit for interactive visualization. Bayesian MCMC estimation is built-in with adaptive burn-in tuning.


Single-Channel Usage

1. Fitting Curves from Historical Data

Pass raw Spend and Return arrays directly into the module. You can fit using Gradient Descent (MLE), Frequentist Non-Linear Least Squares (NLS), or Bayesian MCMC:

import numpy as np
from dxpoint import MarketingReturnCurve

spends = np.array([1200, 5000, 15000, 25000, 40000])
returns = np.array([200, 1500, 12000, 22000, 28000])

# 1. Unified Interface (Auto-selects best fitting method)
model = MarketingReturnCurve.fit(
    spend_array=spends,
    return_array=returns,
    channel_name="YouTube Performance",
    adstock_type="bounded",
    adstock_bounds=(1.0, 14.0)
)

# Or explicitly choose an engine:

# 1a. Gradient Descent (MLE)
model_mle = MarketingReturnCurve.fit_gradient_descent(
    spend_array=spends,
    return_array=returns,
    channel_name="YouTube Performance",
    adstock_type="bounded",
    adstock_bounds=(1.0, 14.0)
)

# 1b. Frequentist NLS (includes standard errors & 95% confidence intervals)
model_freq = MarketingReturnCurve.fit_frequentist(
    spend_array=spends,
    return_array=returns,
    channel_name="YouTube Performance",
    adstock_type="bounded",
    adstock_bounds=(1.0, 14.0),
    confidence_level=0.95
)

# 1c. Bayesian MCMC (includes posterior distributions & experimental calibration)
model_bayes = MarketingReturnCurve.fit_bayesian(
    spend_array=spends,
    return_array=returns,
    channel_name="YouTube Performance",
    adstock_type="bounded",
    adstock_bounds=(1.0, 14.0)
)

2. Extracting Intelligence & Inflection Points

# Evaluate current headroom and efficiency status
model.evaluate_current_budget(current_spend=12000, target_mroas=1.5)

# Programmatically retrieve key boundaries
inflection = model.get_inflection_point()
opt_window = model.get_optimal_scaling_window(target_mroas=1.0)

print(f"Peak Efficiency Spend: ${inflection:,.2f}")
print(f"Optimal Scaling Window: ${opt_window[0]:,.2f} - ${opt_window[1]:,.2f}")

Example Output:

--- Budget Evaluation: YouTube Performance ---
Current Spend: $12,000.00 | Current mROAS: 2.10
Status: OPTIMAL SCALING ZONE
Recommendation: You are operating within the highly efficient growth window.

3. Statistical Fit Evaluation & Goodness-of-Fit

Assess the quality of fitted saturation and adstock curves with standard statistical diagnostic metrics ($R^2$, Adjusted $R^2$, RMSE, MAE, MAPE, AIC, BIC):

# Compute and print goodness-of-fit metrics
metrics = model.evaluate_fit(verbose=True)
print(f"R²: {metrics['r_squared']:.4f} | RMSE: {metrics['rmse']:,.2f} | MAPE: {metrics['mape']:.2f}%")

4. Predictive Uncertainty & Confidence Intervals

Generate point predictions and statistical uncertainty intervals using the Frequentist Delta Method or Bayesian Posterior Samples:

# Returns (point_prediction, lower_ci, upper_ci)
pred, low, high = model.predict_incremental_return(spend=15000, return_interval=True, confidence_level=0.95)
print(f"Expected Incremental Return: {pred:,.1f} (95% CI: [{low:,.1f}, {high:,.1f}])")

5. Incrementality Experiment Calibration & Parallel Association

Associate causal lift test results (e.g. geo-experiments, conversion lift studies) in parallel across individual channels, joint multi-channel models, or portfolio allocators:

# 5a. Associate experiments directly on a single-channel model
model_youtube.add_experiment(spend=15000, lift=11500, se=800, name="YT_Lift_Q1")
model_youtube.validate_experiments(verbose=True)

# 5b. Associate experiments in parallel across multiple channels in a joint model
mc_model.attach_experiments({
    "YouTube": [{"name": "YT_Q1", "spend": 15000, "lift": 11500, "se": 800}],
    "Paid Search": {"name": "Search_Q1", "spend": 25000, "lift": 38000, "se": 1200}
})
mc_validation = mc_model.validate_experiments(verbose=True)

# 5c. Audit calibration across portfolio allocator channels before optimization
allocator = PortfolioAllocator([model_search, model_youtube, model_social])
calibration_audit = allocator.get_calibration_summary()

6. Cross-Channel Portfolio Optimization (Scenario Planning)

Once you have fitted single-channel curves, the PortfolioAllocator calculates the budget distribution that maximizes total portfolio return:

from dxpoint import PortfolioAllocator

# Initialize the Allocator with fitted channel models
allocator = PortfolioAllocator([model_search, model_youtube, model_social])

# Run scenario analysis for a $1,000,000 budget
scenario = allocator.allocate_budget(
    total_budget=1000000,
    channel_bounds={"Paid Search": (50000, 300000)} # Optional constraints
)

print(scenario["allocation"])
print(f"Expected Portfolio Return: ${scenario['expected_total_return']:,.2f}")

7. Interactive Dashboard & Example Notebooks

  • Web App Dashboard: Launch the built-in Streamlit app to explore single-channel curves, adstock carryover timelines, and cross-channel allocation simulations:
    dxpoint dashboard
    # or using the short alias:
    dxpt dashboard
    
  • Practitioner's Single-Channel Starter Template: See examples/practitioner_single_channel_template.ipynb for a production-ready boilerplate template for media science practitioners, featuring automated diagnostic fit callouts, causal experiment validation, and customized Matplotlib visualizations.
  • Single-Channel YouTube Saturation Example: See examples/single_channel_youtube_branded_search.ipynb for a concise, step-by-step engineering tutorial on fitting daily YouTube video spend to Attributed Branded Search volume, calculating the geometric carryover half-life, locating Peak Efficiency ($f''(x) = 0$), and identifying the Stop Scaling Point against a $16.00 target CPA.
  • Causal Experiment Calibration Example: See examples/single_channel_incrementality_calibration.ipynb for an engineering case study integrating holdout conversion lift studies via Bayesian MCMC to decouple organic baseline demand from paid media lift.
  • Multi-Channel Stacked Walkthrough: See examples/dxpoint_walkthrough.ipynb for an end-to-end tutorial on multi-channel budget allocation and visualizing how brand consideration campaigns shift response curves upward.

Exploring Multi-Channel Dynamics: Joint Response Curves (MultiChannelModel)

While dxpoint is built around lightweight single-channel curve fitting and portfolio allocation, it also provides a multi-channel modeling class (MultiChannelModel) that allows practitioners to explore how individual channels interact under a unified framework.

What MultiChannelModel Provides

When you need to analyze multiple spend series simultaneously:

  • Joint Parameter Estimation: Simultaneously estimates adstock decay ($\theta_m$), Hill saturation ($\alpha_m, K_m$), baseline ($\beta_0$), and channel scale ($\beta_m$) via MCMC.
  • Hierarchical Partial Pooling: Stabilizes estimates for smaller or noisy channels by pooling across channel distributions.
  • Geo/Regional Hierarchy: Fits geo-specific multipliers when regional panel data is available.
  • Historical Contribution Decomposition: Breaks down historical revenue into organic baseline and channel-specific return series.
import pandas as pd
from dxpoint import MultiChannelModel

df = pd.read_csv("weekly_marketing_data.csv")

mc_model = MultiChannelModel(channel_names=["Search", "YouTube", "Social"])
mc_model.fit(
    spend_data=df[["Search", "YouTube", "Social"]],
    return_array=df["Revenue"],
    fit_baseline=True,
    n_samples=2000,
    burn_in=500
)

# Decompose historical contributions and channel ROIs
decomp = mc_model.decompose_historical_contributions(
    spend_data=df[["Search", "YouTube", "Social"]],
    return_array=df["Revenue"]
)
print(decomp["summary_table"])

Integrating with External MMMs (Google Meridian)

If you already run Google Meridian or PyMC-Marketing, you can extract your posterior mean parameters and initialize MarketingReturnCurve directly without refitting:

# Initialize directly from your existing MMM posterior outputs
model = MarketingReturnCurve(
    beta=120000.0,
    alpha=1.65,
    half_saturation_k=25000.0,
    theta=0.6,
    baseline=5000.0,
    channel_name="YouTube"
)

Metadata

Release files for dxpoint 0.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dxpoint 0.6.0
File Size Uploaded
dxpoint-0.6.0.tar.gz 1.6 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for dxpoint 0.6.0
File Interpreter ABI Platform
dxpoint-0.6.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.6 MB

Release files / dxpoint-0.6.0.tar.gz

Download URL dxpoint-0.6.0.tar.gz
Size 1.6 MB
Tags Source
SHA-256 checksum
How to use checksums
7e8c0764fc4c578ff2e3a9743f7e670d29a80067b71ea5bb50593de463a020ab
BLAKE2b-256 checksum
How to use checksums
c7a4c661d94ca3cdc04e396668e592d0279ed4c2fcb2d428aea4e5d568aaf0f5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Release files / dxpoint-0.6.0-py3-none-any.whl

Download URL dxpoint-0.6.0-py3-none-any.whl
Size 67.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e1ab17ab7b375ea16b09f18eb6d76ac5164f46727389574805e3b42663507ef7
BLAKE2b-256 checksum
How to use checksums
05204f2fe2eb0bcd646f4ec5b625b837102b66fa6178437614369ba48ad6efde
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Release history Release notifications | RSS feed

This release

0.6.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page