Panel DML and Rolling Window Methods
DML for panel data: the within (fixed-effects) transformation, Panel DML combining fixed effects with ML nuisance models, cluster-robust standard errors, Rolling Window DML for time-varying effects and structural-break detection, with insurance and macro applications.
On this page
- Introduction
- The panel data advantage
- Chapter roadmap
- Panel data fundamentals
- Fixed effects model
- Two-way fixed effects
- Panel DML
- The problem with fixed effects + ML
- Implementation
- Monte Carlo comparison
- Cluster-robust inference
- Why clustering matters
- Implementation details
- Rolling Window DML
- Time-varying treatment effects
- Implementation
- Detecting structural breaks
- Applications
- Application 1: insurance pricing
- Application 2: macroeconomic policy
- Exercises
- Exercise 6.1: within transformation
- Exercise 6.2: cluster-robust SE
- Recursive Dynamic G-Estimation
- Structural Nested Mean Models and the Blip
- Neyman-Orthogonal Recursive Peeling
- Implementation
- Summary
- Roadmap to Chapter 7
Panel DML and Rolling Window Methods
Introduction
Chapter 5 introduced TemporalPLRDML for single time series with autocorrelation. Many economic applications involve panel data — repeated observations on multiple units over time. This chapter develops DML methods for panel data, addressing both fixed effects and time-varying treatment effects.
The panel data advantage
Panel data combines cross-sectional and time series variation:
where:
- indexes individuals (e.g., states, firms, customers).
- indexes time periods.
- is an individual-specific fixed effect.
- is a time-specific fixed effect.
Chapter roadmap
- Section 6.2: panel data fundamentals — fixed effects and within transformation.
- Section 6.3: Panel DML — combining fixed effects with machine learning.
- Section 6.4: cluster-robust inference — accounting for within-unit correlation.
- Section 6.5: Rolling Window DML — time-varying treatment effects.
- Section 6.6: applications.
- Section 6.7: exercises.
Panel data fundamentals
Fixed effects model
The fixed effects model assumes individual-specific intercepts:
The key identification challenge: if correlates with (e.g., more productive firms invest more), OLS on pooled data is biased.
The within transformation demeans each variable by its individual-specific mean:
This eliminates since .
After within transformation:
OLS on the transformed data yields the fixed effects estimator.
Two-way fixed effects
When both individual and time effects matter:
The two-way within transformation:
where is the time-specific mean and is the grand mean.
Two-way fixed effects are particularly important for policy evaluation, where both cross-sectional heterogeneity (states differ) and common time shocks (recessions affect everyone) may confound treatment effects.
Panel DML
The problem with fixed effects + ML
Standard fixed effects require:
- Linear functional form: .
- Known confounders: all relevant included.
DML relaxes the linearity assumption, but combining ML with fixed effects requires care.
Algorithm: Panel DML with Fixed Effects
Require: Panel data (Y_it, T_it, X_it) for i=1..N, t=1..T
Ensure: Treatment effect τ̂ with cluster-robust SE
1. Apply fixed effects transformation:
for each variable V in {Y, T, X}:
individual means: V̄_i = (1/T) Σ_t V_it
demean: V̈_it = V_it - V̄_i
2. Run TemporalPLRDML on transformed data:
τ̂ ← TemporalPLRDML(Ÿ, T̈, Ẍ)
3. Compute cluster-robust standard errors:
for each individual i:
cluster-level influence: Ψ_i = Σ_t ψ_it
cluster variance: V̂_cluster = N/(N-1) · (1/n²) Σ_i Ψ_i² (n = total retained observations)
standard error: SE = sqrt(V̂_cluster)
return τ̂, SE
Implementation
from dml_ts import PanelDML
import numpy as np
# Generate panel data
np.random.seed(42)
n_individuals = 100
n_periods = 20
n_total = n_individuals * n_periods
# Create IDs
individual_id = np.repeat(np.arange(n_individuals), n_periods)
time_id = np.tile(np.arange(n_periods), n_individuals)
# Individual fixed effects (unobserved)
alpha_i = np.random.randn(n_individuals)
alpha_expanded = alpha_i[individual_id]
# Covariates and treatment (correlated with fixed effects!)
X = np.random.randn(n_total, 3)
T = 0.5 * X[:, 0] + 0.3 * alpha_expanded + np.random.randn(n_total)
# Outcome with true effect tau = 2.0
true_tau = 2.0
Y = true_tau * T + X[:, 0]**2 + alpha_expanded + np.random.randn(n_total)
# Without fixed effects: BIASED
from dml_ts import TemporalPLRDML
naive_model = TemporalPLRDML(n_lags=0, model_y="ridge", model_t="ridge")
naive_result = naive_model.fit(Y, T, X)
print(f"Naive (no FE): theta = {naive_result.theta:.3f}") # Biased!
# With fixed effects: UNBIASED
panel_model = PanelDML(
fixed_effects="individual",
cluster_se=True,
model_y="ridge",
model_t="ridge"
)
panel_result = panel_model.fit(Y, T, X, individual_id, time_id)
print(f"Panel DML: theta = {panel_result.theta:.3f}") # Close to 2.0
Monte Carlo comparison
Panel DML vs. naive DML: Monte Carlo results
| Method | Bias | Avg. SE | Coverage |
|---|---|---|---|
| Naive DML (no FE) | 0.040 | 0% | |
| Panel DML (individual FE) | 0.046 | 90% | |
| Panel DML (two-way FE) | 0.046 | 90% |
Generated by scripts/mc_panel_dml_table.py (100 simulations, 40 units × 15 periods, true , ridge nuisances, cluster-robust SEs). The 90% coverage at nominal 95% reflects normal critical values with a moderate cluster count (Remark below); critical values close most of the gap.
Cluster-robust inference
Why clustering matters
Within each individual, observations are typically correlated:
This violates the i.i.d. assumption and inflates standard errors.
Since with the total number of retained observations, the cluster-robust (CR1) variance of with clusters is:
where is the sum of influence scores within cluster . No centering term appears because exactly, by the first-order condition of the FWL estimator; counts only clusters that retain observations after temporal-CV trimming.
Under regularity conditions with clusters:
where ( already estimates the variance of ; dividing by again is the sums-as-means scale error this companion once shipped).
The cluster-robust variance is valid whether or not within-cluster correlation exists; with independent observations it converges to the heteroskedasticity-only variance. Two practical caveats: with few clusters, normal critical values are mildly anticonservative ( critical values are the standard refinement; Cameron & Miller 2015), and clusters whose observations are entirely dropped by temporal-CV trimming carry no information — the companion excludes them from and warns.
Implementation details
def cluster_robust_se(influence_scores, cluster_ids):
"""
Compute cluster-robust standard error.
Parameters
----------
influence_scores : array, shape (n,)
Influence function values for each observation
cluster_ids : array, shape (n,)
Cluster identifiers
Returns
-------
se : float
Cluster-robust standard error
"""
unique_clusters = np.unique(cluster_ids)
N = len(unique_clusters)
# Sum influence scores within each cluster
cluster_sums = np.zeros(N)
for i, cluster in enumerate(unique_clusters):
mask = cluster_ids == cluster
cluster_sums[i] = np.sum(influence_scores[mask])
# CR1: V = N/(N-1) * sum(Psi_i^2) / n^2 — the sums are already
# centered (sum of all influence scores is 0 by FWL construction).
n = len(influence_scores)
cluster_var = (N / (N - 1)) * np.sum(cluster_sums**2) / n**2
return np.sqrt(cluster_var)
Rolling Window DML
Time-varying treatment effects
In many applications, treatment effects change over time:
- Policy effects may fade or strengthen.
- Market conditions evolve (e.g., competition intensity).
- Learning effects (customers adapt to prices).
Rolling window DML estimates local treatment effects:
For window size and step size :
- For each center
- Select observations in .
- Estimate DML on window data.
- Store and .
Implementation
from dml_ts import RollingWindowDML
# Fit rolling window DML
model = RollingWindowDML(
window_size=100, # 100 observations per window
step_size=20, # Move 20 observations between estimates
model_y="ridge",
model_t="ridge"
)
model.fit(Y, T, X, time_index=time)
# Get time series of local effects
time_centers, theta_series, se_series = model.get_effects()
# Plot time-varying effects
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 5))
plt.plot(time_centers, theta_series, 'b-', label='Local ATE')
plt.fill_between(
time_centers,
theta_series - 1.96 * se_series,
theta_series + 1.96 * se_series,
alpha=0.3
)
plt.axhline(true_tau, color='r', linestyle='--', label='True ATE')
plt.xlabel('Time')
plt.ylabel('Treatment Effect')
plt.legend()
plt.title('Rolling Window DML: Time-Varying Treatment Effect')
plt.show()
Detecting structural breaks
Rolling window DML naturally detects structural breaks — points where treatment effects change abruptly.
A structural break at time is detected if:
where is the critical value.
For formal structural break testing, use the Chow test or Bai–Perron procedure. Rolling window DML provides visual diagnostics and approximate break detection.
Applications
Application 1: insurance pricing
Setting: states, years, estimating the effect of competitor price changes on market share.
Panel structure:
- Individual FE: state-specific demand (regulation, demographics).
- Time FE: macroeconomic conditions affecting all states.
- Cluster SE: observations within a state are correlated.
Rolling window: detect if price sensitivity changed after major regulatory reform.
Application 2: macroeconomic policy
Setting: effect of monetary policy on output across countries, quarters.
Panel structure:
- Individual FE: country-specific institutions.
- Time FE: global business cycle.
- Two-way FE needed to avoid confounding.
Rolling window: test if policy effectiveness changed after the 2008 financial crisis.
Exercises
Exercise 6.1: within transformation
Consider panel data with individuals and periods:
| 1 | 1 | 5 | 1 | 2 |
| 1 | 2 | 7 | 2 | 2 |
| 2 | 1 | 3 | 1 | 1 |
| 2 | 2 | 4 | 1 | 1 |
| 3 | 1 | 8 | 2 | 3 |
| 3 | 2 | 12 | 4 | 3 |
(a) Compute the within-transformed variables and . (b) Estimate using OLS on the transformed data. (c) What is the true in this DGP?
Solution.
(a) Individual means: , , . Within-transformed : . Similarly for : , , , giving .
(b) OLS on the transformed data: .
(c) The true (coefficient on in the DGP).
Exercise 6.2: cluster-robust SE
You estimate from observations in clusters, with cluster-level influence sums
(they sum to zero, as FWL guarantees).
(a) Compute the cluster variance . (b) Compute the cluster-robust standard error. (c) Construct a 95% confidence interval.
Solution.
(a) . .
(b) .
(c) .
Recursive Dynamic G-Estimation
The estimators so far return a single scalar effect. Many dynamic problems instead ask for a sequence of period-specific effects: when a treatment is applied over periods and can alter both the outcome and the future state of the treated unit, what is the effect of the period- treatment? This is the dynamic treatment-effect setting of Lewis and Syrgkanis (2021), and the companion class DynamicGEstimationDML implements their recursive g-estimation.
Structural Nested Mean Models and the Blip
Consider i.i.d. trajectories, each observed over periods, with treatment , evolving controls (state) , and a final outcome . Crucially, the state may depend on past treatment,
so that past treatment confounds both future treatment and the outcome — the time-varying confounding that ordinary adjustment for mishandles (conditioning on a post-treatment state blocks part of the effect and opens collider paths). A linear structural nested mean model writes the effect of the period- treatment on the final outcome as a constant blip :
where is the history available before . Sequential conditional exchangeability given identifies .
Neyman-Orthogonal Recursive Peeling
Lewis and Syrgkanis give a cross-fitted, Neyman-orthogonal g-estimator — a dynamic generalization of Robinson’s partially linear model — that peels the blips off the outcome from the last period backward. Let denote the residual-maker that partials out , with the conditional means estimated by cross-fitting arbitrary machine-learning nuisances. For :
- Residualize on : and .
- Solve the orthogonal moment: .
- Peel period off the outcome and recurse to .
Inference uses the joint sandwich variance over the stacked moments. Because later-stage estimates enter earlier stages, Neyman orthogonality does not hold across the structural parameters: the Jacobian is triangular and the per-period variances are coupled. The implementation forms the full joint covariance rather than treating each period in isolation, with the sandwich “meat” clustered by unit (panel) or HAC/Newey–West (single series).
Implementation
from dml_ts import DynamicGEstimationDML
from dml_ts.validation import DynamicTreatmentDGP
# Known per-period blips with treatment-dependent state (time-varying confounding)
dgp = DynamicTreatmentDGP(
n_periods=3, theta_t=[1.0, 0.5, 1.5], n_units=1500, p=3,
state_feedback=True, treatment_state_coef=0.8, random_state=7,
)
data = dgp.generate() # data.theta_t == [1.96, 2.90, 1.50] (total blips)
# Linear confounding here, so ridge nuisances are correctly specified
est = DynamicGEstimationDML(model_y="ridge", model_t="ridge", random_state=0)
result = est.fit(data.Y, data.T, data.X, groups=data.groups)
print(result.summary())
The estimator recovers the true total blips — the direct coefficients plus the indirect path through the treatment-dependent state — to within Monte Carlo error, whereas a naive pooled regression that ignores the sequential structure is badly biased. A reference path, fit_econml_reference, wraps EconML’s DynamicDML (the reference Lewis–Syrgkanis implementation, optional via the full extra) and agrees with the native estimator to within sampling error — a custom-versus-reference numerical cross-check.
A single long, stationary series is also supported (the series mode), where the blips become distributed-lag effects estimated with HAC inference.
Summary
This chapter extended DML to panel data and time-varying effects:
- Panel DML: the within transformation eliminates fixed effects while preserving ML flexibility for nuisance functions.
- Cluster-robust SE: accounting for within-cluster correlation prevents underestimated standard errors.
- Rolling Window DML: local estimation reveals how treatment effects change over time.
- Structural breaks: rolling windows provide visual and approximate detection of regime changes.
Key takeaways:
- Always use fixed effects when unobserved heterogeneity correlates with treatment.
- Cluster standard errors by the unit of treatment assignment.
- Use rolling windows to test the stability of treatment effects.
- Two-way fixed effects handle both cross-sectional and time confounders.
Roadmap to Chapter 7
Chapter 7 integrates our methods with real macroeconomic data from FRED (Federal Reserve Economic Data). We develop:
- FRED data loader: automated retrieval of macro indicators.
- Time alignment: handling mixed-frequency data.
- Macro control variables: GDP, CPI, interest rates as confounders.
- Application: policy effect estimation with real data.
This bridges the gap from synthetic validation to audited applications with real economic data.