* VERSION 1.29 (SJ2-4: st0026) * OCTOBER 8, 2002 * LAST REVISOR: SOB * * program define attnw, rclass version 7.0 syntax varlist [if] [in] [fweight iweight pweight], [ pscore(varname) logit index comsup DETail BOOTstrap Reps(int 50) NOIsily Dots ] tokenize `varlist' local Y `1' local T `2' preserve if `"`if'"'!="" | "`in'"!="" { qui keep `if' `in' } if `"`detail'"' != `""' { di in ye _newline(2) "****************************************************************" di in ye "Estimation of the ATT with the nearest neighbor matching method " di in ye "Equal weights version " di in ye "****************************************************************" } macro shift local rest `*' /* if pscore is not user-provided, it has to be estimated */ if `"`pscore'"' == `""' { if `"`detail'"' != `""' { di in ye _newline(2) " Estimation of the propensity score" } tempvar PSCORE if `"`detail'"' != `""' { if `"`logit'"' != `""' { logit `rest' [`weight'`exp'], nolog } else { probit `rest' [`weight'`exp'], nolog } } else { if `"`logit'"' != `""' { qui logit `rest' [`weight'`exp'], nolog } else { qui probit `rest' [`weight'`exp'], nolog } } if `"`index'"' != `""' { qui predict double `PSCORE', index } else { qui predict double `PSCORE' } } else { local PSCORE `pscore' } /* REGION OF COMMON SUPPORT */ if `"`comsup'"' != `""' { qui sum `PSCORE' if `T'==1 tempname mintreat maxtreat tempvar COMSUP scalar `mintreat' = r(min) scalar `maxtreat' = r(max) if `"`detail'"' != `""' { di _newline(2) in ye " Note: the common support option has been selected" di in ye " The region of common support is [" `mintreat' ", " `maxtreat' "]" } qui g `COMSUP'=(`PSCORE'>=`mintreat' & `PSCORE'<=`maxtreat') qui drop if `COMSUP'!=1 } if `"`detail'"' != `""' { di in ye _newline(2) " The outcome is `Y'" sum `Y' di in ye _newline(2) " The treatment is `T'" tab `T' di in ye _newline(2) " The distribution of the pscore is" sum `PSCORE', detail } di in ye _newline(2) " The program is searching the nearest neighbor of each treated unit. " di in ye " This operation may take a while." tempvar ycnew sameps weight mfweight mbweight foweight bweight fpsdif bpsdif pscoref pscoreb fdiff bdiff tweight stweight sort `PSCORE' `T' qui egen `ycnew' = mean(`Y') , by(`PSCORE' `T') qui egen `sameps' = count(`Y') , by(`PSCORE' `T') qui gen `weight' = 1/`sameps' /* multiple best matches get lower weight according to number of colleagues */ /* determine if forward or backward controls are best match */ qui g `pscoreb'=`PSCORE' if `T'==0 /* generates missings in all treated records */ qui replace `pscoreb'=`pscoreb'[_n-1] if `pscoreb'==. /* fills pscore of closest controls in treated records */ gsort -`PSCORE' `T' qui g `pscoref'=`PSCORE' if `T'==0 /* generates missings in all treated records */ qui replace `pscoref'=`pscoref'[_n-1] if `pscoref'==. /* fills pscore of closest controls in treated records */ qui g `fdiff'=abs(`pscoref'-`PSCORE') /* fdiff is "property" of the treated records */ qui g `bdiff'=abs(`pscoreb'-`PSCORE') /* bdiff is "property" of the treated records */ gsort `PSCORE' -`T' if `"`detail'"' != `""' { di _newline(1) " **************************************************** " di " Forward search" di " " } local i = 1 local fstop = 0 local fcount = 0 qui gen `fpsdif' = . qui gen `foweight' = 0 /* starting value zero and NOT missing */ /* new idea: construct weights as the mirror image of the rest of the procedure, so while we write info about ATT etc into the treated records, write info about weights into the control records NEW stopping rule: stop if no more weights are being updated */ while `fstop' == 0 { local lastfcount = `fcount' qui count if `fpsdif'==. & `T'==1 local fcount = r(N) if `fcount' == `lastfcount' { local fstop = 1 } else { if r(N) != 0 { /* further if-statement: update weights only if a control is really a best match */ qui replace `foweight' = `foweight' + `weight' if `T'==0 & `T'[_n - `i']==1 & `fpsdif'[_n-`i']==. & `fdiff'[_n-`i']<=`bdiff'[_n-`i'] /* changes entry in first control record but not in controls with equal pscore */ qui replace `fpsdif' = `PSCORE' - `PSCORE'[_n + `i'] if `T'==1 & `T'[_n + `i']==0 & `fpsdif'==. local i = `i' + 1 } else if r(N)== 0 { local fstop = 1 } } } sort `PSCORE' `T' by `PSCORE' `T': egen `mfweight' = max (`foweight') qui replace `foweight'=`mfweight' /* since weights are being built up over all loops but the loop stops once all treated have found their first control, for multiple best matches some control units will not have the right weight yet (because we exit the loop before their weights are correct => write the correct weight into all records of multiple best matches taking the maximum weight which should be the weight of the "first" best match*/ if `"`detail'"' != `""' { di _newline(1) " **************************************************** " di " Backward search" di " " } local i = 1 local bstop = 0 local bcount = 0 qui gen `bpsdif' = . qui gen `bweight' = 0 /* starting value zero and NOT missing */ while `bstop' == 0 { local lastbcount = `bcount' /* di " " di "The number of unmatched bck treated at this round is " di " " */ qui count if `bpsdif'==.&`T'==1 local bcount = r(N) if `bcount' == `lastbcount' { local bstop = 1 } else { if r(N) != 0 { qui replace `bweight' = `bweight' + `weight' if `T'==0 & `T'[_n + `i']==1 & `bpsdif'[_n+`i']==. & `bdiff'[_n+`i']<=`fdiff'[_n+`i'] /* changes entry in first control record but not in controls with equal pscore */ qui replace `bpsdif' = `PSCORE' - `PSCORE'[_n - `i'] if `T'==1 & `T'[_n - `i']==0&`bpsdif'==. local i = `i' + 1 } else if r(N)== 0 { local bstop = 1 } } } /* tab `bweight' tab `bweight' `T' */ qui by `PSCORE' `T': egen `mbweight' = max (`bweight') qui replace `bweight'=`mbweight' /* since weights are being built up over all loops but the loop stops once all treated have found their first control, for multiple best matches some control units will not have the right weight yet (because we exit the loop before their weights are correct => write the correct weight into all records of multiple best matches taking the maximum weight which should be the weight of the "first" best match*/ qui tab `bweight' `T' /* TOTAL WEIGHT */ qui g `tweight'=`foweight'+`bweight' /* foweight==0 if a control is never a forward best match bweight==0 if a control is never a backward best match => only for controls that are both forward and backward best matches tweight is the sum of two positive weights */ /* tab `tweight' */ if `"`detail'"' != `""' { di _newline(2) " **************************************************** " di " Choice between backward or forward match" di " " } capture drop PSDIF qui gen PSDIF = . qui replace PSDIF =abs(`fpsdif') if abs(`bpsdif') > abs(`fpsdif') & `T'==1 qui replace PSDIF =abs(`bpsdif') if abs(`bpsdif') < abs(`fpsdif') & `T'==1 qui replace PSDIF =abs(`bpsdif') if abs(`bpsdif') == abs(`fpsdif') & `T'==1 if `"`detail'"' != `""' { di _newline(2) "**************************************************** " di "Display of final results " di "**************************************************** " di _newline(2) "The number of treated is" count if `T'==1 local nttot = r(N) di _newline(2) "The number of treated which have been matched is " count if PSDIF != . di _newline(2) "Average absolute pscore difference between treated and controls" sum PSDIF if `T'==1 di _newline(2) "Average outcome of the matched treated" sum `Y' if `T'==1 & PSDIF!=. local myt = r(mean) local varyt = r(Var) di " " /* RESCALING OF THE WEIGHT SO THAT IT SUMS UP TO NTTOT */ qui egen `stweight' = sum(`tweight') qui replace `tweight' = `nttot'*(`tweight'/`stweight') di _newline(2) "Average outcome of the matched controls" sum `Y' [aweight=`tweight'] if `T'==0 local myc = r(mean) local nc = r(N) qui sum `Y' if `T'==0 & `tweight'!=0 local varyc = r(Var) tempvar sqweight nweight qui gen `sqweight'=`tweight'^2 if `T'==0 qui sum `sqweight' scalar `nweight' = r(sum) } else { qui count if `T'==1 local nttot = r(N) qui sum `Y' if `T'==1 & PSDIF!=. local myt = r(mean) local varyt = r(Var) /* RESCALING OF THE WEIGHT SO THAT IT SUMS UP TO NTTOT */ qui egen `stweight' = sum(`tweight') qui replace `tweight' = `nttot'*(`tweight'/`stweight') qui sum `Y' [aweight=`tweight'] if `T'==0 local myc = r(mean) local nc = r(N) qui sum `Y' if `T'==0 & `tweight'!=0 local varyc = r(Var) tempvar sqweight nweight qui gen `sqweight'=`tweight'^2 if `T'==0 qui sum `sqweight' scalar `nweight' = r(sum) } di " " return scalar attnw = `myt'-`myc' return scalar seattnw = sqrt( `varyt'/`nttot' + (`nweight'/(`nttot'^2))*`varyc' ) return scalar tsattnw = return(attnw) / return(seattnw) return scalar ntnw = `nttot' return scalar ncnw = `nc' di _newline(3) _column(1) in ye "ATT estimation with Nearest Neighbor Matching method" di in ye _column(1) "(equal weights version)" di _column(1) in ye "Analytical standard errors" di in gr _newline(1) in text "{hline 57}" di _column(1) in gr "n. treat." /* */ _column(13) in gr "n. contr." /* */ _column(25) in gr " ATT" /* */ _column(37) in gr "Std. Err." /* */ _column(49) in gr " t" di in gr in text "{hline 57}" di _newline(1) _column(1) in ye %9.0f return(ntnw) /* */ _column(13) in ye %9.0f return(ncnw) /* */ _column(25) in ye %9.3f return(attnw) /* */ _column(37) in ye %9.3f return(seattnw) /* */ _column(49) in ye %9.3f return(tsattnw) di in gr _newline(1) in text "{hline 57}" di in gr "Note: the numbers of treated and controls refer to actual" di in gr "nearest neighbour matches" restore if `"`bootstrap'"' != `""' { di _newline(5) in gr "Bootstrapping of standard errors " bs "attnw `varlist' `if' `in' `fweight' `iweight' `pweight', pscore(`pscore') `logit' `index' `comsup' " "r(attnw)", nowarn reps(`reps') `noisily' `dots' } if `"`bootstrap'"' != `""' { return scalar attnw = r(stat) return scalar bseattnw = r(se) return scalar btsattnw = return(attnw) / return(bseattnw) di _newline(3) _column(1) in ye "ATT estimation with Nearest Neighbor Matching method" di _column(1) "(equal weights version)" di _column(1) in ye "Bootstrapped standard errors" di in gr _newline(1) in text "{hline 57}" di _column(1) in gr "n. treat." /* */ _column(13) in gr "n. contr." /* */ _column(25) in gr " ATT" /* */ _column(37) in gr "Std. Err." /* */ _column(49) in gr " t" di in gr in text "{hline 57}" di _newline(1) _column(1) in ye %9.0f return(ntnw) /* */ _column(13) in ye %9.0f return(ncnw) /* */ _column(25) in ye %9.3f return(attnw) /* */ _column(37) in ye %9.3f return(bseattnw) /* */ _column(49) in ye %9.3f return(btsattnw) di in gr _newline(1) in text "{hline 57}" di in gr "Note: the numbers of treated and controls refer to actual" di in gr "nearest neighbour matches" if `"`detail'"' != `""' { di _newline(3) " Saving results in r()" } qui attnw `varlist' `if' `in' `fweight' `iweight' `pweight', pscore(`pscore') `logit' `index' `comsup' } if `"`detail'"' != `""' { di _newline (3) "******************************************************************************* " di "End of the estimation with the nearest neighbor matching (equal weights) method " di "******************************************************************************* " } end