*! version 2.0 April 15, 2008 @ 21:11:42
*! Author: Roger Harbord, University of Bristol

	/* bivariate meta-analysis of diagnostic accuracy
		- wrapper for gllamm binomial-binormal model

		Changes since v0.4 :
		1.00 uses -xtmelogit- in Stata10 instead of -gllamm-
		     & ereturns estimates for bivariate & hsroc
		0.45 fixed plot option bug (if `touse' -> `if' `in' )
		0.44: changed "Summary point" to "... pt." so "Copy Table" works better.
		0.43: fixed "dataset has changed since last saved" after -metandi-,
		changed -error nnn- to -exit nnn- where appropriate.
		0.42: fixed -by- bug
		(-by: metandi ...- fitted model repeatedly to all data)
*/

program define metandi, byable(onecall) sortpreserve
version 8.2

	if _by() {
		local BY `"by `_byvars'`_byrc0':"'
		}

	local version: di "version " string(_caller()) ":"

	if replay() {
		if "`e(cmd)'" != "metandi"  ///
		  error 301        // last estimates not found
		if _by() error 190 // request may not be combined with by
		Display `0'
		exit
		}

   `version' `BY' Estimate `0'
end


program define Estimate, eclass byable(recall)
version 8.2
	syntax varlist(min=4 max=4) [if] [in] ///
	  [, Plot IP(string) NIp(int -1)  ///
	  Detail Level(real `c(level)') ///
	  TRace noLOg  Gllamm ///
	  noBivariate noHsroc noSummarypt ///
	  /* undocumented: */ NIPUni(int 9) clear  allc ]

	marksample touse
	tokenize `varlist'
	local true1  `1'
	local false0 `2'
	local false1 `3'
	local true0  `4'

	/* xtmelogit or gllamm ? */
	capture which xtmelogit
	if _rc != 0 {
		local gllamm gllamm
		}

	if "`gllamm'" != "" {
		capture which gllamm
		if _rc != 0 {
			di as error "command gllamm not found"
			di as error "Try -ssc install gllamm-"
			exit 111
			}
		if "`detail'" == "" {
			local nodis nodisplay
			local unilog nolog
			}
		}



	/** Parse ip() and nip() **/
	if ~inlist("`ip'","", "g", "m") {
		di as error "ip option can only be ip(g) or ip(m)"
		exit 198
		}
	if "`ip'" == "m" {
		if "`gllamm'" == "" {
			di as error  ///
			  "ip(m) has no effect unless option gllamm is specified"
			exit 198
			}
		if `nip' == -1 local nip 9
		/* exit early if ip(m) with unimplemented nip() */
		else if  !inlist(`nip', 5, 7, 9, 11, 15) {
			di as error "nip must be 5, 7, 9, 11 or 15 with ip(m)"
			//  - else fitting bivariate model in gllamm will give this error
			exit 198
			}
		}
	else if `nip' == -1 local nip 5

	/* this sidesteps oversight in gllamm version < 2.3.13 (11 nov 2006)
	where explicitly specifying ip(g) gave an error message */
	if "`ip'" == "g" {
		local ip //
		}

	/* allc implies display */
	if "`allc'" != "" local nodis

	qui count if `touse'
	if r(N) < 4 {
		di as error "metandi requires a minimum of four studies"
		exit 2001 // insufficient observations
		}

	/** end parsing ***/


	if "`clear'" =="" preserve
	/** reshape data to long format suitable for gllamm **/
	qui {
		gen long _metandi_i = _n
		gen long _metandi_n1 = `true1' +`false1' if `touse'
		gen long _metandi_n0 = `true0' + `false0' if `touse'
		gen long _metandi_true1 = `true1'
		gen long _metandi_true0 = `true0'

		/* d1 is diseased (sensitivity) d0 is nondiseased (specificity) */
		reshape long _metandi_n _metandi_true, i(_metandi_i) j(_metandi_d1)
		sort _metandi_i _metandi_d1
		gen byte _metandi_d0 = 1 - _metandi_d1
		} // end quietly

	if "`gllamm'" != "" {


		eq eq1: _metandi_d1
		eq eq0: _metandi_d0

		/*** run univariate models to get good starting values ***/
		foreach g of numlist 1 0 {
			if `g'==1 local ss Sensitivity, Se
			else      local ss Specificity, Sp
			/* use Sum(true)/Sum(n) as starting value for summary proportion */
			summ _metandi_true if _metandi_d`g' & `touse', meanonly
			local sumtrue = r(sum)
			summ _metandi_n if _metandi_d`g' & `touse', meanonly
			matrix b0uni = ( logit( `sumtrue' / r(sum) ), 0.5 )

			if "`log'" == "" {
				di as txt _n "Fitting univariate model for " _c
				di as txt "`ss' = " as res "`true`g''" as txt" / ("  ///
				  as res "`true`g''" as txt" + " as res "`false`g''" as txt ")"
				}

			gllamm _metandi_true _metandi_d`g' if _metandi_d`g' & `touse', ///
			  nocons i(_metandi_i) nrf(1) eqs(eq`g') ///
			  family(binomial) denom(_metandi_n) link(logit) from(b0uni) copy ///
			  `nodis' `unilog' `allc' `trace' `dots' nip(`nipuni')  ///
			  noadapt // ip(g) always. ip(m) is not appropriate for 1 dimension.

			matrix b`g' = e(b)
			qui gllapred _metandi_u`g' if `touse', u
			drop _metandi_u`g's1
			}



		/*** construct starting values ***/
		qui bysort _metandi_i (_metandi_d1) :  ///
		  replace _metandi_u0m1 = _metandi_u0m1[_n-1] if mi(_metandi_u0m1) & `touse'
		if "`detail'" !="" {
			di as txt "Covariance of posterior mean logits:"
			correlate _metandi_u1m1 _metandi_u0m1 if `touse', covariance
			}
		qui correlate _metandi_u1m1 _metandi_u0m1 if `touse'
		drop _metandi_u1m1 _metandi_u0m1
		if "`detail'" !="" di as txt "..giving correlation " as res %7.4f r(rho)
		scalar cov = r(rho) * b1[1,2] * b0[1,2]
		matrix v0 = ( b1[1,2]^2, cov \ cov, b0[1,2]^2 )
		matrix b00 = cholesky(v0)
		matrix b0 = ( b1[1,1], b0[1,1], b00[1,1], b00[2,2], b00[2,1] )

		/*** fit full model ***/
		if "`log'" == "" {
			di as txt _n "Fitting bivariate model:"
			}

		gllamm _metandi_true _metandi_d1 _metandi_d0 if `touse', ///
		  nocons i(_metandi_i) nrf(2) eqs(eq1 eq0) ///
		  family(binomial) denom(_metandi_n) link(logit) from(b0) copy  ///
		  `log' `allc' `trace' `dots' nip(`nip') ip(`ip') adapt `nodis'

		capture _estimates drop _metandi
		_estimates hold _metandi, copy

		tempname ll
		scalar `ll' = e(ll) // log-likelihood

		// c22 v small -> corrlogits=+/-1 & numerical problem if include in nlcom
		local c22term "[_me1_2]_b[_metandi_d0]^2"
		if abs([_me1_2]_b[_metandi_d0]) < c(epsfloat) local c22term 0
		qui nlcom ///
		  ( muA: _b[_metandi_d1] ) ///
		  ( muB: _b[_metandi_d0] ) ///
		  ( s2A: [_me1_1]_b[_metandi_d1]^2 ) /// c11^2
		  ( s2B: [_me1_2_1]_b[_cons]^2 + `c22term' ) /// c12^2 + c22^2
		  ( sAB: [_me1_1]_b[_metandi_d1] * [_me1_2_1]_b[_cons] ) /// c11 * c21
		  , post

		}   // endif gllamm



	else { // xtmelogit
		if "`detail'" == ""  ///
		  local xtmeopts "noretable nofetable noheader nogroup nolrtest"
		xtmelogit _metandi_true _metandi_d1 _metandi_d0, nocons ///
		  || _metandi_i: _metandi_d1 _metandi_d0, nocons covariance(un) ///
		  binomial(_metandi_n) ///
		  intp(`nip') refineopts(iterate(3)) /// default 2 fails with CT data
		  `xtmeopts' `log' `trace'

		capture _estimates drop _metandi
		_estimates hold _metandi, copy

		tempname ll
		scalar `ll' = e(ll) // log-likelihood

		// c22 v small -> corrlogits=+/-1 & numerical problem if include in nlcom
		local corrterm "tanh([atr1_1_1_2]_b[_cons])"
		if [atr1_1_1_2]_b[_cons] < -4 local corrterm "(-1)"
		if [atr1_1_1_2]_b[_cons] >  4 local corrterm "1"

		qui nlcom ///
		  ( muA: _b[_metandi_d1]              ) ///
		  ( muB: _b[_metandi_d0]              ) ///
		  ( s2A: exp(2 * [lns1_1_1]_b[_cons]) ) ///
		  ( s2B: exp(2 * [lns1_1_2]_b[_cons]) ) ///
		  ( sAB: exp([lns1_1_1]_b[_cons]) * exp([lns1_1_2]_b[_cons])  ///
		  * `corrterm'                        ) ///
		  , post


		}

	if "`clear'" == "" restore

	tempname biv bhsroc Vhsroc


	_estimates hold `biv', copy

	local s2alphaterm  "2*( sqrt(_b[s2A] * _b[s2B])  + _b[sAB] )"
	local s2thetaterm ".5*( sqrt(_b[s2A] * _b[s2B])  - _b[sAB] )"
	if "`c22term'"=="0" | "`corrterm'"=="(-1)" | "`corrterm'"=="1" {
		if _b[sAB] < 0 local s2alphaterm 0
		else           local s2thetaterm 0
		}

	qui nlcom ///
	  ( Lambda:     ( _b[s2B] / _b[s2A] )^(.25) * _b[muA] ///
	  + ( _b[s2A] / _b[s2B] )^(.25) * _b[muB]               ) ///
	  ( Theta: .5*( ( _b[s2B] / _b[s2A] )^(.25) * _b[muA] ///
	  - ( _b[s2A] / _b[s2B] )^(.25) * _b[muB] )             ) ///
	  ( beta:  .5*log(_b[s2B] / _b[s2A])                    ) ///
	  ( s2alpha: `s2alphaterm'  ) ///
	  ( s2theta: `s2thetaterm'  ) ///
	  , post

	matrix `bhsroc' = e(b)
	matrix `Vhsroc' = e(V)

	_estimates unhold `biv'

	ereturn repost, esample(`touse')
	ereturn matrix b_hsroc `bhsroc'
	ereturn matrix V_hsroc `Vhsroc'
	ereturn local cmd "metandi"
	ereturn local predict metandi_p
	ereturn scalar N =e(N)/2
	ereturn scalar ll = `ll'
	/* store varlist (e.g. tp fp fn tn) for use by metandi_p */
	ereturn local tpfpfntn "`varlist'"
	if "`gllamm'" == "" ereturn local method "xtmelogit"
	else ereturn local method "gllamm"
	ereturn local title "Meta-analysis of diagnostic accuracy"


	Display, level(`level') `bivariate' `hsroc' `summarypt'

	if "`plot'" != "" metandiplot `e(tpfpfntn)' `if' `in'
	/* NB not -if `touse'- as `touse' var now deleted - see -help ereturn-. */

end   // program Estimate



program define Display, eclass
	syntax [, Level(real `c(level)') noBivariate noHsroc noSummarypt]

	/* Header */
	di as txt _n e(title) _n
	di as txt "Log likelihood   = " as res e(ll)  ///
	  _col(51) as txt "Number of studies = " as res %8.0f e(N)
// di as txt "Condition number = " as res  e(cn)
	di in smcl as txt "{hline 13}{c TT}{hline 64}"
	di in smcl as txt "{col 14}{c |}      Coef.   Std. Err.      z    P>|z|     [95% Conf. Interval]"

	tempname biv b V
	_estimates hold `biv', copy


	/*** Bivariate parameters ***/
	RenamePost b V  ///
	  "muA:_cons muB:_cons s2A:_cons s2B:_cons sAB:_cons"
	/*  Corr(logits) */
	local corr = [sAB]_b[_cons] / sqrt( [s2A]_b[_cons] * [s2B]_b[_cons] )

	if "`bivariate'" == "" {
		di in smcl as txt "{hline 13}{c +}{hline 64}"
		di in smcl as res "Bivariate" as txt "{col 14}{c |}"
		_diparm muA, label(E(logitSe)) noprob level(`level')
		_diparm muB, label(E(logitSp)) noprob level(`level')
		/* Var(logitSe), Var(logitSp) */
		_diparm s2A s2B, label(Var(logitSe)) ci(log) noprob level(`level') f(@1) d(1 0)
		_diparm s2B s2A, label(Var(logitSp)) ci(log) noprob level(`level') f(@1) d(1 0)

		if abs(`corr')>0.995 /// SE & CI incalculable
		  di in smcl as txt "Corr(logits){col 14}{c |}" ///
		  as res "{col 17}" %9.0f `corr' "{col 36}.{col 66}.{col 78}."
		else _diparm sAB s2A s2B, label(Corr(logits)) ci(atanh) noprob  ///
		  level(`level') f(@1/sqrt(@2*@3))  ///
		  d(1/sqrt(@2*@3)  -.5*@1/sqrt(@2^3*@3)  -.5*@1/sqrt(@2*@3^3))
		}

	/*** HSROC parameters: Lambda, Theta, beta, sigma^2_alpha, sigma^2_theta ***/
	_estimates unhold `biv'
	_estimates hold `biv', copy

	if "`hsroc'" == "" {
		RenamePost b_hsroc V_hsroc ///
		  "Lambda:_cons Theta:_cons beta:_cons s2alpha:_cons s2theta:_cons"
		di in smcl as txt "{hline 13}{c +}{hline 64}"
		di in smcl as res "HSROC" as txt "{col 14}{c |}"
		_diparm Lambda, label(Lambda) noprob level(`level')
		_diparm Theta, label(Theta) noprob level(`level')
		_diparm beta, label(beta) level(`level')

		if `corr'< -0.995  ///s2alpha is (virtually) 0 and SE and CI incalculable
		  di in smcl as txt %12s "s2alpha" "{col 14}{c |}"  ///
		  as res "{col 17}" %9.7g [s2alpha]_b[_cons] "{col 36}.{col 66}.{col 78}."
		else _diparm s2alpha beta,  ///
		  label(s2alpha) ci(log) noprob level(`level') f(@1) d(1 0)

		if `corr'> 0.995  /// s2theta is (virtually) 0 and SE and CI incalculable
		  di in smcl as txt %12s "s2theta" "{col 14}{c |}"  ///
		  as res "{col 17}" %9.7g [s2theta]_b[_cons] "{col 36}.{col 66}.{col 78}."
		else _diparm s2theta beta,  ///
		  label(s2theta) ci(log) noprob level(`level') f(@1) d(1 0)
		}

	/****** Se, Sp, DOR, LR+, LR- ******/
	_estimates unhold `biv'
	_estimates hold `biv', copy

	if "`summarypt'" == "" {
		RenamePost b V "muA:_cons muB:_cons s2A:_cons s2B:_cons rAB:_cons"
		di in smcl as txt "{hline 13}{c +}{hline 64}"
		di in smcl as res "Summary pt." as txt "{col 14}{c |}"
		_diparm muA   , label(Se) invlogit
		_diparm muB   , label(Sp) invlogit
		_diparm muA muB, label(DOR) ci(log) f( exp(@1+@2) ) d( exp(@1+@2) exp(@1+@2) )
		_diparm muA muB, label(LR+) ci(log) f( invlogit(@1)/(1-invlogit(@2)) ) ///
		  d( exp(@2-@1)*invlogit(@1)^2/invlogit(@2)  exp(@2)*invlogit(@1)  )
		_diparm muA muB, label(LR-) ci(log) f( (1-invlogit(@1))/invlogit(@2) ) ///
		  d( exp(-@1)*invlogit(@1)^2/invlogit(@2)  exp(-@1-@2)*invlogit(@1)  )
		_diparm muA muB, label(1/LR-) ci(log) f( invlogit(@2)/(1-invlogit(@1)) ) ///
		  d( exp(@1)/(1+exp(-@2))  exp(@1-@2)*(1+exp(-@1))/(1+exp(-@2))^2  )
		}

	di in smcl as txt "{hline 13}{c BT}{hline 64}"

	/* Cov(mu_A, mu_B) (needed to draw confidence and prediction regions) */
	if "`bivariate'" == "" {
		tempname vmu
		matrix `vmu' = e(V)
		di as txt "Covariance between estimates of E(logitSe) & E(logitSp)  "  ///
		  as res %9.0g `vmu'[1,2]
		}

	_estimates unhold `biv'


end

program define RenamePost, eclass
	tempname b V
	matrix `b' = e(`1')
	matrix `V' = e(`2')
	matrix colnames `b' = `3'
	matrix colnames `V' = `3'
	matrix rownames `V' = `3'
	ereturn post `b' `V'
end


	exit