*! 1.21 Ariel Linden 18Feb2017 // Fixed bug in IPTW for multiple treatments *! 1.20 Ariel Linden 22Jan2017 // Added tlevel option; cleaned up common support code; changed figure to show only values within common support when common is specified *! 1.10 Ariel Linden 31Dec2014 //added IPTW option *! 1.00 Ariel Linden 06June2014 program mmws, rclass version 13.0 /* obtain settings */ syntax varlist(min=1 max=1 numeric) [if] [in], /// treatment variable PScore(varlist min=1 numeric) /// propensity score provided by user [ NSTRata(numlist min=1 int) /// if user wants mmws to provide strata STRata(varlist min=1 numeric) /// if user provides strata ORDinal /// if ordinal treatments TLEVel(numlist min=1 max=1 int) /// if ordinal, which level was used for predicting pscore NOMinal /// if nominal treatments ATT /// if average treatment effect on the treated (binary treatments only) IPTW /// adds IPTW as an option COMMon /// common support FIGure /// histogram of pscore distribution(s) REPLace PREfix(str) *] gettoken treat : varlist quietly { marksample touse count if `touse' if r(N) == 0 error 2000 local N = r(N) replace `touse' = -`touse' /* drop program variables if option "replace" is chosen */ if "`replace'" != "" { local mmws : char _dta[`prefix'_mmws] if "`mmws'" != "" { foreach v of local mmws { capture drop `v' } } } if "`common'" != "" { local supp if `prefix'_support == 1 local supp1 & `prefix'_support == 1 } ********************************* ***** Binary treatments ********* ********************************* if "`ordinal'`nominal'" == "" { * Data verification * tabulate `treat' if `touse' if r(r) != 2 { di as err "With a binary treatment, `treat' must have exactly two values (coded 0 or 1)." exit 420 } else if r(r) == 2 { capture assert inlist(`treat', 0, 1) if `touse' if _rc { di as err "With a binary treatment, `treat' must be coded as either 0 or 1." exit 450 } } local Npscore : word count `pscore' if `Npscore' > 1 { di as err "With binary treatments, only one pscore can be specified" exit 198 } local Nstrata : word count `nstrata' if `Nstrata' > 1 { di as err "With binary treatments, only one Nstrata can be specified" exit 198 } * Common support * gen `prefix'_support = 1 if `touse' label var `prefix'_support "common support" sum `pscore' if `treat' ==0 & `touse', meanonly replace `prefix'_support = 0 if `pscore' r(max) & `treat'==0 & `touse' * Test for strata vs nstrata and generate 5 quantiles if nstrata not specified * if ("`strata'" != "") & ("`nstrata'" != "") { di as err "Either strata or nstrata may be specified, but not both" exit 198 } if ("`strata'" == "") & ("`nstrata'" != "") { local `nstrata' } else if ("`strata'" == "") & ("`nstrata'" == "") local nstrata = 5 * If common if ("`strata'" == "") & ("`common'" != "") { xtile `prefix'_strata = `pscore' if `touse' `supp1', nq(`nstrata') } * If no common else if ("`strata'" == "") & ("`common'" == "") { xtile `prefix'_strata = `pscore' if `touse', nq(`nstrata') } else if ("`strata'" != "") { clonevar `prefix'_strata = `strata' if `touse' } * get min/max support for later use in graphs sum `pscore' if `prefix'_support==1 & `touse', meanonly local suppmin = r(min) local suppmax = r(max) ret scalar suppmin = `suppmin' ret scalar suppmax = `suppmax' * Get overall sample size and treatment proportions * if ("`common'" != "") { count if `touse' `supp1' // Overall N local Ntot = r(N) count if `treat'==1 `supp1' & `touse' // N for treated group local Ntreat = r(N) local treatprop = `Ntreat' / `Ntot' // Proportion treated in sample (Pr=Z1) count if `treat'==0 `supp1' & `touse' // N for non-treated group local Ncontrol = r(N) local controlprop = `Ncontrol' / `Ntot' // Proportion non-treated in sample (Pr=Z0) } else { count if `touse' // Overall N local Ntot = r(N) count if `treat'==1 & `touse' // N for treated group local Ntreat = r(N) local treatprop = `Ntreat' / `Ntot' // Proportion treated in sample (Pr=Z1) count if `treat'==0 & `touse' // N for non-treated group local Ncontrol = r(N) local controlprop = `Ncontrol' / `Ntot' // Proportion non-treated in sample (Pr=Z0) } * Generate ATT weights * if "`att'" != "" { gen `prefix'_mmws =. if `touse' label var `prefix'_mmws "ATT weights for binary treatment" local propatt = (1 - `treatprop') / `treatprop' if ("`common'" != "") { levelsof `prefix'_strata if `touse' `supp1' , local(levels) foreach st of local levels { count if `prefix'_strata==`st' & `treat'==1 `supp1' & `touse' local ntreat = r(N) count if `prefix'_strata==`st' & `treat'==0 `supp1' & `touse' local ncont = r(N) local mmwc = (`ntreat' / `ncont') * `propatt' replace `prefix'_mmws = `mmwc' if `prefix'_strata==`st' & `treat'==0 & `touse' replace `prefix'_mmws = 1 if `prefix'_strata==`st' & `treat'==1 & `touse' replace `prefix'_mmws = 0 if `prefix'_support != 1 & "`common'" != "" & `touse' } } else { // not common levelsof `prefix'_strata if `touse', local(levels) foreach st of local levels { count if `prefix'_strata==`st' & `treat'==1 & `touse' local ntreat = r(N) count if `prefix'_strata==`st' & `treat'==0 & `touse' local ncont = r(N) local mmwc = (`ntreat' / `ncont') * `propatt' replace `prefix'_mmws = `mmwc' if `prefix'_strata==`st' & `treat'==0 & `touse' replace `prefix'_mmws = 1 if `prefix'_strata==`st' & `treat'==1 & `touse' } } * Generate IPTW weights for ATT if "`iptw'" != "" & "`att'" != "" { gen `prefix'_iptw = cond(`treat'==1, 1, `pscore' /(1- `pscore')) if `touse' replace `prefix'_iptw = 0 if `prefix'_support != 1 & "`common'" != "" & `touse' label var `prefix'_iptw "IPTW (ATT) weights for binary treatment" } local mmws `prefix'_support `prefix'_strata `prefix'_mmws `prefix'_iptw char def _dta[`prefix'_mmws] "`mmws'" } //end if att * Generate ATE Weights * else if "`att'" == "" { gen `prefix'_mmws =. if `touse' label var `prefix'_mmws "ATE weights for binary treatment" if ("`common'" != "") { levelsof `prefix'_strata if `touse' `supp1', local(levels) foreach st of local levels { count if `prefix'_strata==`st' `supp1' & `touse' local ntot = r(N) count if `prefix'_strata==`st' & `treat'==1 `supp1' & `touse' local ntreat = r(N) count if `prefix'_strata==`st' & `treat'==0 `supp1' & `touse' local ncont = r(N) local mmwt = (`ntot' / `ntreat') * `treatprop' local mmwc = (`ntot' / `ncont') * `controlprop' replace `prefix'_mmws = `mmwc' if `prefix'_strata==`st' & `treat'==0 & `touse' replace `prefix'_mmws = `mmwt' if `prefix'_strata==`st' & `treat'==1 & `touse' replace `prefix'_mmws = 0 if `prefix'_support !=1 & "`common'" != "" & `touse' } } // end common else { levelsof `prefix'_strata if `touse', local(levels) foreach st of local levels { count if `prefix'_strata==`st' & `touse' local ntot = r(N) count if `prefix'_strata==`st' & `treat'==1 & `touse' local ntreat = r(N) count if `prefix'_strata==`st' & `treat'==0 & `touse' local ncont = r(N) local mmwt = (`ntot' / `ntreat') * `treatprop' local mmwc = (`ntot' / `ncont') * `controlprop' replace `prefix'_mmws = `mmwc' if `prefix'_strata==`st' & `treat'==0 & `touse' replace `prefix'_mmws = `mmwt' if `prefix'_strata==`st' & `treat'==1 & `touse' } } // end not common } // end ATE * Generate IPTW weights for ATE if "`iptw'" != "" & "`att'" == "" { gen `prefix'_iptw = cond(`treat'==1, 1/`pscore', 1/(1- `pscore')) if `touse' replace `prefix'_iptw = 0 if `prefix'_support !=1 & "`common'" != "" & `touse' label var `prefix'_iptw "IPTW (ATE) weights for binary treatment" } local mmws `prefix'_support `prefix'_strata `prefix'_mmws `prefix'_iptw char def _dta[`prefix'_mmws] "`mmws'" * figure on or off common support * if ("`figure'" != "") { if ("`figure'" != "") { histogram `pscore' `supp', dens by(`treat', cols(1) legend(off)) xline(`suppmin' `suppmax') xla(0(.20)1) kdensity } else { histogram `pscore', dens by(`treat', cols(1) legend(off)) xline(`suppmin' `suppmax') xla(0(.20)1) kdensity } } //end fig } // Closing bracket for binary treatments ********************************** ***** Ordinal treatments ********* ********************************** if ("`ordinal'" != "") & ("`nominal'" != "") { di as err "Either ordinal or nominal options can be specified, but not both" exit 198 } if ("`ordinal'" != "") { * Data verification * tabulate `treat' if `touse' if r(r) < 3 { di as err "With an ordinal treatment, `treat' must have more than two values." exit 420 } * Verify there is a matching number of pscores and nstrata defined, then generate strata * local Npscore : word count `pscore' if `Npscore' > 1 { di as err "With ordinal treatments, only one pscore can be specified" exit 198 } local Nstrata : word count `nstrata' if `Nstrata' > 1 { di as err "With ordinal treatments, only one Nstrata can be specified" exit 198 } * Common support - for ordinal -- default is lowest treatment level in data * if ("`tlevel'" == "") { sum `treat' if `touse', meanonly local tlevel = r(min) // use the lowest treatment level as guide for support } gen `prefix'_support = 1 if `touse' label var `prefix'_support "common support" sum `pscore' if `treat' == `tlevel' & `touse', meanonly replace `prefix'_support = 0 if (`pscore' < r(min) | `pscore' > r(max)) & `touse' * Test for strata vs nstrata and generate 5 quantiles if nstrata not specified * if ("`strata'" != "") & ("`nstrata'" != "") { di as err "Either strata or nstrata may be specified, but not both" exit 198 } if ("`strata'" == "") & ("`nstrata'" != "") { local `nstrata' } else if ("`strata'" == "") & ("`nstrata'" == "") local nstrata = 5 * If common if ("`strata'" == "") & ("`common'" != "") { xtile `prefix'_strata = `pscore' if `touse' `supp1', nq(`nstrata') } * If no common else if ("`strata'" == "") & ("`common'" == "") { xtile `prefix'_strata = `pscore' if `touse', nq(`nstrata') } else if ("`strata'" != "") { clonevar `prefix'_strata = `strata' if `touse' } * get min/max support for later use in graphs sum `pscore' if `prefix'_support==1 & `touse', meanonly local suppmin = r(min) local suppmax = r(max) ret scalar suppmin = `suppmin' ret scalar suppmax = `suppmax' if ("`common'" != "") { count if `touse' `supp1' local Nall = r(N) // Total N of sample } else { count if `touse' local Nall = r(N) // Total N of sample } * Generate weights * gen `prefix'_mmws =. if `touse' label var `prefix'_mmws "weights for ordinal treatments" if ("`common'" != "") { levelsof `treat' if `touse' `supp1', local(treatment) foreach i of local treatment { count if `treat'==`i' `supp1' & `touse' local ntreat = r(N) // N for each treatment category local treatprop = `ntreat' / `Nall' // Proportion of treated in sample (Pr=Z) levelsof `prefix'_strata if `touse' `supp1', local(stratae) foreach s of local stratae { count if `prefix'_strata==`s' `supp1' & `touse' local jstrata = r(N) // N in each strata count if `prefix'_strata==`s' & `treat' == `i' `supp1' & `touse' local n_ij = r(N) // N in treatment/strata cell replace `prefix'_mmws = (`jstrata' / `n_ij' ) * `treatprop' if `prefix'_strata == `s' & `treat' == `i' & `touse' `supp1' replace `prefix'_mmws = 0 if `prefix'_support !=1 & "`common'" != "" & `touse' } } } // end commom else { levelsof `treat' if `touse', local(treatment) foreach i of local treatment { count if `treat'==`i' & `touse' local ntreat = r(N) // N for each treatment category local treatprop = `ntreat' / `Nall' // Proportion of treated in sample (Pr=Z) levelsof `prefix'_strata if `touse', local(stratae) foreach s of local stratae { count if `prefix'_strata==`s' & `touse' local jstrata = r(N) // N in each strata count if `prefix'_strata==`s' & `treat' == `i' & `touse' local n_ij = r(N) // N in treatment/strata cell replace `prefix'_mmws = (`jstrata' / `n_ij' ) * `treatprop' if `prefix'_strata == `s' & `treat' == `i' & `touse' } } } // end no common * Generate IPTW weights for ATE if ("`iptw'" != "") { gen `prefix'_iptw = 1/`pscore' if `touse' replace `prefix'_iptw = 0 if `prefix'_support !=1 & "`common'" != "" & `touse' label var `prefix'_iptw "IPTW weights for ordinal treatment" } local mmws `prefix'_support `prefix'_strata `prefix'_mmws `prefix'_iptw char def _dta[`prefix'_mmws] "`mmws'" * figure on or off common support * if ("`figure'" != "") { if ("`common'" != "") { histogram `pscore' `supp', dens by(`treat', cols(1) legend(off)) xline(`suppmin' `suppmax') xla(0(.20)1) kdensity } else { histogram `pscore', dens by(`treat', cols(1) legend(off)) xline(`suppmin' `suppmax') xla(0(.20)1) kdensity } } // end fig } // Closing bracket for ordinal treatments ********************************** ***** Nominal treatments **** ********************************** if ("`ordinal'" != "") & ("`nominal'" != "") { di as err "Either ordinal or nominal options can be specified, but not both" exit 198 } if ("`nominal'" != "") { * Data verification * tabulate `treat' if `touse' if r(r) < 3 { di as err "With a nominal treatment, `treat' must have more than two values." exit 420 } * Verify there is a matching number of pscores and nstrata defined, then generate strata * local Npscore : word count `pscore' tabulate `treat' if `touse' if r(r) != `Npscore' { di as err "For nominal treatments, there should be one pscore for each treatment level" exit 198 } if ("`strata'" != "") & ("`nstrata'" != "") { di as err "Either strata or nstrata may be specified, but not both" exit 198 } local Nstrata : word count `nstrata' if `Nstrata' != `Npscore' & `Nstrata' !=0 & `touse' { di as err "For nominal treatments, there should be one stratification specified for each pscore" exit 198 } local Nstratae : word count `strata' if `Nstratae' != `Npscore' & `Nstratae' !=0 & `touse' { di as err "For nominal treatments, there should be one stratification specified for each pscore" exit 198 } * Common support * gen `prefix'_support = 1 if `touse' label var `prefix'_support "common support" levelsof `treat', local(levels) foreach tr of local levels { foreach p of varlist `pscore' { sum `p' if `treat' ==`tr' & `touse', meanonly replace `prefix'_support = 0 if (`p'r(max)) & `treat' !=`tr' & `touse' } } * Test for strata vs nstrata * if ("`strata'" != "") & ("`nstrata'" == "") { forval i = 1/`Nstratae' { local n : word `i' of `strata' clonevar `prefix'_strata`i' = `n' if `touse' local bag `bag' `prefix'_strata`i' } } * Generate strata using nstrata * if ("`nstrata'" != "") & ("`strata'" == ""){ if ("`common'" != "") { forval i = 1/`Npscore' { local v : word `i' of `pscore' local n : word `i' of `nstrata' xtile `prefix'_strata`i' = `v' if `touse' `supp1', nq(`n') local bag `bag' `prefix'_strata`i' } } //if common else { forval i = 1/`Npscore' { local v : word `i' of `pscore' local n : word `i' of `nstrata' xtile `prefix'_strata`i' = `v' if `touse', nq(`n') local bag `bag' `prefix'_strata`i' } } //if not common } // end nstrata with specified # strata else if ("`nstrata'" == "") & ("`strata'" == "") { if ("`common'" != "") { forval i = 1/`Npscore' { local v : word `i' of `pscore' xtile `prefix'_strata`i' = `v' if `touse' `supp1', nq(5) local bag `bag' `prefix'_strata`i' } } // if common else { forval i = 1/`Npscore' { local v : word `i' of `pscore' xtile `prefix'_strata`i' = `v' if `touse', nq(5) local bag `bag' `prefix'_strata`i' } } //if not common } // end nstrata with default (5) strata * get min/max support for later use in graphs forval i = 1/`Npscore' { local v : word `i' of `pscore' sum `v' if `prefix'_support==1 & `touse', meanonly local suppmin`i' = r(min) local suppmax`i' = r(max) ret scalar suppmin`i' = `suppmin`i'' ret scalar suppmax`i' = `suppmax`i'' } * Generate weights * levelsof `treat' if `touse', local(treatment) matrix input A = (`treatment') // generate matrix of treatment for parallel reference gen `prefix'_mmws =. if `touse' label var `prefix'_mmws "weights for nominal treatments" * common if ("`common'" != "") { local T=1 foreach s of varlist `prefix'_strata* { // loop over each Strata local tr=el("A",1,`T') // map strata to treatment level derived above tab `s' if `touse' `supp1', matcell(f) // get counts in each strata and generate matrix local sN = r(N) tab `s' if `treat'== `tr' `supp1' & `touse', matcell(f1) // get counts in each strata/treatment and generate matrix local stN = r(N) local treatprop = `stN' / `sN' // get proportion of treated within each strata (Pr=Z) matrix C = J(`=rowsof(f)' ,`=colsof(f)',0) // generate a new matrix with weights for each strata/treatment forvalues i = 1/`=rowsof(f)' { forvalues j = 1/`=colsof(f)' { matrix C[`i',`j']= f[`i',`j']/f1[`i',`j'] * `treatprop' } } replace `prefix'_mmws = C[`s', 1] if `treat'==`tr' & `touse' // put weights in data file replace `prefix'_mmws = 0 if `prefix'_support != 1 & "`common'" != "" & `touse' local T=`T'+1 } //close foreach s } //close common -- yes else { local T=1 foreach s of varlist `prefix'_strata* { // loop over each Strata local tr=el("A",1,`T') // map strata to treatment level derived above tab `s' if `touse', matcell(f) // get counts in each strata and generate matrix local sN = r(N) tab `s' if `treat'== `tr' & `touse', matcell(f1) // get counts in each strata/treatment and generate matrix local stN = r(N) local treatprop = `stN' / `sN' // get proportion of treated within each strata (Pr=Z) matrix C = J(`=rowsof(f)' ,`=colsof(f)',0) // generate a new matrix with weights for each strata/treatment forvalues i = 1/`=rowsof(f)' { forvalues j = 1/`=colsof(f)' { matrix C[`i',`j']= f[`i',`j']/f1[`i',`j'] * `treatprop' } } replace `prefix'_mmws = C[`s', 1] if `treat'==`tr' & `touse' // put weights in data file local T=`T'+1 } //close foreach s } // close common -- no * Generate IPTW weights for ATE if "`iptw'" != "" { gen `prefix'_iptw = . if `touse' label var `prefix'_iptw "IPTW weights for nominal treatment" tabulate `treat' if `touse' local trcount = r(r) forvalues x = 1/`trcount' { local tr=el("A",1,`x') // get treatment level value derived in levelsof above local v: word `x' of `pscore' // get position of respective pscore replace `prefix'_iptw = 1/`v' if `treat' == `tr' & `touse' // compute iptw based on respective treatment and pscore } replace `prefix'_iptw = 0 if `prefix'_support !=1 & "`common'" != "" & `touse' } local mmws `prefix'_support `bag' `prefix'_mmws `prefix'_iptw char def _dta[`prefix'_mmws] "`mmws'" * figure on or off common support * if ("`figure'" != "") { if ("`common'" != "") { forval i = 1/`Npscore' { local v : word `i' of `pscore' local fig fig`i' histogram `v' `supp', dens by(`treat', cols(1) legend(off)) xline(`suppmin`i'' `suppmax`i'') xla(0(.20)1) name(`fig', replace) nodraw kdensity local figname `figname' `fig' } } else { forval i = 1/`Npscore' { local v : word `i' of `pscore' local fig fig`i' histogram `v', dens by(`treat', cols(1) legend(off)) xline(`suppmin`i'' `suppmax`i'') xla(0(.20)1) name(`fig', replace) nodraw kdensity local figname `figname' `fig' } } graph combine `figname', altshrink name(combined, replace) } // end fig } // Closing bracket for nominal treatments } // Closing bracket for quietly end