/****************************************************************************** *! version 3.0.2 Ian White 9jun2015 handles failure by returning missing matrix version 3.0.1 Ian White 27may2015 version 2.3.4 Ian White 30mar2015 deleted unused prog mmnew4 version 2.3.3 Ian White 15nov2011 Unstructured between-studies variance Sigma ******************************************************************************/ program define mvmeta_bscov_unstructured, rclass syntax [if] [in], [cholnames /// setup start(string) /// Set up mm1 mm2 notrunc /// Method of moments varparms(string) /// Within -ml- postfit /// After -ml- debug mmfix ] if "`debug'"=="debug" di as input "mvmeta_bscov_unstructured `0'" local p $MVMETA_p marksample touse tempname chol Sigma binit init if "`cholnames'"=="cholnames" { forvalues r=1/`p' { local y`r' = word("$MVMETA_ylist",`r') } } if "`setup'"!="" { di as text "Note: variance-covariance matrix is " as result "unstructured" // Set Sigma to starting value if "`start'"=="" mat `Sigma' = I(`p') else { cap mat `Sigma' = `start' if _rc { di as error `"mvmeta_bscov_unstructured: start() must be a `p'x`p' matrix"' exit 498 } if rowsof(`Sigma')!=`p' | colsof(`Sigma')!=`p' { di as error `"mvmeta_bscov_unstructured: start() must be a `p'x`p' matrix"' exit 498 } if trace(`Sigma')==0 { di as error `"mvmeta_bscov_unstructured: start() has trace zero: changed to I(`p')"' mat `Sigma' = I(`p') } } mvmeta_mufromsigma, sigma(`Sigma') mat `binit' = e(b) cholesky2 `Sigma' `chol' * add chol into inits mat `init'=`binit' forvalues r=1/`p' { mat `init' = `init', `chol'[`r',1..`r'] } return local nvarparms = `p'*(`p'+1)/2 return matrix binit=`binit' return matrix init=`init' // SET UP EQUATIONS forvalues r=1/`p' { forvalues s=1/`r' { local eq = cond("`cholnames'"=="cholnames", "c_`y`s''_`y`r''", "chol`s'`r'") local eqlist2 `eqlist2' (`eq':) } } return local eqlist `eqlist2' } if "`varparms'" != "" { mat `chol' = J(`p',`p',0) local k 0 forvalues r=1/`p' { forvalues s=1/`r' { local ++k matrix `chol'[`r',`s'] = `varparms'[1,`k'] } } matrix `Sigma'=`chol'*`chol'' } if "`postfit'" != "" { mat `chol'=J(`p',`p',0) forvalues r=1/`p' { forvalues s=1/`r' { local eq = cond("`cholnames'"=="cholnames", "c_`y`s''_`y`r''", "chol`s'`r'") matrix `chol'[`r',`s'] = [`eq']_b[_cons] } } matrix `Sigma'=`chol'*`chol'' mat rownames `Sigma' = $MVMETA_ylist mat colnames `Sigma' = $MVMETA_ylist // SET UP NLCOM EXPRESSIONS // Note problem: If an element is too close to 0, nlcom fails with message "Maximum number of iterations exceeded". My first fix was to ignore elements smaller than 1E-8, but now I just ignore elements that make nlcom fail. forvalues r=1/`p' { forvalues s=1/`r' { forvalue t=1/`s' { local plus = cond("`nlcom`s'`r''"=="","","+") local nlcom`s'`r' `nlcom`s'`r'' `plus' [chol`t'`r']_b[_cons]*[chol`t'`s']_b[_cons] } return local Sigma`s'`r' `nlcom`s'`r'' } } } // METHOD OF MOMENTS if ("`mm1'"=="mm1") | ("`mm2'"=="mm2") { tempname Q Qa Qb * READ GLOBAL MACROS local ylist $MVMETA_ylist local xcons $MVMETA_xcons forvalues r = 1/`p' { local var`r' ${MVMETA_yvar_`r'} local xvars_`r' ${MVMETA_xvars_`r'} forvalues s = `r'/`p' { local cov`r'`s' ${MVMETA_Svar_`r'_`s'} } } if "`mm1'"=="mm1" { // ORIGINAL METHOD OF MOMENTS * NB this code uses the original variables, not the matrices: it is sensitive to missingness tempname Mu varMu pred xi AX AY A P psisqrt WX WY VX VY ael bel tempvar ok w wsqrt term mmcorr foreach name in `Q' `Qa' `Qb' `Sigma' { matrix `name' = J(`p',`p',.) matrix rownames `name' = `ylist' matrix colnames `name' = `ylist' } forvalues r = 1/`p' { forvalues s = `r'/`p' { qui gen `ok' = !missing(`var`r'') & !missing(`var`s'') if `touse' qui count if `ok' & `touse' local n_ok = r(N) qui gen `w'=1/sqrt(`cov`r'`r''*`cov`s'`s'') if `ok' & `touse' qui replace `w'=0 if !`ok' & `touse' * FIT MODEL TO APPROPRIATE OBSERVATIONS & GET FITTED VALUES cap drop `pred'* cap reg `var`r'' `xvars_`r'' [aw=`w'] if `touse', `constant' cap predict double `pred'`r' if `touse' if `s'>`r' { cap reg `var`s'' `xvars_`s'' [aw=`w'] if `touse', `constant' cap predict double `pred'`s' if `touse' } * Q MATRIX cap gen `term'=`w'*(`var`r''-`pred'`r')*(`var`s''-`pred'`s') if `ok' & `touse' if !_rc { summ `term' if `touse', meanonly matrix `Q'[`r',`s'] = r(sum) * EXPECTATION OF Q MATRIX mkmat `xvars_`r'' `xcons' if `ok' & `touse', matrix(`WX') mkmat `xvars_`s'' `xcons' if `ok' & `touse', matrix(`WY') qui gen double `wsqrt' = sqrt(`w') if `ok' & `touse' mkmat `wsqrt' if `ok' & `touse', matrix(`psisqrt') mat `psisqrt' = diag(`psisqrt') qui gen double `mmcorr' = `cov`r'`s''/sqrt(`cov`r'`r''*`cov`s'`s'') if `ok' & `touse' mkmat `mmcorr' if `ok' & `touse', matrix(`P') matrix `P' = diag(`P') mat `VX' = `psisqrt' * `WX' mat `VY' = `psisqrt' * `WY' mat `AX' = I(`n_ok') - `VX'*syminv(`VX''*`VX')*`VX'' mat `AY' = I(`n_ok') - `VY'*syminv(`VY''*`VY')*`VY'' mat `A' = `AX''*`AY' mat `ael' = trace(`A'*`P') mat `bel' = trace(`A'*`psisqrt'*`psisqrt') matrix `Qa'[`r',`s'] = `ael'[1,1] matrix `Qb'[`r',`s'] = `bel'[1,1] * SOLVE FOR SIGMA matrix `Sigma'[`r',`s'] = (`Q'[`r',`s']-`Qa'[`r',`s']) / `Qb'[`r',`s'] foreach name in `Q' `Qa' `Qb' `Sigma' { matrix `name'[`s',`r'] = `name'[`r',`s'] } drop `term' `wsqrt' `mmcorr' } if `Sigma'[`r',`s'] == . { if `s'>`r' & "`mmfix'"=="mmfix" { di as error "Method of moments warning: Sigma[`r',`s'] could not be computed and is being set to 0" matrix `Sigma'[`r',`s'] = 0 matrix `Sigma'[`s',`r'] = 0 } else { * di as error "Method of moments error: Sigma[`r',`s'] could not be computed" * di as error " (consider mmfix option)." } } drop `ok' `w' } } if "`debug'"=="debug" { foreach matrix in Q Qa Qb { mat list ``matrix'', title(Method of moments: `matrix' matrix) } mat list `Sigma', title(Method of moments: untruncated estimate of Sigma) } } else if "`mm2'"=="mm2" { mata: mmnew5("$MVMETA_ymat","$MVMETA_Smat","$MVMETA_Xmat", /// "`Sigma'","`Q'","`Qa'","`Qb'","`debug'") foreach name in `Q' `Sigma' { matrix rownames `name' = `ylist' matrix colnames `name' = `ylist' } } * at this point we have `Sigma' as the MM estimate * TRUNCATION if matmissing(`Sigma') { if "`debug'"=="debug" di as error "Warning: Sigma could not be computed by method of moments" mat `Sigma' = J(`p',`p',.)+`Sigma' // this syntax keeps row/colnames local negevals . } else { // count negative evals, and optionally set to 0 local negevals 0 tempname evecs evals mat symeigen `evecs' `evals' = `Sigma' forvalues r=1/`p' { if `evals'[1,`r']<0 { mat `evals'[1,`r']=0 local ++negevals } } if "`trunc'"!="notrunc" mat `Sigma' = `evecs'*diag(`evals')*`evecs'' } * TIDY UP AND RETURN mat colnames `Sigma'=`ylist' if "`debug'"=="debug" mat list `Sigma', title(Method of moments: possibly truncated estimate of Sigma) return scalar negevals = `negevals' return matrix Q = `Q' return matrix Qa = `Qa' return matrix Qb = `Qb' } // COMMON ENDING FOR ALL OPTIONS *mat rownames `Sigma' = $MVMETA_ylist return matrix Sigma = `Sigma' return scalar nparms_aux = `p'*(`p'+1)/2 return scalar neqs_aux = `p'*(`p'+1)/2 end *============================= CHOLESKY2 PROGRAM =============================== program cholesky2 * produce an approximate cholesky decomposition, even if matrix is not positive definite args M C cap matrix `C' = cholesky(`M') if _rc { local eps = trace(`M')/1000 if `eps'<=0 { di as error "cholesky2: matrix `M' has non-positive trace" matrix list `M' exit 498 } local p = rowsof(`M') cap matrix `C' = cholesky(`M'+`eps'*I(`p')) if _rc { di as error "cholesky2: can't decompose matrix `M'" matrix list `M' exit 498 } } end *============================= END OF CHOLESKY2 PROGRAM =============================== *=================== NEW MM PROGRAM - META-REGRESSION VERSION ================ mata: void mmnew5(string scalar yname, string scalar Sname, string scalar Xname, string scalar Sigmaname, string scalar Qname, string scalar vecbtrBname, string scalar BcrossAtermname, | string scalar debug) { // Notation as in Dan's article if(debug=="debug") "mmnew5: Starting loop 0: find p and n" for(i=1;1;++i) { y = st_matrix(yname+strofreal(i))' if(i==1) p=rows(y) if(!rows(y)) break } n=i-1 if(debug=="debug") "mmnew5: Starting loop 1: set up data" yy = J(n,1,NULL) SS = J(n,1,NULL) WW = J(n,1,NULL) RR = J(n,1,NULL) XX = J(n,1,NULL) for(i=1;i<=n;++i) { y = st_matrix(yname+strofreal(i))' r = !colmissing(y') S = st_matrix(Sname+strofreal(i)) S = editmissing(S,0) X = st_matrix(Xname+strofreal(i)) if(i==1) p=rows(y) if(i==1) qplus=rows(X) small = select(I(p),r') Ssmall = small*S*small' Wsmall = cholinv(Ssmall) yy[i] = &(editmissing(y,0)) RR[i] = &(diag(r)) SS[i] = &(small'*Ssmall*small) WW[i] = &(small'*Wsmall*small) XX[i] = &(X') // NB &(X) makes all XX's point to same (last) X } if(debug=="debug") "mmnew5: Starting loop 2: regress y on x" XWXsum = J(qplus,qplus,0) XWysum = J(qplus,1,0) for(i=1;i<=n;++i) { y = *(yy[i]) W = *(WW[i]) X = *(XX[i]) XWXsum = XWXsum + X'*W*X XWysum = XWysum + X'*W*y } beta = cholinv(XWXsum)*XWysum if(debug=="debug") "mmnew5: beta" if(debug=="debug") beta Wsuminv = cholinv(Wsum) ybar = Wsuminv*Wysum Q = J(p,p,0) A = J(p,p,0) Bvec = J(p^2,p^2,0) if(debug=="debug") "mmnew5: Starting loop 3: to find Q and hence Sigma" for(i=1;i<=n;++i) { y = *(yy[i]) W = *(WW[i]) X = *(XX[i]) R = *(RR[i]) yhat = X*beta Q = Q + W*(y-yhat)*(y-yhat)'*R if(i==1) bigX=X else bigX=(bigX \ X) if(i==1) bigW=W else bigW=blockdiag(bigW, W) if(i==1) bigR=R else bigR=blockdiag(bigR, R) } if(debug=="debug") "mmnew5: Q" if(debug=="debug") Q if(debug=="debug") "mmnew5: bigW" if(debug=="debug") bigW if(debug=="debug") "mmnew5: bigX" if(debug=="debug") bigX bigH = bigX*cholinv(bigX'*bigW*bigX)*bigX'*bigW if(debug=="debug") "mmnew5: bigH" if(debug=="debug") bigH bigA = (I(n*p) - bigH)' * bigW if(debug=="debug") "mmnew5: bigA" if(debug=="debug") bigA bigB = (I(n*p) - bigH)' * bigR if(debug=="debug") "mmnew5: bigB" if(debug=="debug") bigB btrB = J(p,p,0) BcrossAterm = J(p^2,p^2,0) for(i=1;i<=n;i++) { iu=i*p il=(i-1)*p+1 btrB = btrB + bigB[|il, il \ iu, iu|] for(j=1;j<=n;j++) { ju=j*p jl=(j-1)*p+1 Aij = bigA[|il, jl \ iu, ju|] Bji = bigB[|jl, il \ ju, iu|] BcrossAterm = BcrossAterm + Bji' # Aij } } if(debug=="debug") "mmnew5: btrB" if(debug=="debug") btrB if(debug=="debug") "mmnew5: BcrossAterm" if(debug=="debug") BcrossAterm vecSigma = luinv(BcrossAterm) * vec(Q-btrB) SigmaL=rowshape(vecSigma,p) SigmaR = SigmaL' Sigma=(SigmaL+SigmaR)/2 if(debug=="debug") "mmnew5: Sigma" if(debug=="debug") Sigma /* Return results */ st_matrix(Sigmaname,Sigma) st_matrix(Qname,Q) st_matrix(vecbtrBname,vec(btrB)) st_matrix(BcrossAtermname,BcrossAterm) } end *========================== END OF NEW MM PROGRAM ============================