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:
Estimate the dose-response curve \(E[Y(t)]\) at arbitrary treatment levels?
Get valid confidence intervals at each point?
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).