Quick Start
Minimal workflow
import numpy as np
from hddid import fit_hddid
# Generate example data
np.random.seed(42)
n, p = 200, 10
x = np.random.randn(n, p)
z = np.random.randn(n)
treat = (np.random.rand(n) > 0.5).astype(float)
y0 = x @ np.random.randn(p) * 0.1 + np.random.randn(n)
y1 = y0 + 0.5 * treat + np.random.randn(n) * 0.1
# Fit the HDDID model
fit = fit_hddid(
y0=y0, y1=y1, treat=treat, x=x, z=z,
z0=np.array([-1.0, 0.0, 1.0]),
basis_family="polynomial",
basis_degree=2,
)
# View results
print(fit.summary(format="text"))
Example output (generated with seed=42, n=200, p=10, polynomial
basis of degree 2):
--- HDDID Estimation Results ---
Basis: polynomial (degree=2)
Folds: 2 | Valid obs: 200 | Alpha: 0.1
Parametric Estimates:
Name Estimate SE 95% CI
beta_hat[1] 0.0057 - -
beta_hat[2] 0.0103 - -
beta_hat[3] -0.0189 - -
beta_hat[4] 0.0032 - -
beta_hat[5] -0.0231 - -
beta_hat[6] -0.0332 - -
beta_hat[7] -0.0212 - -
beta_hat[8] 0.0028 - -
beta_hat[9] 0.0192 - -
beta_hat[10] 0.0120 - -
Nonparametric Estimates:
Name Estimate SE 95% CI
gamma_hat[1] 0.4458 - -
gamma_hat[2] -0.0445 - -
gamma_hat[3] 0.0586 - -
f(z0=-1.00) 0.5489 - -
f(z0=0.00) 0.4458 - -
f(z0=1.00) 0.4600 - -
Choosing a basis family and solver
The basis_family argument selects the sieve basis used for the
nonparametric component \(f(Z)\) in Eq. (3.1):
"polynomial"(default): powers \([1, z, z^2, \dots, z^{\text{degree}}]\). Simple and fast; a good default for smooth, low-dimensional \(Z\)."trigonometric": Fourier basis \([1, \cos(2\pi z), \sin(2\pi z), \dots]\). Useful when \(f(\cdot)\) is periodic."bspline": cubic B-splines with quantile-based interior knots. Often gives the best approximation properties because B-splines have the optimal Bernstein constant; seehddid.bspline_sieve_basis()andhddid.suggest_basis_degree().
The solver argument selects the Lasso backend for the parametric block in
Eq. (3.1):
"sklearn"(default): scikit-learn’s Lasso. Fast and supports warm starts, but requires scikit-learn to be installed."builtin"/"native": a pure-NumPy coordinate-descent implementation. Has no extra dependencies and is bit-for-bit reproducible across platforms.
Using B-spline basis (optimal Bernstein constant)
fit_bspline = fit_hddid(
y0=y0, y1=y1, treat=treat, x=x, z=z,
z0=np.array([0.0]),
basis_family="bspline",
basis_degree=4,
)
Auto penalty selection
fit_auto = fit_hddid(
y0=y0, y1=y1, treat=treat, x=x, z=z,
z0=np.array([0.0]),
penalty_lambda="auto", # λ = 2.2√(log(p)/n_valid), n_valid = trimmed sample size
)
Advanced: custom nuisance estimators
By default, fit_hddid uses an IRLS logistic regression for the propensity
score and OLS for the conditional outcome means. You can replace the default
nuisance models with any sklearn-compatible estimator by passing
nuisance_estimator. Pass a dict with "propensity" and "outcome"
keys to customize each stage separately:
from sklearn.linear_model import LogisticRegression, Ridge
fit_sklearn = fit_hddid(
y0=y0, y1=y1, treat=treat, x=x, z=z,
z0=np.array([0.0]),
nuisance_estimator={
"propensity": LogisticRegression(max_iter=1000),
"outcome": Ridge(),
},
)
The propensity estimator must implement .fit(X, y) and
.predict_proba(X); the outcome estimator must implement .fit(X, y) and
.predict(X). For details see hddid.fit_hddid().