*! Date : 3 Sep 2007 *! Version : 1.72 *! Authors : Adrian Mander/David Clayton *! Email : adrian.mander@mrc-hnr.cam.ac.uk *! Description : Hotdeck imputation /* 25/07/06 version 1.67 - removed some = and investigated the set seed problem 16/3/07 version 1.68 - spruced up the displays 13/6/07 version 1.69 - Made sure set seed does what the masses want although it is truly the wrong thing to do 27/7/07 version 1.70 - The warnings were not strong enough about the strata information.. in fact I think when there is no data to impute it still tries to combine results. Also a slight error in the output. 15/8/07 version 1.71 - Corrected the confidence intervals.. the tail probability was wrong ALSO checked the calculation of T B Ubar Qbar by hand! 3/9/07 version 1.72 - Corrected the % lines missing to % lines complete */ program define hotdeck version 9.0 syntax [varlist] [if] [in] [using/], [BY(varlist) IMPute(integer 1) STORE GENerate(string) COMmand(string) PARMS(string asis) REPlace NOISE KEEP(varlist) SEED(string) QUIET INFILES(string) ] tokenize "`varlist'" local z "`1'" preserve if "`if'"~="" qui keep `if' /* Check the seed option */ if "`seed'"=="1" local seed 2 if "`seed'"=="" { local noseed "noseed" local seed 1 } confirm number `seed' /* To generate a seed from the time note need to truncate the seed to be below 2^31-1 */ if `seed'==1 { local time "$S_TIME" local date "$S_DATE" tokenize "`time'", parse(":") local seed1 "`1'`3'`5'" tokenize "`date'", parse(" ") local dat "`1'`2'`3'" local dat1 = date("`dat'","dmy") local seed1 "`seed1'`dat1'" di local l_seed "2^31-1" local seed1 = mod(`seed1',`l_seed') set seed `seed1' local seed "`seed1'" /* Added to sort out the seed */ } di "{txt}Seed is set as {res} `seed'" estimates clear tempfile olddata tempvar touse mark `touse' `if' `in' markout `touse' `by', strok di in green "DELETING all matrices...." mat drop _all /* Display the patterns of missingness.. only on observed data not imputed */ if "`infiles'"=="" { if "`by'"=="" _misspat `varlist' `if' `in' else _misspat `varlist' `if' `in', by(`by') local nfill=r(nmiss) } qui save "`olddata'" /* Count the missing data for displaying later */ if "`infiles'"=="" { global allpat = r(allpat) qui count if `touse' local miss = (r(N)-`nfill')/r(N) } /* Make sure the users are using the right syntax.. lots of checks here to make sure*/ if "`command'"=="" { di in red "WARNING: When the option is not selected " di in red "then no analysis is performed on the imputed datasets" di if "`store'"=="" { di "ALSO STORE isnt selected so hotdeck will appear to do nothing" exit(198) } } if `impute'<1 { di in red "The number of imputations must be more than 0 not `impute'" exit(198) } if `impute'==1 & "`infiles'"=="" { if "`store'"=="" | "`command'"~="" { di in red "If one imputation is made then command option should NOT be used" di in red "AND the store option must be specified" exit(198) } } if "`using'"~="" { if "`store'"=="" { di in red "To save datasets you must specify the STORE option" exit(198) } } if "`keep'"~="" { if "`store'"=="" { di in red "If you use the KEEP option you must specify the STORE option" exit(198) } } if "`noise'"~="" & "`command'"=="" { di in red "When specifying noise you must also specify the command option" exit(198) } if "`command'"~="" { if `"`parms'"'==`""' { di in red "To obtain any output from the command option you must also specify " di in red "the parameters of interest using the parms() option" exit(198) } } /************************************************ * Loop over the number of imputed data sets * required ************************************************/ if "`seed'"~="1" set seed `seed' /* This is the if statement that allows the input of imputed datafiles */ if "`infiles'"~="" { local i 1 tokenize "`infiles'" while "`1'"~="" { use "`1'",replace mac shift 1 if "`command'"=="" { di in red "You must use the command option when using INFILES" exit(198) } if "`noise'"~="" `command' else qui `command' /* Do the analysis */ _parms, parms(`"`parms'"') command(`command') iter(`i') /* Select Parameters of interest*/ local i=`i'+1 } local impute=`i'-1 } /* If there are no INFILES .. then just have to create the imputed datasets and analyse them */ else { forv i =1/`impute' { /* Use original dataset */ use "`olddata'",replace qui keep if `touse' /* Impute values */ if "`by'"~="" _hotdeck `varlist', by(`by') i((`seed'+`i')) `noseed' else _hotdeck `varlist', i((`seed'+`i')) `noseed' /* Save imputed datasets */ if "`store'"~="" { if "`using'"=="" local using "imp" if "`keep'"=="" { qui keep `varlist' `by' qui save `using'`i',replace } else { mkvlist `varlist' `by', vlist(`keep') qui keep `r(vlist)' qui save `using'`i',replace } } if "`command'"~="" { /* Do the analysis */ if "`noise'"~="" `command' else qui `command' /* Select Parameters of interest*/ _parms, parms(`"`parms'"') command(`command') iter(`i') } } } /******************************************************** * Loop to calculate the estimates needed * * First get the dimensions of the parameter matrices ********************************************************/ if "`command'"~="" { local dim= rowsof(impV1) mat Qbar = J(1,`dim',0) mat Ubar = J(`dim',`dim',0) /* calc the averaging factor */ local inv = 1/`impute' /* calc the average coef and variance qbar and ubar */ forv i=1/`impute' { mat Qbar= `inv'*impb`i'+ Qbar mat Ubar= `inv'*impV`i'+ Ubar } /* calc between variances */ mat B=J(`dim',`dim',0) local inv1 = 1/(`impute'-1) forv i=1/`impute' { mat B= B + `inv1'*(impb`i' - Qbar)'*(impb`i' - Qbar) } /* Calc total variance */ mat T = Ubar+(1+1/`impute')*B cap mat tempmt=B*inv(Ubar) if _rc==504 { di as error "WARNING: Trying to invert variance matrix with zero elements?" local ter = rowsof(Ubar) mat temp = J(`ter',1,1) mat temp2 = Ubar*temp local tei 1 local names: colfullnames impb1 matrix rownames temp2 = `names' while `tei'<=`ter' { if temp2[`tei',1]==0 { local var:word `tei' of "`names'" di as txt "Variance for covariate `tei' is 0 !!" } local tei=`tei'+1 } mat tempmt=B*inv(Ubar) } local trace=trace(tempmt) /* Everything hunky dorey until now... a strange 1 appears.. */ local r1 = 1-((1+1/`impute')*`trace'/`dim') /************************************************ * Just sorting out the matrix names ************************************************/ local names: rowfullnames impb1 matrix rownames Qbar = `names' local names: colfullnames impb1 matrix colnames Qbar = `names' local names: rowfullnames impV1 matrix rownames T = `names' matrix rownames B = `names' matrix rownames Ubar = `names' local names: colfullnames impV1 matrix colnames T = `names' matrix colnames B = `names' matrix colnames Ubar = `names' mat Tsurr= `r1'*T mat D = Qbar*inv(Tsurr)*Qbar' local D1 = D[1,1]/`dim' local t=`dim'*(`impute'-1) local v1= 4+(`t'-4)*(1+(1-2/`t')*1/`r1')^2 local ftest= fprob(`dim',`v1',`D1') /******************************************************** * The next will output the main results in Stata style * if the normal approximation is good then you could * use the matrix post command ********************************************************/ if "`quiet'"=="" { if `r1'<0 { di in red "WARNING: between se larger than within se in one or more " di in red "parameters invalidating the global F test" } if `t'<4 { di in red "WARNING: t less than 4 invalid global test " di in red "increase parameters OR imputations" } } di di in gr _col(1) "Number of Obs.", _col(45) "= ", as res %5.0f _N di in gr _col(1) "No. of Imputations", _col(45) "= ", as res %5.0f `impute' if "`infiles'"=="" di in gr _col(1) "% Lines of Complete Data", _col(45) "= ", as res %10.4f `miss'*100, as text "%" di in gr _col(1) "F(",%6.3f `v1',",`dim')", _col(45) "= ", as res %10.4f `D1' di in gr _col(1) "Prob > F " , _col(45) "= ", as res %10.4f `ftest' di "{text}{dup 14:{c -}}{c TT}{dup 68:{c -}}" local names: colfullnames impb1 /* Transform the double quoted names to a macrolist */ di in gr _continue "Variable" _col(15) "{c |}",_col(17) "Average", _col(28) "Between", _col(38) "Within", _col(48) "Total", _col(58) "df", _col(68) "t", _col(77) "p-value" di di in gr _continue _col(15) "{c |}", _col(17) "Coef.",_col(28) "Imp. SE", _col(38) "Imp. SE", _col(47) " SE", _col(58) "", _col(68) "", _col(74) "" di di _continue "{text}{dup 14:{c -}}{c +}{dup 68:{c -}}" foreach name of local names { di mat qhat=Qbar[1,"`name'"] mat b=B["`name'","`name'"] mat u=Ubar["`name'","`name'"] mat t=T["`name'","`name'"] local df = (`impute'-1)*(1+(u[1,1])/((1+1/`impute')*b[1,1]))^2 local ttest= qhat[1,1]/sqrt(t[1,1]) di as text _continue "`name'",_col(15) "{c |}", as res _col(10) %7.4f qhat[1,1],_col(17) %9.3f sqrt(b[1,1]), _col(25) %9.3f sqrt(u[1,1]), _col(34) %9.3f sqrt(t[1,1]), _col(44) %9.1f `df', _col(53) %9.3f `ttest', _col(62) %9.3f tprob(`df',`ttest') } di di _continue "{text}{dup 14:{c -}}{c +}{dup 68:{c -}}" di local name : word 1 of `names' local i 1 di in gr _continue "Variable", _col(15) "{c |}", _col(17) "[$S_level% Conf. Interval]" di di _continue "{text}{dup 14:{c -}}{c +}{dup 68:{c -}}" while "`name'"~="" { di mat qhat=Qbar[1,"`name'"] mat b=B["`name'","`name'"] mat u=Ubar["`name'","`name'"] mat t=T["`name'","`name'"] local df = (`impute'-1)*(1+(u[1,1])/((1+1/`impute')*b[1,1]))^2 local ttest= qhat[1,1]/sqrt(t[1,1]) local prob = 1-((100-$S_level)/2)/100 local tvalue=abs( invttail(`df',`prob') ) /* The t-distribution function could be very out here.... due to a version 6 bug! version 6 : local tvalue = invt(`df',`prob') THIS HAS BEEN REMOVED 15Aug07 as the probability is calculated on adding the two tails AND it should have been a single tailed value!!! */ local left = qhat[1,1]-`tvalue'*sqrt(t[1,1]) local right = qhat[1,1]+`tvalue'*sqrt(t[1,1]) di as text _continue "`name'",_col(15) "{c |}", as res %9.4f `left', %9.4f `right' local i=`i'+1 local name : word `i' of `names' } di "" di "{text}{dup 14:{c -}}{c BT}{dup 68:{c -}}" } /* end of command if statement */ restore end /**************************************************** * The approximate Bayesian Bootstrap hotdecking ****************************************************/ program define _hotdeck version 9.0 syntax [varlist] [using], [BY(string) Iseed(string) NOSEED] local iseed =`iseed' tokenize "`varlist'" local z "ipattern" if "`by'"!="" confirm ex var `by' tempvar nobs bstrp b2strp temp temp2 local nold = _N local nnew = _N /* This is the place of difference for a set seed command ..*/ if "`noseed'"=="" set seed `iseed' qui sort `by' `z' `varlist' qui gen long `nobs' = (`z'!=.) if "`by'"=="" { qui replace `nobs' = sum(`nobs') qui replace `nobs' = `nobs'[_N] qui gen long `bstrp' = int(uniform()*`nobs'+1) qui gen long `b2strp' = int(uniform()*`nobs'+1) qui gen long `temp' = `bstrp'[`b2strp'] qui replace `bstrp' = `temp' qui replace `bstrp' = _n if _n<=`nobs' qui tokenize "`varlist'" while "`1'"~="" { qui gen `temp2' = `1'[`bstrp'] qui replace `1' = `temp2' qui drop `temp2' qui mac shift 1 } } else { qui by `by': replace `nobs' = sum(`nobs') qui by `by': replace `nobs' = `nobs'[_N] qui by `by': gen long `bstrp' = int(uniform()*`nobs'+1) qui by `by': gen long `b2strp' = int(uniform()*`nobs'+1) qui by `by': gen long `temp' = `bstrp'[`b2strp'] qui by `by': replace `bstrp' = `temp' qui by `by': replace `bstrp' = _n if _n<=`nobs' qui tokenize "`varlist'" while "`1'"~="" { qui by `by': gen `temp2' = `1'[`bstrp'] qui by `by': replace `1' = `temp2' qui mac shift 1 qui drop `temp2' } } end /******************************************************************* * Get the parameters or a subset of them from the * model and the subset * the covariance variance matrix as well * Note that this section can also handle non-regression commands * and macro lists *******************************************************************/ program define _parms syntax [varlist], [PARMS(string asis) ITER(integer 1) COMMAND(string) GENerate(string) REPlace] /* previously accepted a varlist in the parms string.. too many difficulties with multiple equation models so this code below is being dropped * local 0 "`parms'" * while "`parms'"~="" { * gettoken 0 parms: parms , parse(" ,") * cap syntax [varlist] * if _rc~=0 { * if "`0'"=="_cons" local vlist "`vlist' `0'" <-- just extract _cons * else local plist "`plist' `0'" * } * else local vlist "`vlist' `varlist'" * } */ foreach item in `"`parms'"' { local vlist `"`vlist' `item'"' } /* if results were not part of a regression command */ if "`e(cmd)'"=="" { local names "" if `iter'==1 di in red "Using Non Regression Parameters and Command" tokenize "`plist' `vlist'" local np=0 while "`1'"~="" { local names1 ="`names1' `1'" local names2 ="`names2' `2'" if "`2'"=="" { di in red "Must supply variance estimate of `1'" exit(302) } mac shift 2 local `np++' } mat impb`iter' = J(1,`np',0) mat impV`iter' = J(`np',`np',0) tokenize "`plist' `vlist'" local np 1 while "`1'"~="" { if "$`1'"=="" & "``1''"=="" { di in red "Global = $`1' Local = ``1''" di in red "Global/local macro `1' is missing " exit(198) } if "``1''"~="" mat impb`iter'[1,`np'] = ``1'' if "$`1'"~="" & "``1''"=="" mat impb`iter'[1,`np'] = $`1' if "$`2'"=="" & "``2''"=="" { di in red "Global = $`2' Local = ``2''" di in red "Global/local macro `2' is missing " exit(198) } if "``2''"~="" mat impV`iter'[`np',`np'] = ``2'' if "$`2'"~="" & "``2''"=="" mat impV`iter'[`np',`np'] = $`2' local np=`np'+1 mac shift 2 } matrix colnames impb`iter'=`names1' matrix colnames impV`iter'=`names1' matrix rownames impV`iter'=`names1' } /* The regression-type output part */ else { matrix myb = e(b) matrix myV = e(V) /* This next statement is to handle double quoted strings.. otherwise parms will contain one item in a macro */ local teparms :di `parms' local first 1 foreach item of local teparms { if `first'==1 { cap mat impb`iter' = myb[.,"`item'"] if _rc==111 { di as error `" Attempted to extract `item' from e(b) "' mat list e(b) di as error "Check the matrix of estimates and only include column names in the parameters NOT variable names" exit(111) } mat impVt`iter'= myV[.,"`item'"] } else { mat temp = myb[.,"`item'"] mat impb`iter'= impb`iter' , temp mat drop temp mat temp=myV[.,"`item'"] mat impVt`iter'= impVt`iter' , temp mat drop temp } local `first++' } local first 1 foreach item of local teparms { if `first'==1 mat impV`iter' = impVt`iter'["`item'",.] else { mat temp=impVt`iter'["`item'",.] mat impV`iter'= impV`iter' \ temp } local `first++' } } end /************************************************* * Look at the missing pattern in the varlist *************************************************/ program define _misspat,rclass syntax varlist [if] [in] , [BY(string) ] tokenize "`varlist'" tempvar touse2 tempid qui gen long `tempid'=_n mark `touse2' `if' `in' markout `touse2' qui gen str50 pattern="" local allstr "" while "`1'"~="" { qui replace pattern = cond(`1'==.,pattern+"*",pattern+"-") if `touse2' local allstr="-`allstr'" mac shift 1 } qui compress pattern sort pattern lab var pattern "Missing pattern" di di in green "Missing Patterns" di "{text}{dup 16:{c -}}" di di in green "Table of the Missing data patterns " di in green " * signifies missing and - is not missing" di di "Varlist order: `varlist'" tab pattern if `touse2' local n=r(N) qui count if pattern=="`allstr'" & `touse2' if r(N)==`n' { di "There is no missing data in the varlist" exit(198) } return scalar nmiss = `n'-r(N) return local allpat = "`allstr'" qui gen ipattern=cond(pattern=="`allstr'",1,.) if `touse2' /***************************************** * Calculate stratum missing numbers *****************************************/ if "`by'"~="" { di di "{text}STRATUM information" di "{text}{dup 19:{c -}}" di di "{text} Listing the number observed (No_obs) and " di in green "the number missing (No_miss) in each stratum" tempvar cnt mcnt qui sort `by' qui by `by':gen `cnt'=sum(ipattern) qui by `by':gen `mcnt'=sum(ipattern==.) qui by `by': replace `cnt'=cond( _n==_N,`cnt',.) qui by `by': replace `mcnt'=cond( _n==_N,`mcnt',.) rename `cnt' No_obs rename `mcnt' No_miss l `by' No_obs No_miss if No_obs~=., noobs di qui count if No_obs==0 if `r(N)'>0 { di in red "WARNING: `r(N)' strata with NO complete records" di di "{error}This implies that within these strata the missing data will NOT be replaced " di "and hence will give the wrong answers in the analysis because the analysis" di "command will do casewise deletion" } qui count if No_obs==1 if `r(N)'>0 di in blue "Note: `r(N)' strata with only 1 complete record" qui count if (No_obs>1 & No_obs<6) if `r(N)'>0 di in blue "Note: `r(N)' strata with 2-5 complete records" di } /* I thought that the following bit of command might've sorted out the seed problem :( but I don't think so */ qui sort `tempid' end /************************************************* * Expand stata syntax *************************************************/ program define mkvlist, rclass syntax varlist, VLIST(string) local o_vlist "`varlist'" local keep "`vlist'" local 0 "`keep'" while "`keep'"~="" { gettoken 0 keep: keep , parse(" ,") cap syntax [varlist] if _rc~=0 { if "`0'"=="_cons" local vlist "`vlist' `0'" else local plist "`plist' `0'" } else local vlist "`vlist' `varlist'" } return local vlist "`o_vlist' `vlist'" end