/* Stata Macro -- Written by David B. Wilson
   Version 2021.05.05
   See help for masum */

program define masum, rclass
preserve
version 14.0

#delimit ;
syntax varlist(min=1 max=1 numeric) [if] [in],
   [var(string)] [se(string)] [w(string)] [print(string)]
   [model(string)] ;
#delimit cr

marksample touse
markout `touse'
tokenize `varlist'

tempvar _w _mes _k _df _sumw _sem _les _ues _min _max _q _pq _z _pz _I2 _v _neww
tempname model_type _minw _maxw tau2 vtypical _intercept Wdiag W X invXWX se_tau2

/* Errors */
if "`var'"=="" &  "`se'"=="" &  "`w'"=="" {
  di in error "Specify variance [var(varname)], standard error [se(varname)], "
  di in error "or inverse variance weight [w(varname)]."
  exit
}

if ("`var'"!="" &  "`se'"!="") |   ("`var'"!="" &  "`w'"!="") |   ("`se'"!="" &  "`w'"!="") {
  di in error "Only specify one of the following:"
  di in error "variance [var(varname)], standard error [se(varname)], "
  di in error "or inverse variance weight [w(varname)]."
  exit
}

if "`var'"!="" {
       qui g `_w' = 1/`var'
       qui g `_v' = `var'
   }
if  "`se'"!="" {
       qui g `_w' = 1/`se'^2
       qui g `_v' = `se'^2
   }
if "`w'"!="" {
       qui g `_w' = `w'
       qui g `_v' = 1/`w'
   }

/* Drop missing values */
if `1'==. | `_w'==. {
   qui drop if `1'==. | `_w'==.
   }

/* Set default model to REML */
local model = strupper("`model'")
if "`model'"!="FE" &  "`model'"!="DL" &  "`model'"!="HE" &  "`model'"!="HS" &  "`model'"!="SJ" &  "`model'"!="SJIT" &  "`model'"!="ML" &  "`model'"!="EB" {
   local model = "REML"
}

/* Set model to FE if only 1 effect size */
if _N == 1 {
  local model = "FE"
}

/* fixed (common) effect model */
/* always compute */
qui sum `1' if `touse' [aw = `_w']
scalar `_mes' = r(mean)
scalar `_k' = r(N)
scalar `_df' = `_k' - 1
scalar `_sumw' = r(sum_w)
scalar `_sem' = sqrt(1/`_sumw')
scalar `_les' = `_mes' -    1.95996*`_sem'
scalar `_ues' = `_mes' +    1.95996*`_sem'
scalar `_min' = r(min)
scalar `_max' = r(max)
scalar `_q' = r(Var)*((`_k'-1)/`_k')*`_sumw'
scalar `_pq' = chiprob(`_df',`_q')
scalar `_z' = `_mes'/`_sem'
scalar `_pz' = (1 - normprob(abs(`_z')))*2

/* determine random effects tau2 */
if "`model'" != "FE" {
   _matau2 `1' `_w' if `touse', model("`model'")
   scalar `tau2' = r(tau2)
   scalar `se_tau2' = r(se_tau2)
      if `tau2' < 0 {
         scalar `tau2' = 0
      }
   }

/* compute I^2 */
if "`model'" == "FE" | "`model'" == "DL" {
  scalar `_I2' = 100 * (`_q' - `_df')/`_q'
  }
if "`model'" != "FE" & "`model'" != "DL" {
  qui gen `_intercept' = 1
  mkmat `_intercept', matrix(`X')
  mkmat `_w', matrix(`W')
  matrix `Wdiag' = diag(`W')
  matrix `invXWX' =  syminv(`X'' * `Wdiag' * `X')
  matrix `vtypical' = (`_k' - 1)/trace(`Wdiag' - `Wdiag' * `X' * `invXWX' * `X'' * `Wdiag')
  scalar `_I2' = `tau2'/(`vtypical'[1,1] + `tau2') * 100
  }
if `_I2' < 0 {
  scalar `_I2' = 0
}

/* random effects model */
if "`model'"!="FE" {
    qui g `_neww' = 1/(`_v' + `tau2')
    qui sum `1' if `touse' [aw = `_neww']
    scalar `_mes' = r(mean)
    scalar `_sumw' = r(sum_w)
    scalar `_sem' = sqrt(1/`_sumw')
    scalar `_les' = `_mes' -    1.95996*`_sem'
    scalar `_ues' = `_mes' +    1.95996*`_sem'
    scalar `_min' = r(min)
    scalar `_max' = r(max)
    scalar `_z' = `_mes'/`_sem'
    scalar `_pz' = (1 - normprob(abs(`_z')))*2
}

/* determine min and max weights */
if "`model'"=="FE" {
  qui sum `_w'
  scalar `_minw' = r(min)
  scalar `_maxw' = r(max)
  scalar `_sumw' = r(sum)
  }
/* determine min and max weights */
if "`model'"!="FE" {
  qui sum `_neww'
  scalar `_minw' = r(min)
  scalar `_maxw' = r(max)
  scalar `_sumw' = r(sum)
  }

/* transform data if requested via Print statement */
local print = strupper("`print'")
if "`print'"=="EXP" | "`print'"=="IVZR" | "`print'"=="PROP" {
       scalar `_sem' = .
       foreach x in `_mes' `_les' `_ues' `_min' `_max' {
          if "`print'"=="EXP"  {
             scalar `x' = exp(`x')
             }
          if "`print'"=="IVZR" {
             scalar `x' = (exp(2*`x')-1)/(exp(2*`x')+1)
             }
          if "`print'"=="PROP" {
             scalar `x' = (exp(`x')/(exp(`x')+1))
             }
        }
}

if "`model'"=="FE" {
    local model_type = "Fixed (Common) Effect model"
    }
if "`model'"=="DL" {
    local model_type = "Random effects: Dersimonian-Laird"
    }
if "`model'"=="HS" {
    local model_type = "Random effects: Hunter-Schmidt"
    }
if "`model'"=="HE" {
    local model_type = "Random effects: Hedges"
    }
if "`model'"=="SJ" {
    local model_type = "Random effects: Sikik-Jonkman non-iterative"
    }
if "`model'"=="SJIT" {
    local model_type = "Random effects: Sikik-Jonkman iterative"
    }
if "`model'"=="ML" {
    local model_type = "Random effects: iterative full-information maximum likelihood"
    }
if "`model'"=="REML" {
    local model_type = "Random effects: iterative restricted maximum likelihood (default)"
    }
if "`model'"=="EB" {
    local model_type = "Random effects: iterative empirical bayes"
    }

di " "
di in text _col(1) "No. of obs  =" in result _col(21) %8.0f `_k'   /*
  */ in text _col(53) "Homogeneity Analysis"
di in text _col(1) "Minimum obs =" in result _col(21) %8.3f `_min' /*
  */ in text _col(59) "Q =" in result _col(64) %9.4f `_q'
di in text _col(1) "Maximum obs =" in result _col(21) %8.3f `_max' /*
  */ in text _col(58)"df =" in result _col(62) %11.0f `_df'
di in text _col(1) "Minimum weight =" in result _col(21) %8.4f `_minw' /*
  */ in text _col(59) "p =" in result _col(62) %11.5f `_pq'
di in text _col(1) "Maximum weight =" in result _col(21) %8.4f `_maxw' /*
  */ in text _col(57) "I^2 =" in result _col(67) %4.2f `_I2' "%"
if "`model'"!="FE" {
di in text _col(1) /*
  */ in text _col(55) "tau^2 =" in result _col(58) %11.5f `tau2'
di in text _col(1) /*
  */ in text _col(50) "se (tau^2) =" in result _col(58) %11.5f `se_tau2'
di in text _col(1) /*
  */ in text _col(57) "tau =" in result _col(58) %11.5f sqrt(`tau2')
  }

di in text _col(1) "--------------------------------------------------------------------------"
di in text _col(1) "                |    Mean    -95%CI    +95%CI        se         z        p"
di in text _col(1) "----------------+---------------------------------------------------------"
di in text _col(1) "Model " "`model' " in text _col(17) "|" in result _col(16) %8.4f `_mes'  _col(28) %8.4f `_les' /*
  */  _col(38) %8.4f `_ues'  _col(48) %8.4f `_sem'  _col(58) %8.4f `_z'  _col(69) %6.4f `_pz'
di in text _col(1) "--------------------------------------------------------------------------"
di in text _col(1) "`model_type'"

if "`print'"=="EXP" {
   di in text _col(1) /*
   */ "Results are the exponent of computed values (i.e., results are odds-ratios)"
}
if "`print'"=="IVZR" {
   di in text _col(1) /*
   */ "Results are inverse Fisher Zr transformed (i.e., results are correlations)"
}
if "`print'"=="PROP" {
   di in text _col(1) /*
   */ "Results are inverse logits (i.e., results are proportions)"
}

di " "
di  in gr "Version 2021.04.18 of masum written by David B. Wilson"

/* return list */
return scalar I2    = `_I2'
return scalar mean  = `_mes'
return scalar lci   = `_les'
return scalar uci   = `_ues'
return scalar Q     = `_q'
return scalar pQ    = `_pq'
return scalar se    = `_sem'
return scalar z     = `_z'
return scalar pz    = `_pz'
return scalar k     = `_k'
return scalar df     = `_df'
if "`model'"!="FE" {
    return scalar tau2  = `tau2'
    }

restore
end