/*────────────────────────────────────────────────────────────────────────────── trop_validation.mata Pre-estimation data validation for the TROP estimator. The unit distance metric requires a non-empty set of jointly observed control periods for each pair of units entering the weighted regression. Specifically, the denominator sum_{u!=t} (1 - W_{iu})(1 - W_{ju}) must be positive for at least one donor j per treated unit i; otherwise the distance is undefined and estimation cannot proceed. This module verifies that condition prior to calling the estimation backend. Contents _trop_chk_common_ctrl_periods() minimum pairwise control-period overlap ──────────────────────────────────────────────────────────────────────────────*/ version 17 mata: mata set matastrict on /*────────────────────────────────────────────────────────────────────────────── _trop_chk_common_ctrl_periods() For each ever-treated unit i, find the donor j != i that shares the most control periods with i, then return the minimum of those maxima across all ever-treated units. Define C_{it} = (1 - W_{it}) * 1{Y_{it} observed}. The pairwise overlap between units i and j is overlap(i,j) = sum_t C_{it} * C_{jt}. The statistic returned is min_{i : ever-treated} max_{j != i} overlap(i,j). A return value >= 2 guarantees that every ever-treated unit has at least one donor with a non-degenerate set of joint control periods, so the leave-one-out unit distance (which excludes one period at a time) retains a positive denominator. Arguments panelid_var variable name: integer panel identifier (1..N) timeid_var variable name: integer time identifier (1..T) treatvar variable name: binary treatment indicator W_{it} depvar variable name: outcome Y_{it} tousevar variable name: estimation-sample marker N_panel number of panel units T_panel number of time periods Returns real scalar minimum best-pairwise overlap defined above ──────────────────────────────────────────────────────────────────────────────*/ real scalar _trop_chk_common_ctrl_periods( string scalar panelid_var, string scalar timeid_var, string scalar treatvar, string scalar depvar, string scalar tousevar, real scalar N_panel, real scalar T_panel) { real colvector _pid, _tid, _treat, _yvals real scalar _kk, _ii, _jj st_view(_pid, ., panelid_var, tousevar) st_view(_tid, ., timeid_var, tousevar) st_view(_treat, ., treatvar, tousevar) st_view(_yvals, ., depvar, tousevar) /* ── Build indicator matrices ────────────────────────────────────────── C[i,t] = 1 iff W_{it} = 0 and Y_{it} non-missing (control) D[i,t] = 1 iff W_{it} = 1 (treated) ────────────────────────────────────────────────────────────────────── */ real matrix _C, _D _C = J(N_panel, T_panel, 0) _D = J(N_panel, T_panel, 0) for (_kk = 1; _kk <= rows(_pid); _kk++) { _ii = _pid[_kk] _jj = _tid[_kk] if (_treat[_kk] == 1) { _D[_ii, _jj] = 1 } if (_treat[_kk] == 0 & _yvals[_kk] < .) { _C[_ii, _jj] = 1 } } /* ── Ever-treated indicator: unit i with sum_t D[i,t] > 0 ─────────── */ real colvector _ever _ever = (rowsum(_D) :> 0) /* ── Pairwise overlap: overlap[i,j] = C_i' * C_j (Gram matrix) ──── */ real matrix _overlap _overlap = _C * _C' /* ── min_{i: ever-treated} max_{j != i} overlap[i,j] ───────────── */ real scalar _mvp, _maxov _mvp = . for (_ii = 1; _ii <= N_panel; _ii++) { if (_ever[_ii]) { _maxov = 0 for (_jj = 1; _jj <= N_panel; _jj++) { if (_jj != _ii & _overlap[_ii, _jj] > _maxov) { _maxov = _overlap[_ii, _jj] } } if (_mvp == . | _maxov < _mvp) { _mvp = _maxov } } } /* If no ever-treated unit exists, default to 1 (degenerate panel). */ if (_mvp == .) _mvp = 1 return(_mvp) } /*────────────────────────────────────────────────────────────────────────────── _trop_panel_health_check() Pre-estimation panel health diagnostics. Emits warnings for panels that are technically valid but may produce unreliable estimates, and aborts with an error for panels that cannot be estimated at all. Arguments n_units number of cross-sectional units n_periods number of time periods n_obs total in-sample observations pct_missing percentage of outcome cells that are missing (0-100) Side effects - Prints warnings for small panels or high missingness - Calls exit(498) if n_periods < 2 (fatal: estimation impossible) ──────────────────────────────────────────────────────────────────────────────*/ void _trop_panel_health_check(real scalar n_units, real scalar n_periods, real scalar n_obs, real scalar pct_missing) { if (n_periods < 2) { errprintf("Error: At least 2 time periods required (got %g).\n", n_periods) exit(498) } if (n_units < 3) { printf("{txt}Warning: Only %g units detected. Estimates may be unreliable.\n", n_units) } if (pct_missing > 30) { printf("{txt}Warning: %.1f%% missing outcomes detected. Consider data quality.\n", pct_missing) } if (n_obs < n_units * n_periods * 0.5) { printf("{txt}Warning: Panel is less than 50%% balanced (%g obs of %g possible).\n", n_obs, n_units * n_periods) } } end