Continuous Treatment Tutorial#

This tutorial demonstrates dose-response analysis with continuous treatments using deep-inference, following Colangelo & Lee (2026).

Motivation#

Problem: Many treatments are continuous (advertising spend, drug dosage, price levels). How do we:

  1. Estimate the dose-response curve \(E[Y(t)]\) at arbitrary treatment levels?

  2. Get valid confidence intervals at each point?

  3. Compute treatment elasticities?

Key insight: deep-inference handles continuous treatments natively. Every model family (linear, logit, Poisson, etc.) takes a scalar \(T\) and estimates \(\theta(X) = (\alpha(X), \beta(X))\) where \(\beta\) is the treatment coefficient. The influence function framework provides valid inference at any evaluation point \(\tilde{t}\).

When to Use#

Use this approach when:

  • Treatment is continuous (not binary)

  • You want heterogeneous dose-response, not just ATE

  • Examples: advertising spend, drug dosage, pricing, subsidy levels

Connection to Colangelo & Lee (2026)#

Colangelo & Lee (2026) develop double debiased machine learning for nonparametric inference with continuous treatments. They show that deep neural networks (citing FLM 2021) are valid first-stage nuisance estimators. deep-inference provides a complementary structural approach: instead of nonparametrically estimating \(E[Y|T=t,X]\), we model the data-generating process parametrically with heterogeneous coefficients \(\theta(X)\).

Trade-off:

Approach

Flexibility

Interpretability

Extrapolation

Nonparametric (C&L)

High

Low

Poor

Structural DNN (FLM)

Medium

High

Good

The structural approach works best when you have a reasonable economic model (e.g., Poisson for counts, logit for binary choice).

Example: Advertising and Sales#

Step 1: Generate Data#

Poisson model: \(Y \sim \text{Poisson}(\exp(\alpha(X) + \beta(X) \cdot T))\)

import numpy as np

np.random.seed(42)
n = 2000

# Firm characteristics
X = np.random.randn(n, 3)

# Heterogeneous parameters
alpha_true = 2.0 + 0.3 * X[:, 0]       # Baseline log-sales
beta_true = 0.1 + 0.05 * X[:, 0]       # Ad effectiveness (positive)

# Advertising spend (continuous treatment)
T = np.random.exponential(2.0, n)

# Sales (count outcome)
mu = np.exp(alpha_true + beta_true * T)
Y = np.random.poisson(mu).astype(float)

Step 2: Estimate Average Treatment Effect#

from deep_inference import inference

# Average β (marginal effect in log-sales)
result_beta = inference(
    Y, T, X,
    model='poisson',
    target='beta',
    epochs=100,
    n_folds=50,
)

print(f"E[β(X)]: {result_beta.mu_hat:.4f}")
print(f"95% CI: [{result_beta.ci_lower:.4f}, {result_beta.ci_upper:.4f}]")
# True value: E[β(X)] = 0.1

Step 3: Dose-Response via Elasticity#

For a Poisson (log-link) model, the elasticity at treatment level \(t\) is simply \(\beta \cdot t\):

# Elasticity at different spending levels
for t in [1.0, 2.0, 3.0, 5.0]:
    result = inference(
        Y, T, X,
        model='poisson',
        target='elasticity',
        t_tilde=float(t),
        epochs=100,
        n_folds=50,
    )
    print(f"t={t:.1f}: elasticity={result.mu_hat:.4f} +/- {result.se:.4f}")

Step 4: Custom Dose-Response Target#

For the predicted outcome \(E[Y(t)] = \exp(\alpha + \beta \cdot t)\) at a given level:

import torch

def dose_response(x, theta, t_tilde):
    """E[Y(t)] = exp(alpha + beta * t)"""
    return torch.exp(theta[0] + theta[1] * t_tilde)

result_dr = inference(
    Y, T, X,
    model='poisson',
    target_fn=dose_response,
    t_tilde=2.0,
    epochs=100,
    n_folds=50,
)

print(f"E[Y(2.0)]: {result_dr.mu_hat:.4f}")
print(f"95% CI: [{result_dr.ci_lower:.4f}, {result_dr.ci_upper:.4f}]")

Multiple Evaluation Points#

Trace out the full dose-response curve by evaluating at multiple \(\tilde{t}\) values:

t_values = np.linspace(0.5, 5.0, 10)
results = []

for t in t_values:
    r = inference(Y, T, X, model='poisson', target='elasticity',
                  t_tilde=float(t), epochs=100, n_folds=50)
    results.append({
        't': t,
        'elasticity': r.mu_hat,
        'se': r.se,
        'ci_lower': r.ci_lower,
        'ci_upper': r.ci_upper,
    })

# Plot with confidence bands
import matplotlib.pyplot as plt

ts = [r['t'] for r in results]
etas = [r['elasticity'] for r in results]
ci_lo = [r['ci_lower'] for r in results]
ci_hi = [r['ci_upper'] for r in results]

plt.fill_between(ts, ci_lo, ci_hi, alpha=0.2)
plt.plot(ts, etas, 'b-o')
plt.xlabel('Treatment level (t)')
plt.ylabel('Elasticity')
plt.title('Dose-Response Elasticity with 95% CI')
plt.show()

Supported Model Families#

All families support continuous treatment natively:

Family

Link

Elasticity Formula

Use Case

Linear

Identity

\(\beta \cdot t\)

Continuous outcomes

Logit

Logit

\((1-p) \cdot \beta \cdot t\)

Binary outcomes

Poisson

Log

\(\beta \cdot t\)

Count data

Gamma

Log

\(\beta \cdot t\)

Positive continuous

NegBin

Log

\(\beta \cdot t\)

Overdispersed counts

References#

  • Colangelo, K. & Lee, Y.-Y. (2026). Double Debiased Machine Learning Nonparametric Inference with Continuous Treatments.

  • Farrell, M. H., Liang, T., & Misra, S. (2021). Deep Neural Networks for Estimation and Inference. Econometrica, 89(1).