*! version 0.1.1 25jul2012

/*
History
MJC 25jul2012: version 0.1.1 - fixed local name clash in sens/spec var_sens and var_spec
							 - now using rbinomial() instead of rndbin() so seed will work
MJC 18jul2012: version 0.1.0
*/

program define metasim
	version 11.2
	syntax varlist(min=4 max=6 numeric), 											///
												N(integer) 							/// -number of subjects-
												ES(numlist min=1 max=2)     		/// -pooled estimate-
												VAR(numlist min=1 max=2) 			/// -variance/s for es-
												TYpe(string) 						/// -type of study: clinical or diagnostic-
																					///
											[										///
												MEASure(string) 					/// -or/rr/rd/nostandard/d/ss-
												P(real 0)							/// -event rate in control group / prob being disease in positive group-
												R(real 1) 							/// -ratio of number of subjects in each group-
												STudies(integer 1) 					/// -number of new studies to be generated-
												MODel(string)						/// -type of meta-analysis-
												TAUsq(numlist min=1 max=2)  		/// -between study variation-
												DIST(string)						/// -type of distribution: (normal/t)-
												CORR(real 0)						/// -correlation between logit(sens) and logit(spec)-
											]

	//===============================================================================================================================================================//
	// Error checks and defaults

		capture which metan
		if _rc>0 {
			display as error "You need to install the command metan. This can be installed using,"
			display as error ". {stata ssc install metan}"
			exit 198
		}

		if "`type'"=="diagnostic" {
			capture which metandi
			if _rc>0 {
				display as error "You need to install the command metandi. This can be installed using,"
				display as error ". {stata ssc install metandi}"
				exit 198
			}
			capture which midas
			if _rc>0 {
				display as error "You need to install the command midas. This can be installed using,"
				display as error ". {stata ssc install midas}"
				exit 198
			}
		}

		/* Set defaults */

		if "`model'"=="" & "`measure'"!="nostandard" {
			local model "fixed"
		}

		if "`model'" == "" & "`measure'"=="nostandard" {
			local model "fixedi"
		}

		if "`model'"=="random" | "`model'"=="randomi" | "`model'"=="bivariate" {
			if "`tausq'"=="" & "`measure'"!="ss" {
				local tausq = 0
				di in green "Warning: tausq has been assumed to be 0 as it has not been specified by the user"
			}
			if "`tausq'"=="" & "`measure'"=="ss" {
				local tausq_sens = 0
				local tausq_spec = 0
				di in green "Warning: tausq has been assumed to be 0 for both sensitivity and specificity as it has not been specified by the user"
			}
		}

		if "`dist'" == "" & ("`model'"=="random" | "`model'"=="randomi") {
			local dist "t"
		}

		if "`dist'"=="" & ("`model'"=="fixed" | "`model'"=="fixedi" | "`model'"=="peto" | "`model'"=="bivariate") {
			local dist "normal"
		}


		*** Check user hasn't specified options that don't exist ***
		if ("`model'"!="fixed" & "`model'"!="fixedi" & "`model'"!="random" & "`model'"!="randomi" & "`model'"!="bivariate") {
			di as err "Unknown model specified"
			exit 198
		}

		if ("`type'"!="clinical" & "`type'"!="diagnostic") {
			di as err "Unknown type specified"
			exit 198
		}

		if ("`measure'"!="or" & "`measure'"!="rr" & "`measure'"!="rd" & "`measure'"!="nostandard" & "`measure'"!="dor" & "`measure'"!="ss") {
			di as err "Unknown measure specified"
			exit 198
		}


		/* Count number of estimates specified in numlist variables to determine if using sensitivity and specificity */
		local nes : word count `es'
		if `nes' > 1 {
			local es_sens : word 1 of `es'
			local es_spec : word 2 of `es'
		}

		if ("`measure'"!="ss" & `nes'>1) {
			di as err "Can not specify more than one estimate unless using sensitivity and specificity as measures for a diagnostic study"
			exit 198
		}

		if ("`measure'"=="ss" & `nes'<2) {
			di as err "Must specify an estimate value for both sensitivity and specificity"
			exit 198
		}

		local nse : word count `var'
		if ("`nse'"=="1") {
			if (`var'<0) {
				di as err "Cannot specify a negative variance"
				exit 198
			}
		}

		if "`nse'"=="2" {
			local var_sens : word 1 of `var'
			local var_spec : word 2 of `var'
			if (`var_sens'<0 | `var_spec'<0) {
				di as err "Cannot specify a negative variance"
			}
		}


		if ("`measure'"!="ss" & `nse'==2) {
			di as err "Can not specify more than one variance estimate unless using sensitivity and specificity as measures for a diagnostic study"
			exit 198
		}

		if ("`measure'" == "ss" & `nse'<2) {
			di as err "Must specify an variance value for both sensitivity and specificity"
			exit 198
		}

		if "`tausq'" != "" {
			local ntau : word count `tausq'
			if `ntau' > 1 {
				local tausq_sens : word 1 of `tausq'
				local tausq_spec : word 2 of `tausq'
			}

			if "`measure'"!="ss" & `ntau'>1 {
				di as err "Can not specify more than one tausq value unless using sensitivity and specificity as measures for a diagnostic study"
				exit 198
			}

			if ("`measure'"=="ss" & `ntau'<2) {
				di as err "Must specify an tausq value for both sensitivity and specificity"
				exit 198
			}
		}

		if "`type'"=="clinical" & ("`measure'"=="dor" | "`measure'"=="ss") {
			di as err "Can only use DOR or sensitivity and specificity when simulating a diagnostic study"
			exit 198
		}

		if "`type'"=="diagnostic" & ("`measure'"=="or" | "`measure'"=="rr" | "`measure'"=="rd" | "`measure'"=="nostandard") {
			di as err "Can only use DOR or sensitivity and specificity when simulating a diagnostic study"
			exit 198
		}

		if "`type'"=="clinical" & "`model'"=="bivariate" {
			di as err "Can only use the bivariate model when simulating a diagnostic study using sensitivity and specificity"
			exit 198
		}

		if "`dist'"=="t" & ("`model'"=="fixed" | "`model'"=="fixedi" | "`model'"=="peto" | "`model'"=="bivariate") {
			di as err "Can only use the t-distribution to sample a new study when using the random or randomi models"
			exit 198
		}

		local number = _N
		if "`dist'"=="t" & `number'<3 {
			di as err "Can only use the t-distribution when there are 3 or more studies in the current dataset"
			exit 198
		}

		if "`model'"=="peto" & ("`measure'"=="rr" | "`measure'"=="rd" | "`measure'"=="nostandard" | "`measure'"=="ss") {
			di as err "The Peto method can only be used with OR or DOR"
			exit 198
		}

		if "`model'"=="bivariate" & "`corr'"=="0" {
			di in green "Warning: correlation between logit(sensitivity) and logit(specificity) has been set to 0"
		}

	//===============================================================================================================================================================//
	// Prep

	preserve

		tokenize `varlist'

		qui sum `1', meanonly
		if `r(N)'==0 {
			di as err "Current data set not found"
			exit 198
		}
		qui sum `2', meanonly
		if r(N)==0 {
			di as err "Current data set not found"
			exit 198
		}
		qui sum `3', meanonly
		if r(N)==0 {
			di as err "Current data set not found"
			exit 198
		}
		qui sum `4', meanonly
		if r(N)==0 {
			di as err "Current data set not found"
			exit 198
		}

		if "`type'" == "clinical" {
			if  "`6'"=="" & "`measure'" == "" {
				local measure "rr"
			}
			else if "`6'"!="" & "`measure'" == "" {
				local measure "nostandard"
			}
		}

		if "`type'" == "diagnostic" & "`measure'" == "" {
			local measure = "ss"
		}

		if "`6'"!="" & "`measure'"!= "nostandard" {
			di as err "Can only input 6 values when using nostandard as measure"
			exit 198
		}

		/* continuity correction to studies with no events */
		if "`type'"=="clinical" {
			if "`p'"=="0" & "`6'"=="" {
				tempvar h t p1
				quietly {
					gen `h'=`3'
					gen `t'=`3'+`4'
					replace `t' = `t' + 1 if `h'==0
					recode `h' 0 = 0.5
					gen `p1' = `h' / `t'
					sum `p1', meanonly
					local p = `r(mean)'
					global p = `p'
				}
			}
		}

		if "`type'"=="diagnostic" {
			if "`p'"=="0" {
				quietly {
					tempvar h t p1
					gen `h' = `1'
					gen `t' = `1'+`2'
					replace `t' = `t' + 1 if `h'==0
					recode `h' 0 = 0.5
					gen `p1' = `h'/`t'
					sum `p1', meanonly
					local p = `r(mean)'
					global p = `p'
				}
			}
		}

		if "`dist'"=="t" {
			*** Count the degrees of freedom for the t-distribution. ***
			qui sum `1' if `1'!=., meanonly
			local N = `r(N)'
			local df = `N'-2
			if `N'<3 {
				di as err "Not enough studies to accurately estimate the predictive distribution under the random effects assumption (need 3 or more studies)"
				exit 198
			}
		}

	/*** Postfile declares the filename of a new Stata dataset "temppow". ***/
	/*** "Samp" will contain new study results from each simulation.      ***/

		tempname samp
		postfile `samp' `varlist' using temppow, replace

		/*** Simulate new clinical study ***/
		if "`type'" == "clinical" {

			/*** Calculate the average mean difference and SD in the control group ***/
			if "`6'"!="" {
				qui su `5', meanonly
				local meanc = `r(mean)'
				global meanc = `meanc'
				qui su `6', meanonly
				local stdevc = `r(mean)'
				global stdevc = `stdevc'
			}

			forvalues i = 1/`studies' {

				*** Clear the data memory ***

				drop _all

				*** Create a local macro for the standard error depending on ***
				*** whether the analysis is fixed effect or random effect    ***

				if "`model'" == "random" | "`model'" == "randomi" {
					local std_err = sqrt(`tausq' + (`var'))
				}
				else {
					local std_err = sqrt(`var')
				}


				*** Sample from either the t-distribution or the normal distribution ***
				*** Create a local macro called mu.  Sample from the Normal/t    ***
				*** distribution and calculate the resulting estimate.           ***
				*** If measure is on log scale (OR/RR) then exponentiate result	 ***

				if "`dist'"=="normal" {
					local rnormdraw = rnormal(0,`std_err')
					if "`measure'" == "or" | "`measure'" == "rr" {
						local mu = exp(`es' + `rnormdraw')
					}
					else {
						local mu = (`es' + `rnormdraw')
					}
				}
				else if "`dist'"=="t" {
					local rand=invttail(`df', runiform())
					if "`measure'" == "or" | "`measure'" == "rr" {
						local mu = exp(`es' + (`std_err'*`rand'))
					}
					else{
						local mu = `es' + (`std_err'*`rand')
					}
				}

				*** Set the number of observations in the dataset to be the          ***
				*** number of patients in the control group (defined in program call)***

				qui set obs `n'

				*** BINARY DATA - when `6' is empty					             ***
				*** Randomly sample from the binomial distribution N=n, Prob=p   ***
				*** xb=1 for event, xb=0 for no event                            ***

				if "`6'" == "" {
					qui gen byte xb = rbinomial(1,`p')
				}
				*** CONTINUOUS DATA - when `6' is populated			             ***
				*** Randomly sample from the normal distribution 		         ***
				else if "`6'" != "" {
					qui drawnorm xb, n(`n') means(`meanc') sds(`stdevc')
				}

				*** BINARY DATA								                      ***
				*** Create a local macro called ec counting the number of events. ***
				*** ec represents the number of events in the control group of    ***
				*** the new study.							                      ***

				if "`6'" == "" {
					qui count if xb==1
					local ec=`r(N)'
				}
				*** CONTINUOUS DATA 							                  ***
				*** Create local macros recording the mean and standard deviation ***
				*** for the simulated data						                  ***
				else if "`6'" != "" {
					qui summ xb
					local mcn=`r(mean)'
					local sdcn=`r(sd)'
				}

				*** delete the xb variable   ***
				drop xb

				*** Estimate the number of events in the treatment group     ***
				*** save the result in a local macro called q or meant       ***
				*** This calculation varies depending on the outcome measure ***

				if "`measure'" == "rr" {
					local q=`p'*`mu'
				}
				else if "`measure'" == "or" {
					local q = (`mu'*`p'/(1-`p'))/(1+(`mu'*`p'/(1-`p')))
				}
				else if "`measure'" == "rd" {
					local q=`p'-`mu'
				}
				else if "`measure'"=="nostandard" {
					local meant=`mcn' + `mu'
				}

				*** Calculate the number of patients in the treatment group        ***

				local m=`n'*`r'

				*** Set the number of observations in the dataset to be the     ***
				*** number of patients in the treatment group 				    ***

				qui drop _all
				qui set obs `m'

				*** BINARY DATA 									               ***
				*** Randomly sample from the binomial distribution N=m, Prob=q     ***
				*** Count the number of events and save in a local macro called et ***

				if "`6'"=="" {
					qui gen byte xb = rbinomial(1,`q')
					qui count if xb==1
					local et=r(N)
				}

				*** CONTINUOUS DATA								                   ***
				*** Randomly sample from the normal distribution       	           ***
				*** Assume standard deviation is equal in both groups			   ***
				*** Save results into local macros						           ***

				else if "`6'" != "" {
					qui drawnorm xb, n(`m') means(`meant') sds(`stdevc')
					qui summ xb
					local mtn=r(mean)
					local sdtn=r(sd)
				}

				*** BINARY DATA 									                ***
				*** Create local macros calles net nec containing the number of     ***
				*** patients who did not have an event in the treatment and control ***
				*** groups of the new study									        ***

				if "`6'" == "" {
					local nec=`n'-`ec'
					local net=`m'-`et'
				}

				*** Add the local macros to buff and close the posting to buff ***
				*** This data will be saved in a dataset called temppow        ***

				if "`6'" == "" {
					qui post `samp' (`et') (`net') (`ec') (`nec')
				}

				else if "`6'" != "" {
					qui post `samp' (`m') (`mtn') (`sdtn') (`n') (`mcn') (`sdcn')
				}
			}
		}

		/*** Simulate new diagnostic study ***/
		if "`type'" == "diagnostic" {

			if "`model'"=="bivariate" & "measure'"=="dor" {
				di as err "Can only use sensitivity and specificity with bivariate model"
				exit 198
			}

			forvalues i = 1/`studies' {

				*** Clear the data memory ***
				drop _all

				*** Create local macros for when using ss as measure of accuracy. ***

				if "`measure'"== "ss" {

					if "`model'"=="bivariate" & "`dist'"=="t" {
						di as err "Can not use t-distribution to sample a new study using bivariate meta-analysis"
						exit 198
					}

					*** Create local macros depending on type of model (fixed/random/bivariate) and type of distribution (normal/t). ***
					if "`dist'" == "t" {
						local std_err_sens = sqrt(`tausq_sens' + `var_sens')
						local std_err_spec = sqrt(`tausq_spec' + `var_spec')
						*** Sample from the t-distribution. ***
						local rand = invttail(`df', runiform())
						local sens = invlogit(`es_sens' + (`std_err_sens'*`rand'))
						local spec = invlogit(`es_spec' + (`std_err_spec'*`rand'))
					}
					else if "`dist'" == "normal" {

						*** Specify the matrix for estimates of sens and spec. ***
						matrix m = (`es_sens', `es_spec')

						if "`model'"=="fixed" | "`model'"=="fixedi" {
							local var_sens_new = `var_sens'
							local var_spec_new = `var_spec'
						}
						else {
							local var_sens_new = `var_sens'+`tausq_sens'
							local var_spec_new = `var_spec'+`tausq_spec'
						}

						*** Specify the variance-covariance matrix for sens and spec. ***

						matrix C = (1, `corr', 1)
						local sd1 = sqrt(`var_sens_new')
						local sd2 = sqrt(`var_spec_new')
						matrix sd = (`sd1', `sd2')

						*** Sample from the multivariate normal distribution. ***
						qui drawnorm logsens logspec, n(`n') means(m) corr(C) sd(sd) cstorage(lower)
						local sens=invlogit(logsens)
						local spec=invlogit(logspec)

					}

					*** Set the number of obs to be the number of diseased patients (n). ***

					qui set obs `n'

					*** Randomly sample from the binomial distribution N=n, Prob=sens. ***
					*** If xb=1 then TP result, if xb=0 then FN result.                ***

					qui gen byte xb = rbinomial(1,`sens')

					*** Create a local macro called rtp counting the number of TP. ***

					qui count if xb==1
					local rtp=r(N)

					drop xb

					*** Calculate the number of healthy patients. ***

					local m=`n'*`r'

					qui drop _all

					*** Set the number of obs to be the number of healthy patients (m). ***

					qui set obs `m'

					*** Randomly sample from the binomial distribution N=m, Prob=spec. ***
					*** If xb=1 then TN result, if xb=0 then FP result.                ***

					qui gen byte xb = rbinomial(1,`spec')

					*** Create a local macro called rtn counting the number of TN. ***

					qui count if xb==1
					local rtn=r(N)

					*** Create local macros containing number of FN and FP test results. ***

					local rfn=`n'-`rtp'
					local rfp=`m'-`rtn'
				}
				else if "`measure'"=="dor" {

					if "`model'" == "random" | "`model'" == "randomi" {
						local std_err = sqrt(`tausq' + (`var'))
					}
					else {
						local std_err = sqrt(`var')
					}

					if "`dist'" == "t" {
						local rand=invttail(`df', runiform())
						local dor = exp(`es' + (`std_err'*`rand'))
					}
					else if "`dist'" == "normal" {
						qui drawnorm logdor, n(`n') means(`es') sds(`std_err')
						local dor=exp(logdor)
					}

					*** Set the number of obs to be the number of positive test results (n). ***

					qui set obs `n'

					*** Randomly sample from the binomial distribution N=n, Prob=p. ***

					qui gen byte xb = rbinomial(1,`p')

					*** Create a local macro called rtp counting the number of diseased patients. ***
					***If xb=0 then patient is healthy, if xb=1 then patient is diseased.        ***

					qui count if xb==1
					local rtp=r(N)

					drop xb

					*** Calculate probability of being diseased given a negative result ***
					*** using pp and dor estimate.                                      ***

					local q = (`p'/(1-`p'))/(`dor'+(`p'/(1-`p')))

					*** Calculate the number of patients with negative test results. ***

					local m = `n'*`r'

					qui drop _all

					*** Set the number of obs to be the number of negative test results (m). ***

					qui set obs `m'

					*** Randomly sample from the binomial distribution N=m, Prob=q. ***

					qui gen byte xb = rbinomial(1,`q')

					*** Create a local macro called rfn counting the number of diseased patients. ***
					***If xb=0 then patient is healthy, if xb=1 then patient is diseased.        ***

					qui count if xb==1
					local rfn = `r(N)'

					*** Create local macros containing number of FN and FP test results. ***

					local rfp=`n'-`rtp'
					local rtn=`m'-`rfn'

				}

				*** Add a continuity correction to simulated data set if any values are 0. ***
				local zeros = (`rtp'==0 | `rfp'==0 | `rfn'==0 | `rtn'==0 )
				if `zeros'==1 {
					local rtp = `rtp' + 0.5
					local rfp = `rfp' + 0.5
					local rfn = `rfn' + 0.5
					local rtn = `rtn' + 0.5
				}

				qui post `samp' (`rtp') (`rfp') (`rfn') (`rtn')
			}
		}

	/* Close postfile temppow */
	qui postclose `samp'
	restore
	local dir `c(pwd)'
	display in green "New study/studies simulated are saved in file called `dir'\temppow"

end