*! version 1.2  Justin Wiltshire 11/11/2024 - Implement synthetic control placebo sampling for averaging and calculating p-values

* Program stackscpvals: automates the stacking and averaging of (generally, modified)
* synthetic control estimated gaps for multiple treated units and the donor pool units,
* the creation of the sample empirical ditribution of placebo
* average treatment effects, and the calculation of specified p-values.
program stackscpvals, rclass
	version 15.1
	#delimit ;
		syntax ,
		gap(string)
		time(string)
		unit(string)
		pvalues(string)
		filepath(string)
		[
		filepath_2(string)
		filepath_3(string)
		filepath_4(string)
		filepath_5(string)
		filepath_6(string)
		filepath_7(string)
		filepath_8(string)
		filepath_9(string)
		savepath(string)
		savename(string)
		keeptrunits(numlist min=2)
		numavg(numlist min=1 max=1 >0 integer)
		emin(numlist min=1 max=1 <0 integer)
		emax(numlist min=1 max=1 >0 integer)
		avgwts(string)
		balance
		*
		]
		;
	#delimit cr

	* Rename locals
	local tvar "`time'"
	local pvar "`unit'"
	local avgs "`numavg'"
	if "`uW'" != "" {
		local unique_W "unique_W"
	}

	* If keeptrunits() is specified
	if "`keeptrunits'" != "" {
		local _trunits=subinstr("`keeptrunits'", " ", ",", .)
	}
	if "`keeptrunits'" == "" {
		local keeptrunits ""
		local _trunits "."
	}

	* Initialize pvalues specification locals
	local pvaluesvariance = 0
	local pvaluesrmspe = 0

	* Parse the pvalues() string
	foreach s of local pvalues {
		capture assert "`s'" == "variance" | "`s'" == "rmspe"
		if _rc != 0 {
			di as err "pvalues() contains an invalid specification. Only -variance-, -rmspe-, or both are allowed. Specifying pvalues() without any options will be treated as if pvalues() is not specified"
			exit 198
		}
		local pvalues`s' = 1
	}
	if "`avgs'" == "" {
		local avgs = 100
	}

	if `pvaluesrmspe' == 1 & `avgs' < 30 {
		local avgs = 30
	}
	if `pvaluesvariance' == 1 {
		local avgs = 1000
	}

	* Tempfiles
	tempfile core
	tempfile core2
	tempfile core3

	* Get filelist
	local filelist: dir "`filepath'" files "*.dta"

	* Append the estimates and adjust as appropriate
	local counter = 0
	foreach f of local filelist {
		if strpos("`f'", "_ate") == 0 {
			local g = subinstr("`f'", "tr_", "", .)
			local g = subinstr("`g'", ".dta", "", .)
			if "`keeptrunits'" == "" | ("`keeptrunits'" != "" & inlist(`g', `_trunits')) {
				qui use "`filepath'/`f'", clear

				* Ensure specified variables exist
				foreach y in gap time unit {
					capture confirm variable ``y''
					if _rc != 0 {
						di as err "`y'() was defined as ``y'', but variable ``y'' is not found in the data"
						exit 198
					}
				}
				if "`avgwts'" != "" {
					capture confirm variable `avgwts'
					if _rc != 0 {
						di as err "avgwts() was defined as `avgwts', but variable `avgwts' is not found in the data"
						exit 198
					}
				}


				* Confirm local macros
				qui rename _time `tvar'
				if `counter' == 0 {
					capture assert `pvar'
					if _rc != 0 {
						local pvar ""
					}
				}

				* Clean up
				qui keep `pvar' `tvar' `gap' `unique_W' trunit trperiod `avgwts'
				qui keep if !mi(`gap')
				qui gen _tm = `tvar'
				qui format _tm `tvarformat'
				if "`emin'" != "" | "`emax'" != ""  {
					capture assert "`emin'" != ""
					if _rc != 0 {
						local emin = -5
					}
					capture assert "`emax'" != ""
					if _rc != 0 {
						local emax = 5
					}
					qui replace _tm = `tvar' - trperiod
					qui keep if inrange(_tm, `emin', `emax')
				}
				if `counter' > 0 {
					qui append using "`core'"
				}
				qui save "`core'", replace
				local counter = `counter' + 1
			}
		}
	}

	* If additional filepaths are specified
	forval m = 2/9 {
		if "`filepath_`m''" != "" {

			* Get filelist
			local filelist: dir "`filepath_`m''" files "*.dta"

			* Append the estimates and adjust as appropriate
			foreach f of local filelist {
				if strpos("`f'", "_ate") == 0 {
					local g = subinstr("`f'", "tr_", "", .)
					local g = subinstr("`g'", ".dta", "", .)
					if "`keeptrunits'" == "" | ("`keeptrunits'" != "" & inlist(`g', `_trunits')) {
						qui use "`filepath_`m''/`f'", clear

						* Ensure specified variables exist
						foreach y in gap time unit {
							capture confirm variable ``y''
							if _rc != 0 {
								di as err "`y'() was defined as ``y'', but ``y'' is not found in the data"
								exit 198
							}
						}

						* Confirm local macros
						qui rename _time `tvar'
						if `counter' == 0 {
							capture assert `avgwts'
							if _rc != 0 {
								local avgwts ""
							}
							capture assert `pvar'
							if _rc != 0 {
								local pvar ""
							}
						}

						* Clean up
						qui keep `pvar' `tvar' `gap' `unique_W' trunit trperiod `avgwts'
						qui keep if !mi(`gap')
						qui gen _tm = `tvar'
						qui format _tm `tvarformat'
						if "`emin'" != "" {
							qui replace _tm = `tvar' - trperiod
							qui keep if inrange(_tm, `emin', `emax')
						}
						qui append using "`core'"
						qui save "`core'", replace
					}
				}
			}
		}
	}

	* Update save filepath if specified
	if "`savepath'" != "" {
		local filepath "`savepath'"
	}

	* Update save filename if specified
	local keepname "stackedsc_att"
	if "`savename'" != "" {
		local keepname "`savename'"
	}

	* Calculate the estimated average treatment effect for treated units
	di "Calculating the estimated average treatment effect for treated units"
	di
	qui use "`core'", clear
	if "`uW'" != "" {
		qui levelsof trunit if unique_W == 0, local(droptrunits)
		if "`droptrunits'" != "" {
			di
			di as txt "Treated units `droptrunits' do not have unique W matrices and are being dropped as -unique_w- is specified in the allsynth option stacked()"
			di
		}
		qui levelsof trunit if unique_W == 1, local(keeptrunits)
		if "`keeptrunits'" == "" {
			di
			di as err "No treated units are left after dropping units without unique W matrics. Try a different specification"
			exit 198
		}
		if "`droptrunits'" == "" {
			di
			di as txt"All treated units have unique W matrices"
			di
		}
		qui keep if unique_W == 1
	}
	qui sum trperiod
	local trperiod = r(mean)
	capture assert trperiod == `trperiod' if !mi(trperiod)
	if _rc != 0 & "`emin'" == "" {
		local emin = -5
		local emax = 5
		qui replace _tm = `tvar' - trperiod
		qui keep if inrange(_tm, `emin', `emax')
	}
	if "`emin'" != "" {
		local trperiod = 0
		qui sum `tvar'
		local eperiods = r(max) - r(min)
		format _tm %9.0g
	}
	qui save "`core'", replace
	qui keep if `pvar' == trunit
	if "`balance'" != "" {
		quietly {
			qui levelsof `pvar', local(_Units)
			local nunits : list sizeof _Units
			bysort _tm: gen _tmCount = _N
			qui keep if _tmCount == `nunits'
		}
	}
	if "`avgwts'" != "" {
		local awts "[aw=`avgwts']"
	}
	collapse (mean) `gap' `awts', by(_tm)
	qui drop if mi(_tm)
	qui compress
	qui save "`filepath'/`keepname'.dta", replace
	list
	di
	di as txt "Estimated average treatment effects saved in `filepath'/`keepname'.dta"
	di

	* Set seed
	set seed 12345

	di
	di "Randomly sampling `avgs' placebo average treatment effects"
	di
	qui use "`core'", clear
	qui levelsof trunit, local(trunits)
	qui drop if `pvar' == trunit
	qui save "`core2'", replace
	forval n = 1/`avgs' {
		if mod(`n',20) == 0 {
			display "`n'" _continue
		}
		if mod(`n',20) != 0 {
			if mod(`n',5)== 0 {
				display "." _continue
			}
		}

		* Randomly select a placebo treated unit from each actually treated unit
		qui use "`core2'", clear
		qui gen p = runiform() if `tvar' == trperiod
		qui sort trunit p
		bysort trunit: gen px = (_n == 1)
		bysort `pvar' trunit: egen keepdonor = max(px)
		qui keep if keepdonor == 1
		qui sort `pvar' `tvar'

		* Calculate the sample average treatment effect
		if "`balance'" != "" {
			qui levelsof trunit, local(_Units)
			local nunits : list sizeof _Units
			bysort _tm: gen _tmCount = _N
			qui keep if _tmCount == `nunits'
		}
		collapse (mean) `gap' `awts', by(_tm)
		qui compress
		qui gen _placeboID = `n'
		if `n' > 1 {
			qui append using "`core3'"
		}
		qui save "`core3'", replace
		}
		qui append using "`filepath'/`keepname'.dta"
	qui replace _placeboID = 0 if mi(_placeboID)
	qui save "`core3'", replace

	quietly {

		* Placebo RMSPE and p-val estimation
		if `pvaluesrmspe' == 1 {
			*** Calculate the RMSPEs and RMSPE p-values
			local pvar _placeboID
			local tvar _tm
			qui sum `tvar'
			local T0 = `trperiod' - r(min)
			local T1 = r(max) - `trperiod' + 1
			qui levelsof `tvar' if `tvar' >= `trperiod', local(trtvars)
			qui gen rmspe = .
			qui levelsof(`pvar'), local(id)
			bysort `tvar': gen N = _N
			foreach i of local id {
				qui egen xpre = total(`gap'^2) if `tvar' < `trperiod' & `pvar' == `i'
				qui gen xpre2 = xpre/`T0'
				qui egen mspe_pre = max(xpre2) if `pvar' == `i'
				qui drop x*

				* Treated period
				foreach tt of local trtvars {
					qui egen xpost2 = mean(`gap'^2) if inrange(`tvar', `trperiod', `tt') & `pvar' == `i'
					qui egen mspe_post_`tt' = max(xpost2) if `pvar' == `i'
					qui replace rmspe = mspe_post_`tt'/mspe_pre if `pvar' == `i' & `tvar' == `tt'
					qui drop x* mspe_post_`tt'
				}
				qui drop mspe*
			}

			* Generate RMSPE-ranked p-values
			qui gsort `tvar' -rmspe
			bysort `tvar': gen rmspe_rank = _n  if `tvar' >= `trperiod'
			qui gen p = rmspe_rank/N if `tvar' >= `trperiod'
			qui order `pvar' `tvar' `gap' rmspe* p N
			local pvalrmspevars "rmspe* p N"
		}
		qui save "`core3'", replace

		* Placebo variance estimation
		if `pvaluesvariance' == 1 {

			local pvar _placeboID
			local tvar _tm

			* Set seed
			set seed 12345

			* Re-sample 1000 times
			tempfile _Plvar_S
			qui use "`core3'", clear
			qui drop if `pvar' == 0

			* Get the estimated variance and standard eror
			qui collapse (mean) _Avg_plgap=`gap', by(`tvar')
			qui merge 1:m `tvar' using "`core3'", nogen norep
			qui gen _Sq_demean = (`gap' - _Avg_plgap)^2
			qui collapse (mean) _Pl_var=_Sq_demean `_plvarbc', by(`tvar')
			qui gen _Se = sqrt(_Pl_var)
			qui save "`_Plvar_S'", replace

			* Merge in and estimate 95% CIs
			qui merge 1:m `tvar' using "`core3'", nogen norep
			qui keep if `pvar' == 0
			qui keep `tvar' `pvar' `gap' _Se
			qui gen _Tstat = abs(`gap')/_Se
			qui gen _Pval = 2*ttail(`avgs',(_Tstat))
			qui gen LB_95 = `gap' - 1.96*_Se
			qui gen UB_95 = `gap' + 1.96*_Se
			qui drop `gap'
			qui merge 1:1 `tvar' `pvar' using "`core3'", nogen norep
			qui sort `tvar' `pvar' `gap' `pvalrmspevars' _Se* _Pval*
			local pvalvariancevars "_Se* _Tstat* _Pval* LB* UB*"
			qui compress
		}
	}

	qui save "`filepath'/`keepname'_distn.dta", replace
	qui sort _placeboID _tm
	qui rename `gap' corrected_gap
	di ""
	di
	di as txt "Sample distribution saved in `filepath'/`keepname'_distn.dta"
	di

end