*! pididplot.ado - Graph the causal impact of a Path-Integrated DiD analysis *! Companion plotting command to pidid.ado *! Draws (1) the treated vs. counterfactual paths with the cumulative *! effect shaded, and (2) the running cumulative effect sigma(t), which *! plateaus even after tau(t) has decayed back to zero -- the visual *! signature of the endpoint-subtraction bias in Salavi (2026). *! version 1.0.3 11jul2026 program define pididplot version 14.0 syntax varname(numeric) [if] [in], PANELvar(varname) TIMEvar(varname) /// TREATvar(varname) T0(real) [T1(real -999) T2(real -999) /// XTITLE(string) TITLE(string) SUBTITLE(string) NAME(string) /// SAVING(string) SCHEME(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' 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 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 } capture assert `t2' == -999 if _rc { if `t2' <= `t0' | `t2' > `t1' { display as error "t2() must lie strictly between t0() and t1()" restore exit 198 } } quietly generate double __tau__ = __c1__ - __c0__ quietly count if `timevar' == `t0' if r(N) == 0 { display as error "t0(`t0') is not an observed value of `timevar'" restore exit 198 } *---------------------------------------------------------------- * 2. Running (partial-sum) cumulative effect: sigma(t) for t>=t0, * via the trapezoidal rule; undefined/blank before t0 *---------------------------------------------------------------- quietly generate double __cumsigma__ = . quietly count local N = _N tempname run scalar `run' = 0 forvalues i = 1/`N' { local ti = `timevar'[`i'] if `ti' == `t0' { scalar `run' = 0 quietly replace __cumsigma__ = 0 in `i' } else if `ti' > `t0' { local im1 = `i' - 1 local tprev = `timevar'[`im1'] local taucur = __tau__[`i'] local tauprv = __tau__[`im1'] scalar `run' = `run' + 0.5*(`taucur' + `tauprv')*(`ti' - `tprev') quietly replace __cumsigma__ = `run' in `i' } } quietly summarize __cumsigma__ if `timevar' == `t1', meanonly local sigma_final = r(mean) quietly summarize __tau__ if `timevar' == `t0', meanonly local tau_a = r(mean) quietly summarize __tau__ if `timevar' == `t1', meanonly local tau_b = r(mean) local did_static = `tau_b' - `tau_a' *---------------------------------------------------------------- * 3. Build the two panels *---------------------------------------------------------------- local gxtitle "Time" if `"`xtitle'"' != "" local gxtitle `"`xtitle'"' local gtitle "PI-DiD causal impact" if `"`title'"' != "" local gtitle `"`title'"' local xln "`t0' `t1'" if `t2' != -999 local xln "`t0' `t2' `t1'" * default to the Stata Journal-recommended "sj" scheme, which is * designed to remain legible when printed in grayscale if `"`scheme'"' == "" local scheme sj tempname pathgraph cumgraph twoway /// (rarea __c1__ __c0__ `timevar' if `timevar' >= `t0' & `timevar' <= `t1', /// color(gs12%60)) /// (line __c0__ `timevar', lpattern(dash) lcolor(navy) lwidth(medthick)) /// (line __c1__ `timevar', lcolor(cranberry) lwidth(medthick)) /// , /// xline(`xln', lpattern(shortdash) lcolor(gs8)) /// legend(order(2 "Counterfactual c0(t)" 3 "Treated c1(t)" /// 1 "Area = {&sigma}") pos(6) rows(1) size(small)) /// xtitle(`"`gxtitle'"') ytitle("`depvar'", orientation(horizontal)) /// title("Realized vs. counterfactual path", size(medium)) /// scheme(`scheme') name(`pathgraph', replace) local att_final = `sigma_final' / (`t1' - `t0') local notetext = "Path-integrated ATT ({&tau}-bar) = " + strofreal(`att_final', "%9.2f") + /// " Static endpoint DiD = " + strofreal(`did_static', "%9.2f") twoway /// (line __cumsigma__ `timevar' if `timevar' >= `t0' & `timevar' <= `t1', /// lcolor(cranberry) lwidth(medthick) msymbol(O) mcolor(cranberry) recast(connected)) /// , /// yline(0, lcolor(gs8)) /// yline(`sigma_final', lpattern(shortdash) lcolor(cranberry)) /// xline(`xln', lpattern(shortdash) lcolor(gs8)) /// xtitle(`"`gxtitle'"') ytitle("Cumulative effect {&sigma}(t)", orientation(horizontal)) /// title("Path-integrated cumulative impact", size(medium)) /// note(`"`notetext'"', size(vsmall)) /// scheme(`scheme') name(`cumgraph', replace) local combopts if `"`subtitle'"' != "" local combopts `"`combopts' subtitle(`"`subtitle'"')"' local nameopt if `"`name'"' != "" local nameopt "name(`name', replace)" local saveopt if `"`saving'"' != "" local saveopt "saving(`saving', replace)" graph combine `pathgraph' `cumgraph', /// rows(2) title(`"`gtitle'"') `combopts' `nameopt' `saveopt' scheme(`scheme') restore display as text "" display as text "Graph drawn. Cumulative effect at t1 (sigma) = " as result %9.3f `sigma_final' display as text "Static endpoint DiD = " as result %9.3f `did_static' end