Semiparametric two-period DID estimators target the period-1 ATT. The design uses high-dimensional sparse covariates, a sparse propensity score, and two periods of potential outcomes. The target is the period-1 ATT,
\(\rho = 2\): unconditional parallel trends fails, but conditional parallel trends holds after adjusting for \(X\) through the propensity-index component in the untreated trend.
The nuisance fits use lightweight ridge/logistic-ridge learners so the page is self-contained in the Python docs environment. The estimator menu includes unadjusted DID, outcome-model DID, IPW DID, and AIPW DID, with plug-in and cross-fit versions of the nuisance-based estimators.
1 Imports And Helpers
Show code
from html import escapeimport matplotlib.pyplot as pltimport numpy as npfrom IPython.display import HTML, displaynp.set_printoptions(precision=4, suppress=True)def html_table(headers, rows): parts = ["<table>", "<thead>", "<tr>"] parts.extend(f"<th>{escape(str(h))}</th>"for h in headers) parts.extend(["</tr>", "</thead>", "<tbody>"])for row in rows: parts.append("<tr>") parts.extend(f"<td>{cell}</td>"for cell in row) parts.append("</tr>") parts.extend(["</tbody>", "</table>"])return"".join(parts)def expit(z): z = np.clip(z, -35.0, 35.0)return1.0/ (1.0+ np.exp(-z))def add_intercept(x):return np.column_stack([np.ones(x.shape[0]), x])def make_folds(n, k, rng): idx = rng.permutation(n)return [fold for fold in np.array_split(idx, k) if fold.size]
2 DGP
Let \(N=200\), \(p=100\), and \(s=5\). The covariates are
where \(\beta_0 = \gamma_0 + 0.5\), \(\theta_0 = 3\), and \(\varepsilon_i \sim \mathcal{N}(0, 0.1^2)\). A common shock is used across the baseline, untreated-trend, and treated-outcome equations. The observed outcomes are \(Y_i(0)=Y_i^0(0)\) and
\[
Y_i(1) = (1-D_i)Y_i^0(1) + D_iY_i^1(1).
\]
When \(\rho = 0\), the untreated trend does not depend on the propensity score and the simple DID contrast is valid. When \(\rho \neq 0\), units with high treatment probability also have different untreated trends, so unconditional DID is biased unless the covariates are accounted for.
The AIPW score subtracts the untreated-trend model \(\hat m_0(X_i) \approx \mathbb{E}[\Delta Y_i \mid X_i, D_i=0]\) before applying the same balancing score:
When \(\rho=0\), unconditional parallel trends holds. The unadjusted DID estimator is therefore centered near the true effect, while the post-period naive contrast remains badly confounded because treated and control units differ in the sparse propensity-index covariates.
When \(\rho=2\), unconditional DID breaks: the untreated trend itself contains the propensity score, so high-propensity units would have grown differently even without treatment. The adjusted estimators target the conditional parallel-trends structure. Outcome modeling works when the nuisance regressions recover the relevant high-dimensional linear signal. IPW works by reweighting the untreated trend changes toward the treated covariate distribution, but it is more variance-sensitive because the propensity score appears in the denominator. AIPW combines both pieces: it removes the fitted untreated trend and then balances the residual trend by the propensity score.
The cross-fit columns avoid using each observation’s own outcome to construct its nuisance prediction. In this small-\(N\), high-\(p\) design, cross-fitting is not guaranteed to reduce variance in every estimator, but it is the safer pattern for the doubly robust score.