*! ivmediate 1.0.4 1 Dec 2020 *! authors Christian Dippel, Andreas Ferrara, Stephan Heblich cap program drop ivmediate program define ivmediate, eclass version 11.0 * Program Syntax syntax varlist(numeric ts fv min=1 numeric) [if] [in], MEDiator( varname min=1 max=1 numeric) /// TREATment( varname min=1 max=1 numeric) /// INSTrument(varlist min=1 max=1 numeric) /// [Absorb( varname min=1 max=1 numeric)] /// [vce(string)] /// [Full] /// [Level(integer 95)] marksample touse * mark the outcome variable gettoken depvar indepvars : varlist _fv_check_depvar `depvar' * mark the controls (if any) fvexpand `indepvars' * mark the instrument(s) fvexpand `instrument' * mark estimation sample markout `touse' // check that instrument(s) is/are not also in other lists foreach vlist in depvar indepvars treatment mediator { local checklist : list instrument & `vlist' local checknum : word count `checklist' if `checknum' { di as err "syntax error - cannot also use instrument `checklist' in `vlist'" exit 198 } } // check that mediator is not also in other lists foreach vlist in depvar indepvars treatment { local checklist : list mediator & `vlist' local checknum : word count `checklist' if `checknum' { di as err "syntax error - cannot also use mediator `checklist' in `vlist'" exit 198 } } // check that treatment variable is not also in other lists foreach vlist in depvar indepvars { local checklist : list treatment & `vlist' local checknum : word count `checklist' if `checknum' { di as err "syntax error - cannot also use treatment `checklist' in `vlist'" exit 198 } } // check that level is specified correctly if `level'<10 | `level'>99 { di as err "level() must be between 10 and 99 inclusive" exit 198 } * VCE options if ("`vce'"!="") { tempvar `vce' } else { local vcetype "unadjusted" } _vce_parse `touse' , optlist(Robust) argoptlist(CLuster) : , vce(`vce') local vce "`r(vce)'" local clustervar "`r(cluster)'" if "`vce'" == "robust" { tempvar clustervar gen `clustervar' = _n local vcetype "cl `clustervar'" } else if "`vce'" == "cluster" { local vcetype "cl `clustervar'" } if "`clustervar'" != "" { local fname "Kleibergen-Paap " capture confirm numeric variable `clustervar' if _rc { display in red "invalid vce() option" display in red "cluster variable {bf:`clustervar'} is " /// "string variable instead of a numeric variable" exit(198) } sort `clustervar' } *-----------------------------------------------------------------------------------------------------* * Apply FLW Theorem to partial out covariates * generate temp vars for each instrument forval z = 1(1)`: word count `instrument'' { tempvar zres`z' tempvar zres`z'YM local zreslist `zreslist' `zres`z'' local zreslistYM `zreslistYM' `zres`z'YM' } * generate temp vars for outcome, treatment, mediator tempvar yres tres mres yresYM mresYM local outc `depvar' `treatment' `instrument' `mediator' local tvar `yres' `tres' `zreslist' `mres' local tvarYM `yresYM' `tres' `zreslistYM' `mresYM' * if no absorb var specified, set a = 1 tempvar c if "`absorb'" != "" { gen `c' = `absorb' } else { gen `c' = 1 } *-----------------------------------------------------------------------------------------------------* *-----------------------------------------------------------------------------------------------------* * partial out controls and absorb var * if no absorb var and controls are specified, partial out the intercept only tempvar treatres qui areg `treatment' `indepvars' if `touse', a(`c') qui predict double `treatres', res * param numbers for first stage F stat computation later local partialdof = e(N)-e(df_r) local n : word count `outc' qui forval i = 1(1)`n' { local y `: word `i' of `outc'' local r `: word `i' of `tvar'' * partial out controls and FE from i) outcome, ii) mediator, iii) instruments * also partial out T from i), ii), iii) separately for later use (marked YM) * this will be used in the TSLS reg of Y on M using Z as inst and controlling for T if `i'!=2 { areg `y' `indepvars' `treatres' if `touse', a(`c') local rYM `: word `i' of `tvarYM'' predict double `rYM', res scalar btreat = _b[`treatres'] * now add beta*Treat back in to get the residuals for the reg "areg `y' `indepvars' if `touse', a(`c')" * this speeds up the ado file because it avoids additional regressions gen `r' = `rYM' + btreat*`treatres' } * partial out controls and FE from the treatment variable else { areg `y' `indepvars' if `touse', a(`c') predict double `r', res } } *-----------------------------------------------------------------------------------------------------* *-----------------------------------------------------------------------------------------------------* * Run GMM command to estimate mediation effects qui gmm (`yres' - {xb1: `tres'}) /// (`mres' - {xb2: `tres'}) /// (`yres' - {xb3: `tres' `mres'}) if `touse', /// instruments(1: `zreslist' ) /// instruments(2: `zreslist' ) /// instruments(3: `zreslist' `tres') /// winit(unadjusted, independent) /// vce(`vcetype', independent) /// onestep /// deriv(1/xb1 = -1) /// deriv(2/xb2 = -1) /// deriv(3/xb3 = -1) local N = e(N) local N_clust = e(N_clust) *-----------------------------------------------------------------------------------------------------* *-----------------------------------------------------------------------------------------------------* * generate variable lengths for table alignments local lenD = length("`depvar'") local lenT = length("`treatment'") local lenM = length("`mediator'") local len = max(`lenD', `lenT', `lenM') if `len'>=15 { local addY = abs(`lenD'-`len') local deplen = "`depvar'"+`addY'*" " local addT = abs(`lenT'-`len') local treatlen = "`treatment'"+`addT'*" " local addM = abs(`lenM'-`len') local medlen = "`mediator'"+`addM'*" " local addTE = abs(length("total effect")-`len') local TElen = "total effect"+`addTE'*" " local addDE = abs(length("direct effect")-`len') local DElen = "direct effect"+`addDE'*" " local addIE = abs(length("indirect effect")-`len') local IElen = "indirect effect"+`addIE'*" " } else { local addY = abs(`lenD'-15) local deplen = "`depvar'"+`addY'*" " local addT = abs(`lenT'-15) local treatlen = "`treatment'"+`addT'*" " local addM = abs(`lenM'-15) local medlen = "`mediator'"+`addM'*" " local TElen = "total effect"+" " local DElen = "direct effect"+" " local IElen = "indirect effect" } *-----------------------------------------------------------------------------------------------------* *-----------------------------------------------------------------------------------------------------* * If full set of results is requested if "`full'"!="" { * results from y on T inst by Z mat eB = e(b) mat eV = e(V) mat b1 = eB[1,1] mat colnames b1 = "`treatlen'" mat v1 = eV[1,1] mat rownames v1 = "`treatlen'" mat colnames v1 = "`treatlen'" local depvar1 "`deplen'" * results from M on T inst by Z mat b2 = eB[1,2] mat colnames b2 = "`treatlen'" mat v2 = eV[2,2] mat rownames v2 = "`treatlen'" mat colnames v2 = "`treatlen'" local depvar2 "`medlen'" * results from y on M (inst by Z) controlling for T mat b3 = J(1,2,.) mat b3[1,1] = eB[1,3] mat b3[1,2] = eB[1,4] mat colnames b3 = "`treatlen'" "`medlen'" mat v3 = J(2,2,.) mat v3[1,1] = eV[3,3] mat v3[2,1] = eV[4,3] mat v3[1,2] = eV[4,3] mat v3[2,2] = eV[4,4] mat rownames v3 = "`treatlen'" "`medlen'" mat colnames v3 = "`treatlen'" "`medlen'" local depvar3 "`deplen'" } *-----------------------------------------------------------------------------------------------------* *-----------------------------------------------------------------------------------------------------* * Compute results for main table // mediation effect as % of total effect scalar ME = _b[xb2_`tres':_cons]*_b[xb3_`mres':_cons]/_b[xb1_`tres':_cons]*100 *-----------------------------------------------------------------------------------------------------* *-----------------------------------------------------------------------------------------------------* * compute the two first stage F-statistics (inst endog with Z f-stat, and inst mediator with Z f-stat) * number of controls and FE local iv1_ct : word count `indepvars' local iv1_ct = `iv1_ct' * number of excl instruments local exex1_ct : word count `zreslist' // First stage F-stat T, inst with Z * rank test to compute F stats qui ranktest (`tres') (`zreslist') if `touse', full wald /// cluster(`clustervar') local chi2_1 = r(chi2) // First stage F-stat, M inst with Z contr for T qui ranktest (`mres') (`zreslist') if `touse', full wald /// cluster(`clustervar') /// partial(`tres') local chi2_2 = r(chi2) * without clustering or robust s.e. if "`clustervar'" == "" { scalar fstat1 = `chi2_1'/`N'*(`N'-`partialdof'-`exex1_ct')/`exex1_ct' scalar fstat2 = `chi2_2'/`N'*(`N'-`partialdof'-`exex1_ct'-1)/`exex1_ct' } * if cluster or robust, compute Kleibergen-Paap F-stat if "`clustervar'" != "" { scalar fstat1 = `chi2_1'/(`N'-1) * (`N'-`partialdof'-`exex1_ct') * (`N_clust'-1)/`N_clust' / `exex1_ct' scalar fstat2 = `chi2_2'/(`N'-1) * (`N'-`partialdof'-`exex1_ct'-1) * (`N_clust'-1)/`N_clust' / `exex1_ct' } *-----------------------------------------------------------------------------------------------------* *-----------------------------------------------------------------------------------------------------* * Obtain total, direct, and indirect effects qui nlcom (_b[xb1_`tres':_cons]) /// (_b[xb3_`tres':_cons]) /// (_b[xb2_`tres':_cons]*_b[xb3_`mres':_cons]), post mat b = e(b) mat V = e(V) mat colnames b = "`TElen'" "`DElen'" "`IElen'" mat colnames V = "`TElen'" "`DElen'" "`IElen'" mat rownames V = "`TElen'" "`DElen'" "`IElen'" *-----------------------------------------------------------------------------------------------------* *-----------------------------------------------------------------------------------------------------* * display full set of results of requested if "`full'"!="" { di in gr "Intermediate Output" di in smcl in gr "{hline 28}" di in gr "Effect of the treatment on the outcome" di in gr "IV regression of " in ye "`depvar'" in gr " on " in ye "`treatment'" in gr " (instrumented with " in ye "`instrument'" in gr ")" ereturn post b1 v1 ereturn local depvar "`depvar1'" ereturn display, level(`level') di "" di in gr "Effect of the treatment on the mediator" di in gr "IV regression of " in ye "`mediator'" in gr " on " in ye "`treatment'" in gr " (instrumented with " in ye "`instrument'" in gr ")" ereturn post b2 v2 ereturn local depvar "`depvar2'" ereturn display, level(`level') di "" di in gr "Effect of the mediator on the outcome, controlling for the treatment" di in gr "IV regression of " in ye "`depvar'" in gr " on " in ye "`mediator'" in gr " (instrumented with " in ye "`instrument'" in gr ") controlling for " in ye "`treatment'" ereturn post b3 v3 ereturn local depvar "`depvar3'" ereturn display, level(`level') di in gr "Note: All other exogenous controls are partialled out." if "`vce'" == "cluster" { di in gr " Standard errors clustered on `clustervar'" } if "`vce'" == "robust" { di in gr " Standard errors robust to heteroscedasticity." } di "" } *-----------------------------------------------------------------------------------------------------* *-----------------------------------------------------------------------------------------------------* * report main results mat rownames V = _: mat colnames V = _: * right-align number of observations if `len'>=15 { local hl = `len'-15+81 local obsl = `len'-15+66-length("`N'") local dis = `len'-15+58-length("`clustervar'")-length("`N_clust'") } else { local hl = 81 local obsl = 66-length("`N'") local dis = 58-length("`clustervar'")-length("`N_clust'") } di in gr "Linear IV Mediation Analysis" di in smcl in gr "{hline 28}" di in gr "Outcome:" in ye _col(12) "`depvar'" _col(`obsl') in gr "Number of obs = " in ye `N' if "`vce'" == "cluster" { di in gr "Treatment:" in ye _col(12) "`treatment'" in gr _col(`dis') "Number of clusters (" "`clustervar'" ") = " in ye `N_clust' } else { di in gr "Treatment:" in ye _col(12) "`treatment'" } di in gr "Mediator:" in ye _col(12) "`mediator'" ereturn post b V, obs(`N') ereturn scalar fstat1 = fstat1 ereturn scalar fstat2 = fstat2 ereturn scalar mepct = ME if "`vce'" == "cluster" { ereturn scalar N_clust = `N_clust' ereturn local clustvar "`clustervar'" } ereturn local vcetype "`vce'" ereturn local inst "`instrument'" ereturn local med "`mediator'" ereturn local treat "`treatment'" ereturn local depvar "`deplen'" ereturn display, level(`level') di in gr "Mediator " in ye "`mediator'" in gr " explains " in ye %3.2f e(mepct) "%" in gr " of the total effect." di in gr "`fname'F-statistic for excluded instruments in" di in gr "- first stage one (T on Z): " _col(31) in ye %6.3f fstat1 di in gr "- first stage two (M on Z|T): " _col(31) in ye %6.3f fstat2 di in gr "Excluded instruments: " in ye "`instrument'" if "`vce'" == "cluster" { di in gr "Standard errors clustered on `clustervar'" } if "`vce'" == "robust" { di in gr "Standard errors robust to heteroscedasticity." } di in smcl in gr "{hline `hl'}" ereturn local depvar "`depvar'" *-----------------------------------------------------------------------------------------------------* end /* Update Log: 1.0.1 finalized first version of ado file (4 April 2019) 1.0.2 changed displayed output quantities (11 May 2019) - added direct effect - removed mediation effect as % of total effect - only display the % med effect of total as value above first stage F stats 1.0.3 added FULL option to display full GMM output (30 Oct 2019) improved display formatting of results restricted use to only a single instrument 1.0.4 changed how matrix elements are called under the "full" option to make syntax compatible with versions prior to Stata 16