version 11.2 cap program drop _eventiv program define _eventiv, rclass #d; syntax varlist(fv ts numeric) [aw fw pw] [if] [in], /* Covariates go in varlist. Can add fv ts later */ Panelvar(varname) /* Panel variable */ Timevar(varname) /* Time variable */ POLicyvar(varname) /* Policy variable */ LWindow(integer) /* Estimation window. Need to set a default, but it has to be based on the dataset */ RWindow(integer) proxy (varlist numeric) /* Proxy variable(s) */ [ proxyiv(string) /* Instruments. Either numlist with lags or varlist with names of instrumental variables */ nofe /* No fixed effects */ note /* No time effects */ SAVek(string) /* Generate the time-to-event dummies, trend and keep them in the dataset */ nogen /* Do not generate k variables */ kvars(string) /* Stub for event dummies to include, if they have been generated already */ norm(integer -1) /* Normalization */ reghdfe /* Use reghdfe for estimation */ impute(string) /*imputation on policyvar*/ *static /* in this ado used for calling the part of _eventgenvars that imputes*/ addabsorb(string) /* Absorb additional variables in reghdfe */ REPeatedcs /*indicate that the input data is a repeated cross-sectional dataset*/ * ] ; #d cr marksample touse tempname delta Vdelta bb VV bb2 VV2 delta2 Vdelta2 deltaov Vdeltaov deltax Vdeltax deltaxsc bby bbx VVy VVx tousegen * bb delta coefficients * VV variance of delta coefficients * bb2 delta coefficients for overlay plot * VV2 variance of delta coefficients for overlay plot * delta2 included cefficientes in overlaty plot * VVdelta2 variance of included delta coefficients in overlay plot * For eventgenvars, ignore missings in varlist mark `tousegen' `if' `in' loc i = "`panelvar'" loc t = "`timevar'" loc z = "`policyvar'" *parse savek if "`savek'"!="" parsesavek `savek' loc savek = r(savekl) if "`savek'"=="." loc savek "" return loc savek = "`savek'" loc noestimate = r(noestimatel) if "`noestimate'"=="." loc noestimate "" return loc noestimate = "`noestimate'" *If imputation is specified, _eventiv will call _eventgenvars twice. *The first call only imputes the policyvar, but the second call imputes both the policyvar and the event-time dummies *First call: bring the imputed policyvar calling only _eventgenvars' imputation code. This call is neccesary to choose the lead order using the imputed policyvar if "`impute'"!=""{ *rr is the tempvar to be imputed: create it in _eventiv, so after _eventgenvars we can still have access to it. tempvar rr qui gen double `rr'=. *call _eventgenvars _eventgenvars if `tousegen', panelvar(`panelvar') timevar(`timevar') policyvar(`policyvar') impute(`impute') static rr(`rr') `repeatedcs' //with option static, we skip the code that generates the event-time dummies loc impute=r(impute) if "`impute'"=="." loc impute = "" *if imputation succeeded, use the values brought by rr if "`impute'"!="" { tempvar zimp qui gen double `zimp'=`rr' loc z="`zimp'" } *otherwise, keep using the original policyvar else loc z = "`policyvar'" } * if dataset is repeated cross-sectional, create leads of policyvar at state level if "`repeatedcs'"!=""{ qui { preserve tempfile state_level_leads keep if `touse' keep `panelvar' `timevar' (`z') bysort `panelvar' `timevar' (`z'): keep if _n==1 xtset `panelvar' `timevar' forv v=1(1)`=-`lwindow''{ tempvar _fd`v'`z' qui gen double `_fd`v'`z'' = f`v'.d.`z' } save `state_level_leads' restore *merge on the policyvar as well, so missing values in policyvar within a cell will not get lead values merge m:1 `panelvar' `timevar' `z' using `state_level_leads', update nogen } } loc leads : word count `proxy' if "`proxyiv'"=="" & `leads'==1 loc proxyiv "select" * If proxy specified but no proxyiv, assume numlist for leads of policyvar if "`proxyiv'"=="" { di as text _n "No proxy instruments specified. Using leads of differenced policy variables as instruments." loc leads : word count `proxy' forv j=1(1)`leads' { loc proxyiv "`proxyiv' `j'" } } * IV selection if proxyiv = selection else if "`proxyiv'"=="select" { * Only for one proxy case if `leads'>1 { di as err "Proxy instrument selection only available for the one proxy - one instrument case" exit 301 } else { di as text _n "proxyiv=select. Selecting lead order of differenced policy variable to use as instrument." loc Fstart = 0 forv v=1(1)`=-`lwindow'' { if "`repeatedcs'"=="" { tempvar _fd`v'`z' qui gen double `_fd`v'`z'' = f`v'.d.`z' if `touse' } qui reg `proxy' `_fd`v'`z'' if `touse' loc Floop = e(F) if `Floop' > `Fstart' { loc Fstart = `Floop' loc proxyiv "`v'" } } di as text _n "Lead `proxyiv' selected." } } * Parse proxyiv and generate leads if neccesary loc rc=0 loc ivwords = 0 foreach v in `proxyiv' { cap confirm integer number `v' if _rc loc ++rc loc ++ivwords } * Three possible types of lists: all numbers for leads, all vars for external instruments, or mixed * All numbers if `rc' == 0 { loc leadivs "" foreach v in `proxyiv' { if "`repeatedcs'"!=""{ qui gen double _fd`v'`z' = `_fd`v'`z'' if `touse' } else{ qui gen double _fd`v'`z' = f`v'.d.`z' if `touse' } loc leadivs "`leadivs' _fd`v'`z'" } loc instype = "numlist" loc varivs = "" } * All words else if `rc'==`ivwords' { foreach v in `proxyiv' { confirm numeric variable `v' } loc instype = "varlist" loc leadivs = "" loc varivs = "`proxyiv'" } * Mixed else { loc leadivs "" loc varivs "" foreach v in `proxyiv' { cap confirm integer number `v' if _rc loc varivs "`varivs' `v'" else { if "`repeatedcs'"!=""{ qui gen double _fd`v'`z' = `_fd`v'`z'' if `touse' } else{ qui gen double _fd`v'`z' = f`v'.d.`z' if `touse' } loc leadivs "`leadivs' _fd`v'`z'" } } loc instype "mixed" } * Count normalizations and set omitted coefs for plot accordingly * Need one more normalization per IV loc komit "" loc norm0 "`norm'" *split proxyiv into two lists: only numbers or only varnames loc proxyiv_numbers "" loc proxyiv_vrnames "" foreach v in `proxyiv' { cap confirm number `v' if !_rc loc proxyiv_numbers "`proxyiv_numbers' `v'" else loc proxyiv_vrnames "`proxyiv_vrnames' `v'" } foreach v in `proxyiv_numbers' { cap confirm integer number `v' if _rc { di as err "Lead of policy variable to be used as instrument must be an integer." exit 301 } } * Set normalizations in case these are numbers, so we are using leads of delta z loc ivnorm "" if "`instype'"=="numlist" | "`instype'"=="mixed" { foreach v in `proxyiv_numbers' { if `=-`v''==`norm' { loc ivnorm "`ivnorm' `=-`v'-1'" di as txt _n "The corresponding coefficient of lead `v' and the normalized coefficient were the same. Lead `=`v'' has been changed to `=`v'+1'." loc repeatlead=strmatch("`proxyiv_numbers'","*`=`v'+1'*") if "`repeatlead'"=="0"{ di as txt _n "The coefficient at `=-`v'-1' was selected to be normalized to zero." } } else { loc ivnorm "`ivnorm' -`v'" di as txt _n "The coefficient at `=-`v'' was selected to be normalized to zero." } } } *set normalizations for external instruments *get the pool of available coefficients for normalization loc available "" forvalues l=1/`=-`lwindow'+1'{ loc l = -`l' loc available "`available' `l'" } loc available: list available - norm foreach var of loc proxyiv_vrnames{ loc available: list available - ivnorm loc lenav: word count(`available') if `lenav'==1 { di as err "Number of instruments specified in {bf:proxyiv} reached the maximum imposed by the number of pre-event periods." exit 301 } *normalize one extra coefficient per external instrument loc avcomma : subinstr loc available " " ",", all loc avmax = max(`avcomma') //choose the coefficient closest to zero loc ivnorm "`ivnorm' `avmax'" // add it to ivnorm di as text _n "The coefficient at `avmax' was selected to be normalized to zero" } * Normalize one more lag if normalization = number of proxys if "`instype'"=="numlist" | "`instype'"=="mixed" { loc np: word count `proxy' * loc npiv: word count `norm' `ivnorm' loc npiv : list norm | ivnorm loc npiv : list uniq npiv loc npiv : word count `npiv' if `np'==`npiv' { loc ivnormcomma : subinstr local ivnorm " " ",", all loc ivmin = min(`ivnormcomma') loc ivnorm "`ivnorm' `=`ivmin'-1'" } } * No need to normalize for external instruments. If the user generates a lead of z and uses it as a variable, the instrument is collinear. foreach j in `norm' `ivnorm' { loc norm "`norm' `j' " loc komit "`komit' `j'" } loc komit: list uniq komit if "`gen'" != "nogen" { *If impute was specified, this is the second call to _eventgenvars: this time, both the policyvar and the event-time dummies will be imputed. Additional computations will happen as well (e.g., macros, etc.). _eventgenvars if `tousegen', panelvar(`panelvar') timevar(`timevar') policyvar(`policyvar') lwindow(`lwindow') rwindow(`rwindow') `trend' norm(`norm') impute(`impute') `repeatedcs' loc included=r(included) loc names=r(names) loc komittrend=r(komittrend) if "`komittrend'"=="." loc komittrend = "" } else { loc kvstub "`kvars'" loc j=1 loc names "" loc included "" foreach var of varlist `kvstub'* { if `norm' < 0 loc kvomit = "m`=abs(`norm')'" else loc kvomit "p`=abs(`norm')'" if "`var'"=="`kvstub'_evtime" | "`var'" == "`kvstub'_eq_`kvomit'" continue if "`kvstub'"!="_k" { loc sub : subinstr local var "`kvstub'" "_k", all clonevar `sub' = `var' } else { loc sub = "`var'" } if `j'==1 loc names `""`sub'""' else loc names `"`names'.."`sub'""' * " loc included "`included' `sub'" loc ++ j } loc komittrend=r(komittrend) if "`komittrend'"=="." loc komittrend = "" } *" loc komit "`norm' `komittrend'" loc komit = strtrim(stritrim("`komit'")) loc komit: list uniq komit * Check that the iv normalization works foreach v in `leadivs' `varivs' { cap _rmdcoll `v' `included' if `touse' if _rc { di as err "Instrument {bf:`v'} is collinear with the included event-time dummies. You may have generated leads of the policy variable and included them in the proxyiv option instead of specifying the lead numbers." exit 301 } } if "`te'" == "note" loc tte "" else loc tte "i.`t'" **** Main regression ** In the repeated cross section case with fixed effects, cannot use xtivreg, so default to reghdfe if "`repeatedcs'"!="" & "`fe'"!="nofe" { loc reghdfe = "reghdfe" di as text _n "Using {cmd:reghdfe} for fixed effects estimation with repeated cross-sectional data." } if "`noestimate'"==""{ if "`reghdfe'"=="" { if "`fe'" == "nofe" { loc cmd "ivregress 2sls" loc ffe "" loc small "small" } else { loc cmd "xtivreg" loc ffe "fe" } *translate standard error specification: *analyze inclusion of cluster or robust in options parse_es ,`options' foreach orig in cl_orig rob_orig vce_orig other_opts{ loc `orig' = r(`orig') if "``orig''"=="." loc `orig' "" } *if xtivreg, warn the user about robust estandar errors equivalent to vce(cluster panelvar) if "`cmd'"=="xtivreg" & (("`vce_orig'"=="robust" | "`vce_orig'"=="r") | "`rob_orig'"!=""){ di as text _n "You asked for robust standard errors and the underlying estimation command is {cmd:xtivreg}. Standard errors will be clustered by panelvar. See {help xtivreg##options_fe:xtivreg}." } *if it doesn't contain cluster and robust: if "`cl_orig'"=="" & "`rob_orig'"=="" { `cmd' `varlist' (`proxy' = `leadivs' `varivs') `included' `tte' [`weight'`exp'] if `touse' , `ffe' `small' `options' } *if it contains either cluster or robust: else{ *if the user already specified vce, then we cannot specify a second vce if "`vce_orig'"!="" { *execute as defined by the user and expect some error `cmd' `varlist' (`proxy' = `leadivs' `varivs') `included' `tte' [`weight'`exp'] if `touse' , `ffe' `small' `options' } else{ loc vce_opt "" *parse cluster if "`cl_orig'"!=""{ loc vce_opt = "vce(cluster `cl_orig')" //if robust is also specified, no need to add it } else { loc vce_opt = "vce(robust)" } `cmd' `varlist' (`proxy' = `leadivs' `varivs') `included' `tte' [`weight'`exp'] if `touse' , `ffe' `small' `vce_opt' `other_opts' } } } else { loc noabsorb "" *absorb nothing if "`fe'" == "nofe" & "`tte'"=="" & "`addabsorb'"=="" { *loc noabsorb "noabsorb" /*the only option ivreghdfe inherits from reghdfe is absorb, therefore it doesn't support noabsorb. In contrast with reghdfe, ivreghdfe doesn't require noabsorb when absorb is not specified*/ loc abs "" } *absorb only one else if "`fe'" == "nofe" & "`tte'"=="" & "`addabsorb'"!="" { loc abs "absorb(`addabsorb')" } else if "`fe'" == "nofe" & "`tte'"!="" & "`addabsorb'"=="" { loc abs "absorb(`t')" } else if "`fe'" != "nofe" & "`tte'"=="" & "`addabsorb'"=="" { loc abs "absorb(`i')" } *absorb two else if "`fe'" == "nofe" & "`tte'"!="" & "`addabsorb'"!="" { loc abs "absorb(`t' `addabsorb')" } else if "`fe'" != "nofe" & "`tte'"=="" & "`addabsorb'"!="" { loc abs "absorb(`i' `addabsorb')" } else if "`fe'" != "nofe" & "`tte'"!="" & "`addabsorb'"=="" { loc abs "absorb(`i' `t')" } *absorb three else if "`fe'" != "nofe" & "`tte'"!="" & "`addabsorb'"!="" { loc abs "absorb(`i' `t' `addabsorb')" } * else { loc abs "absorb(`i' `t' `addabsorb')" } *analyze inclusion of vce in options loc vce_y= strmatch("`options'","*vce(*)*") *if user did not specify vce option if "`vce_y'"=="0" { ivreghdfe `varlist' (`proxy' = `leadivs' `varivs') `included' [`weight'`exp'] if `touse', `abs' `noabsorb' `options' } *if user did specify vce option else { *find start and end of vce text loc vces=strpos("`options'","vce(") loc vcef=0 loc ocopy="`options'" while `vcef'<`vces' { loc vcef=strpos("`ocopy'", ")") loc ocopy=subinstr("`ocopy'",")", " ",1) } *substrac vce words loc svce_or=substr("`options'",`vces',`vcef') loc vce_len=strlen("`svce_or'") loc svce=substr("`svce_or'",5,`vce_len'-5) loc svce=strltrim("`svce'") loc svce=strrtrim("`svce'") *inspect whether vce contains bootstrap or jackknife loc vce_bt= strmatch("`svce'","*boot*") loc vce_jk= strmatch("`svce'","*jack*") if `vce_bt'==1 | `vce_jk'==1 { di as err "Options {bf:bootstrap} and {bf:jackknife} are not allowed" exit 301 } *if vce contains valid options, parse those options *erase vce from original options loc options_wcve=subinstr("`options'","`svce_or'"," ",1) *** parse vce(*) **** loc vce_wc=wordcount("`svce'") tokenize `svce' *extract vce arguments *robust loc vce_r= strmatch("`svce'","*robust*") loc vce_r2=0 forv i=1/`vce_wc'{ loc zz= strmatch("``i''","r") loc vce_r2=`vce_r2'+`zz' } if `vce_r'==1 | `vce_r2'==1 { loc vceop_r="robust" } *cluster loc vce_c= strmatch("`svce'","*cluster*") loc vce_c2= strmatch("`svce'","*cl*") if `vce_c'==1 | `vce_c2'==1 { forv i=1/`vce_wc'{ loc vce_r2= strmatch("``i''","*cl*") if `vce_r2'==1 { loc j=`i'+1 } } loc vceop_c="cluster(``j'')" } ivreghdfe `varlist' (`proxy' = `leadivs' `varivs') `included' [`weight'`exp'] if `touse', `abs' `noabsorb' `options_wcve' `vceop_r' `vceop_c' } } *clear xtset if repeatedcs and xtivreg, otherwise error message because timevar not setted if ("`repeatedcs'"!="" & "`cmd'"=="xtivreg") qui xtset, clear * Return coefficients and variance matrix of the delta k estimates separately mat `bb'=e(b) mat `VV'=e(V) mat `delta' = `bb'[1,`names'] mat `Vdelta' = `VV'[`names',`names'] if "`reghdfe'"=="" { if "`fe'" == "nofe" { loc df=e(df_r) } else { loc df=e(df_rz) } } else { loc df=e(df_r) if `df'==. loc df=e(Fdf2) } loc kmax=`=`rwindow'+1' loc kmin=`=`lwindow'-1' tempvar esample gen byte `esample' = e(sample) * Plots * Calculate mean before change in policy for 2nd axis in plot * This needs to be relative to normalization tempvar temp_k loc absnorm=abs(`norm0') qui gen `temp_k'=_k_eq_m`absnorm' tokenize `varlist' qui su `1' if `temp_k'!=0 & `temp_k'!=. & `esample', meanonly loc y1 = r(mean) loc depvar "`1'" * Calculate mean proxy before change in policy for 2nd axis in plot if "`proxy'"!="" { loc nproxy: word count `proxy' if `nproxy' ==1 { qui su `proxy' if `temp_k'!=0 & `temp_k'!=. & `esample', meanonly loc x1 = r(mean) } else loc x1 = . } * Variables for overlay plots * Need the ols estimates for y and x * Do not exclude vars other than m1 *loc toexc = "_k_eq_m1" *unab included2: _k_eq_* *loc included2 : list included2 - toexc _estimates hold main qui _eventols `varlist' [`weight'`exp'] if `touse' , panelvar(`panelvar') timevar(`timevar') policyvar(`policyvar') lwindow(`lwindow') rwindow(`rwindow') `fe' `te' nogen nodrop kvars(_k) norm(`norm0') impute(`impute') mat `deltaov' = r(delta) mat `Vdeltaov' = r(Vdelta) *mat `deltay' = `bby'[1,${names}] *mat `Vdeltay' = `VVy'[${names},${names}] qui _eventols `proxy' [`weight'`exp'] if `touse', panelvar(`panelvar') timevar(`timevar') policyvar(`policyvar') lwindow(`lwindow') rwindow(`rwindow') `fe' `te' nogen nodrop kvars(_k) norm(`norm0') impute(`impute') mat `deltax' = r(delta) mat `Vdeltax' = r(Vdelta) *mat `deltax' = `bb'[1,${names}] * mat `Vdeltax' = `VV'[${names},${names}] * Scaling factor loc ivnormcomma = strtrim("`ivnorm'") loc ivnorms : list sizeof ivnormcomma loc ivnormcomma : subinstr local ivnormcomma " " ",", all if `ivnorms'>1 loc scfactlead = -max(`ivnormcomma') else loc scfactlead = -`ivnormcomma' mat Mfn = `deltaov'[1,"_k_eq_m`scfactlead'"] mat Mfd = `deltax'[1,"_k_eq_m`scfactlead'"] loc fn = Mfn[1,1] loc fd = Mfd[1,1] loc factor = `fn'/`fd' * Scale x estimates by factor mat `deltaxsc' = `factor'*`deltax' } * Drop variables if "`savek'" == "" { cap confirm var _k_eq_p0 if !_rc drop _k_eq* cap confirm var __k if !_rc qui drop __k if "`trend'"!="" qui drop _ttrend } else { ren __k `savek'_evtime ren _k_eq* `savek'_eq* if "`trend'"!="" ren _ttrend `savek'_trend } if "`instype'"=="numlist" | "`instype'"=="mixed" { foreach v in `leadivs' { drop `v' } } *skip the rest of the program if the user indicated not to estimate if "`noestimate'"!="" exit * Returns _estimates unhold main return matrix b = `bb' return matrix V = `VV' return matrix delta = `delta' return matrix Vdelta = `Vdelta' return matrix deltaov = `deltaov' return matrix Vdeltaov = `Vdeltaov' return matrix deltax = `deltax' return matrix Vdeltax = `Vdeltax' return matrix deltaxsc = `deltaxsc' loc names: subinstr local names ".." " ", all loc names: subinstr local names `"""' "", all return local names = `"`names'"' * " return local cmd = "`cmd'" return local df = `df' return local komit = "`komit'" return local kmiss = "`kmiss'" return local y1 = `y1' return local depvar = "`depvar'" if `x1'!=. return local x1 = `x1' return local method = "iv" end *program to parse savek program define parsesavek, rclass syntax [anything] , [NOEstimate] return local savekl "`anything'" return local noestimatel "`noestimate'" end *program to parse standar error specification program define parse_es, rclass #d; syntax [anything], [ CLuster(varname) Robust vce(string) * ] ; #d cr return local cl_orig "`cluster'" return local rob_orig "`robust'" return local vce_orig "`vce'" return local other_opts "`options'" end