*! pidid.ado - Path-Integrated Difference-in-Differences (PI-DiD) *! Implements the framework of Salavi (2026), *! "Path-Integrated Difference in Difference (PI-DiD) Framework" *! version 1.0.1 11jul2026 program define pidid, rclass version 14.0 syntax varname(numeric) [if] [in], PANELvar(varname) TIMEvar(varname) /// TREATvar(varname) T0(real) [T1(real -999) GRaph NOTABle /// XTITLE(string) YTITLE(string) TITLE(string) NAME(string)] marksample touse markout `touse' `panelvar' `timevar' `treatvar' quietly count if `touse' if r(N) == 0 { display as error "no observations satisfy the estimation sample" exit 2000 } local depvar `varlist' *---------------------------------------------------------------- * 0. treatvar must be coded 0 (control) / 1 (treated) *---------------------------------------------------------------- tempvar tchk quietly generate `tchk' = `treatvar' if `touse' quietly summarize `tchk' if r(min) != 0 | r(max) != 1 { display as error "treatvar() must be coded 0 (control) and 1 (treated)" exit 198 } preserve quietly keep if `touse' *---------------------------------------------------------------- * 1. Collapse to the two group-time paths c0(t) and c1(t) *---------------------------------------------------------------- quietly collapse (mean) `depvar', by(`treatvar' `timevar') quietly reshape wide `depvar', i(`timevar') j(`treatvar') capture confirm variable `depvar'0 if _rc { display as error "no control observations (treatvar==0) found" restore exit 2000 } capture confirm variable `depvar'1 if _rc { display as error "no treated observations (treatvar==1) found" restore exit 2000 } rename `depvar'0 __c0__ rename `depvar'1 __c1__ quietly sort `timevar' quietly summarize `timevar' local tmin = r(min) local tmax = r(max) if `t1' == -999 local t1 = `tmax' if `t0' < `tmin' | `t0' > `tmax' { display as error "t0(`t0') falls outside the observed time range [`tmin', `tmax']" restore exit 198 } if `t1' <= `t0' { display as error "t1() must be strictly greater than t0()" restore exit 198 } *---------------------------------------------------------------- * 2. Gap function tau(t) = c1(t) - c0(t) and pre-trend check *---------------------------------------------------------------- quietly generate double __tau__ = __c1__ - __c0__ quietly summarize __tau__ if `timevar' == `t0' local tau_t0 = r(mean) *---------------------------------------------------------------- * 3. Optional graph (Figure 2 style) drawn on the FULL path, * before the sample is trimmed to the integration window *---------------------------------------------------------------- if "`graph'" != "" { local gxtitle "Time" local gytitle "`depvar'" local gtitle "PI-DiD: treated vs. counterfactual path" if `"`xtitle'"' != "" local gxtitle `"`xtitle'"' if `"`ytitle'"' != "" local gytitle `"`ytitle'"' if `"`title'"' != "" local gtitle `"`title'"' local gname if `"`name'"' != "" local gname "name(`name', replace)" twoway /// (rarea __c1__ __c0__ `timevar' if `timevar' >= `t0' & `timevar' <= `t1', /// color(gs12%60)) /// (line __c0__ `timevar', lpattern(dash) lcolor(navy)) /// (line __c1__ `timevar', lcolor(cranberry)) /// , /// xline(`t0' `t1', lpattern(shortdash) lcolor(gs8)) /// legend(order(2 "Counterfactual c0(t)" 3 "Treated c1(t)" /// 1 "{&sigma} (cumulative effect)") pos(6) rows(1)) /// xtitle(`"`gxtitle'"') ytitle(`"`gytitle'"', orientation(horizontal)) title(`"`gtitle'"') /// `gname' } *---------------------------------------------------------------- * 4. Trapezoidal path integral over [t0, t1] * sigma = int_t0^t1 tau(t) dt *---------------------------------------------------------------- quietly keep if `timevar' >= `t0' & `timevar' <= `t1' quietly sort `timevar' quietly count if r(N) < 2 { display as error "need at least two observed time points between t0() and t1() to integrate" restore exit 2000 } quietly generate double __dt__ = `timevar' - `timevar'[_n-1] quietly generate double __trapz__ = 0.5*(__tau__ + __tau__[_n-1])*__dt__ if _n > 1 quietly summarize __trapz__, meanonly local sigma = r(sum) quietly summarize __tau__ if `timevar' == `t0' local tau_a = r(mean) quietly summarize __tau__ if `timevar' == `t1' local tau_b = r(mean) local did_static = `tau_b' - `tau_a' local att_path = `sigma' / (`t1' - `t0') tempfile pathdata quietly save `pathdata' restore *---------------------------------------------------------------- * 5. Reporting *---------------------------------------------------------------- if "`notable'" == "" { display as text "" display as text "{hline 60}" display as text "Path-Integrated Difference-in-Differences (PI-DiD)" display as text "{hline 60}" display as text "Outcome: " as result "`depvar'" display as text "Panel id: " as result "`panelvar'" display as text "Time variable: " as result "`timevar'" display as text "Treatment indicator: " as result "`treatvar'" display as text "Window: " as result "t0 = `t0' t1 = `t1'" display as text "{hline 60}" preserve quietly use `pathdata', clear display as text %8s "time" _col(12) "c0(t)" _col(24) "c1(t)" _col(36) "tau(t)" local N = _N forvalues i = 1/`N' { local tt = `timevar'[`i'] local c0v = __c0__[`i'] local c1v = __c1__[`i'] local tv = __tau__[`i'] display as result %8.2f `tt' _col(12) %8.2f `c0v' _col(24) %8.2f `c1v' _col(36) %8.2f `tv' } restore display as text "{hline 60}" } display as text "" display as text "Pre-treatment gap, tau(t0) = " as result %9.3f `tau_t0' /// as text " (parallel pre-trends require ~0)" display as text "Cumulative causal effect, sigma = " as result %9.3f `sigma' display as text "Path-integrated ATT, tau-bar = " as result %9.3f `att_path' display as text "Conventional static DiD (endpoints) = " as result %9.3f `did_static' display as text "" if abs(`did_static') < 1e-6 & abs(`sigma') > 1e-6 { display as text "{bf:Note:} static DiD is (near) zero while the path-integrated" display as text "effect is not -- this is the endpoint-subtraction bias" display as text "documented in Salavi (2026)." } return scalar sigma = `sigma' return scalar att_path = `att_path' return scalar did_static = `did_static' return scalar tau_t0 = `tau_t0' return scalar t0 = `t0' return scalar t1 = `t1' end