*! 1.3.0 Ariel Linden 12feb2024 // replaced 'bonferroni' option with 'madjust' which adjusts using both bonferroni and FDR // provided for an additional pval (total of three available now) *! 1.2.0 Ariel Linden 30jan2024 // added Bonferroni adjustment option *! 1.1.0 Ariel Linden 23jan2024 // added exact stats for smr (cross-sectional) *! 1.0.0 Ariel Linden 21dec2023 capture program drop funnelinst program define funnelinst, rclass version 11.0 syntax anything [if][in] [, /// TARget(numlist max=2 sort) /// target with max of two values CHType(string) /// change type for prop/rate subcommands. 4 vars in the syntax indicates "change" WINsor(real 0) /// percent winsored, default 0 winsoring OVERdisp(string) /// additive (add) or multiplicative (mult) overdispersion NPOints(real 200) /// number of points in the funnel plot denominator XRangehigh(numlist max=1) /// highest value in the funnel plot denominator YPERcent /// multiply Y by 100 for proportion RATEdenom(numlist max=1) /// multiply Y by specified amount and X by specified amount for rate LOGtrans /// log tranformation for prop/rate and SMR ARCsintrans /// arc sin transformation for prop/rate and SMR SQRttrans /// square root tranformation for SMR EXact /// offers exact estimation (works currently for prop/rate for cross-sectional and smr for change data) PVal(numlist max=3 sort) /// p-values used as basis to generate CIs M(numlist max=1) /// not documented. Multiplies Y and CIs by a specified amount to scale the data. MADJust /// present Bonferroni and FDR adjusted CIs YMIn(numlist max=1) /// truncate values below specified min on Y axis YMAx(numlist max=1) /// truncate values above specified mqax on Y axis SAVing(string asis) /// save CIs to a new file FIGure(str asis) /// allows figure options ] quietly { gettoken estype anything : anything, parse(" ") local lcmd = length("`estype'") if !inlist("`estype'", substr("smr", 1, max(3,`lcmd')), substr("prop", 1, max(4,`lcmd')), substr("rate", 1, max(4,`lcmd')), substr("mean", 1, max(4,`lcmd'))) { di as err `"unknown subcommand of {bf:funnelinst}: `estype'"' exit 198 } tokenize `anything' local varcnt : word count `anything' if !inlist(`varcnt', 2, 4) { noi di as err "either two or four variables must be specified" exit 198 } if `varcnt' == 2 { local num `1' local den `2' confirm numeric variable `num' confirm numeric variable `den' } if `varcnt' == 4 { local num1 `1' local den1 `2' local num2 `3' local den2 `4' confirm numeric variable `num1' confirm numeric variable `den1' confirm numeric variable `num2' confirm numeric variable `den2' } preserve marksample touse keep if `touse' // return _N to the original _N in case funnelinst has been run previously count local cnt = r(N) sum `num' local nobs = r(N) if `nobs' < `cnt' { local nobs = `nobs' + 1 drop in `nobs' / `cnt' } if ("`ypercent'" != "") & ("`ratedenom'" != "") { noi di as err "ypercent and ratedenom cannot be specified together" exit 198 } if ("`logtrans'" != "") & ("`arcsintrans'" != "") { noi di as err "logtrans and arcsintrans cannot be specified together" exit 198 } if ("`logtrans'" != "") & ("`sqrttrans'" != "") { noi di as err "logtrans and sqrttrans cannot be specified together" exit 198 } // default p-values if ("`pval'" == "") & ("`madjust'" == "") { local pval 0.001 0.025 } local pcnt : word count `pval' ************ // tempvars ************ * variables used in every case tempvar rho y varY z zwins rhopoints rhopoints2 varplot msum b1a1 b2a2 /// y1 y2 se se1 se2 y_SE2 one_se2 w w2 n1 n2 pibar o1 e1 o2 e2 /// meanSMR g qbinom qpois c x ********************* // Setting targets ********************* // If target(s) are specified if ("`target'" != "") { local tarcnt: word count `target' // when a range is specified if `tarcnt' > 1 { // extract lower and upper range values local range1: word 1 of `target' local range2: word 2 of `target' } // end range specified } // end target specified if inlist("`estype'", substr("prop", 1, max(4,`lcmd')), substr("rate", 1, max(4,`lcmd'))) { if (`varcnt' == 2) { if ("`target'" == "") | ("`range1'" != "") { sum `num', meanonly local maxnum = r(sum) sum `den', meanonly local maxden = r(sum) local target = (`maxnum' / `maxden') } // end no target } if (`varcnt' == 4) { if inlist("`estype'", substr("prop", 1, max(4,`lcmd')), substr("rate", 1, max(4,`lcmd'))) { // diff if ("`chtype'" == "diff") | ("`chtype'" == "") { if ("`target'" == "") | ("`range1'" != "") { sum `num1', meanonly local maxnum1 = r(sum) sum `den1', meanonly local maxden1 = r(sum) sum `num2', meanonly local maxnum2 = r(sum) sum `den2', meanonly local maxden2 = r(sum) local target = (`maxnum2' / `maxden2') - (`maxnum1' / `maxden1') } // end no target } // ratio if ("`chtype'" == "ratio") { if ("`target'" == "") | ("`range1'" != "") { sum `num1', meanonly local maxnum1 = r(sum) sum `den1', meanonly local maxden1 = r(sum) sum `num2', meanonly local maxnum2 = r(sum) sum `den2', meanonly local maxden2 = r(sum) local target = (`maxnum2' / `maxden2') / (`maxnum1' / `maxden1') } // end no target } // or if ("`chtype'" == "or") { if ("`target'" == "") | ("`range1'" != "") { gen `b1a1' = `den1' - `num1' sum `b1a1', meanonly local maxb1a1 = r(sum) gen `b2a2' = `den2' - `num2' sum `b2a2', meanonly local maxb2a2 = r(sum) sum `num1', meanonly local maxnum1 = r(sum) sum `num2', meanonly local maxnum2 = r(sum) local target = (`maxnum2' / `maxb2a2') / (`maxnum1' / `maxb1a1') } // end no target } // end chtype = or } // end inlist prop/rate } // end change prop/rate } // end target prop/rate if inlist("`estype'", substr("smr", 1, max(3,`lcmd'))) { if (`varcnt' == 2) { if ("`target'" == "") | ("`range1'" != "") { sum `num', meanonly local maxnum = r(sum) sum `den', meanonly local maxden = r(sum) local target = (`maxnum' / `maxden') } // end no target } // end no change for smr if (`varcnt' == 4) { if ("`target'" == "") | ("`range1'" != "") { sum `num1', meanonly local maxnum1 = r(sum) sum `den1', meanonly local maxden1 = r(sum) sum `num2', meanonly local maxnum2 = r(sum) sum `den2', meanonly local maxden2 = r(sum) local target = (`maxnum2' / `maxden2') / (`maxnum1' / `maxden1') } // end no target } // end change for smr } // end target smr if inlist("`estype'", substr("mean", 1, max(4,`lcmd'))) { if (`varcnt' == 2) { gen `rho' = 1 / `den'^2 gen `y' = `num' if ("`target'" == "") | ("`range1'" != "") { sum `rho', meanonly local rhosum = r(sum) gen `msum' = `y' / `den'^2 sum `msum', meanonly local maxsum = r(sum) local target = (`maxsum' / `rhosum') } // end no target } // end mean no change if (`varcnt' == 4) { gen `y1' = `num1' gen `y2' = `num2' gen `se1' = `den1' gen `se2' = `den2' gen `y' = `y2' - `y1' gen `se' = sqrt(`se1'^2 + `se2'^2) gen `rho' = 1 / `se'^2 if ("`target'" == "") | ("`range1'" != "") { gen `y_SE2' = `y' / `se'^2 sum `y_SE2', meanonly local y_SE2sum = r(sum) gen `one_se2' = 1 / `se'^2 sum `one_se2', meanonly local one_se2sum = r(sum) local target = (`y_SE2sum' / `one_se2sum') } // end no target } // end mean with change } // end target mean ***************************************************************** // generate Y and varY depending on datatype and transformation ***************************************************************** // prop and rate if inlist("`estype'", substr("prop", 1, max(4,`lcmd')), substr("rate", 1, max(4,`lcmd'))) { if (`varcnt' == 2) { gen `rho' = `den' if "`logtrans'" != "" { gen `y' = log((`num' + 0.5)/(`den' - `num' + 0.5)) // empirical logit gen `varY' = 1/(`target' * (1 - `target') * `rho') } if "`arcsintrans'" != "" { gen `y' = asin(sqrt(`num'/`den')) gen `varY' = 1/(4 * `rho') } else if ("`logtrans'" == "") & ("`arcsintrans'" == "") { gen `y' = `num' / `den' gen `varY' = `target' * (1 - `target')/ `rho' } // end no logtrans or arcsintrans } // end no change if (`varcnt' == 4) { gen `y1' = `num1' / `den1' gen `y2' = `num2' / `den2' gen `n1' = `den1' gen `n2' = `den2' if ("`chtype'" == "diff") | ("`chtype'" == "") { gen `y' = `y2' -`y1' gen `pibar' = (`y1' + `y2') / 2 gen `varY' = ((`pibar' + `target' / 2) * (1 - `pibar' - `target' / 2))/ `n1' + ((`pibar' - `target' / 2) * (1 - `pibar' + `target' / 2)) / `n2' * set `rho' as rough sample size per group sum `pibar', meanonly local mu_pibar = r(mean) gen `rho' = 2 * `mu_pibar' * (1 - `mu_pibar') / `varY' } // end chtype "diff" if ("`chtype'" == "ratio") { * work on log scale local logtrans logtrans gen `y' = log(((`num2' + 0.5) / (`n2' + 0.5)) / ((`num1' + 0.5) / (`n1' + 0.5))) gen `pibar' = sqrt(`y1' * `y2') gen `varY' = (sqrt(`target') - `pibar') / (`pibar' * `n1') + (sqrt(1 / `target') - `pibar') / (`pibar' * `n2') * set `rho' as rough sample size per group sum `pibar', meanonly local mu_pibar = r(mean) local g = (sqrt(`target') + sqrt(1 / `target') - 2 * `mu_pibar') / `mu_pibar' gen `rho' = `g' / `varY' local target = log(`target') } // end chtype "ratio" if ("`chtype'" == "or") { * work on log scale local logtrans logtrans local target = log(`target') gen `y' = log(((`num2' + 0.5) / (`n2' - `num2' + 0.5)) / ((`num1' + 0.5) / (`n1' - `num1' + 0.5))) gen `pibar' = (`y1' + `y2') / 2 gen `varY' = 1 / (`num1' + 0.5) + 1 / (`n1' - `num1' + 0.5) + 1 / (`num2' + 0.5) + 1 / (`n2' - `num2' + 0.5) * set `rho' as rough sample size per group sum `pibar', meanonly local mu_pibar = r(mean) gen `rho' = 2 / (`varY' * `mu_pibar' * (1 - `mu_pibar')) } // end chtype "or" } // end change } // end `y' and `varY' for prop/rate // smr if inlist("`estype'", substr("smr", 1, max(3,`lcmd'))) { if (`varcnt' == 2) { gen `rho' = `den' if "`logtrans'" != "" { gen `c' = min(0.5, `den'/2) gen `y' = log((`num' + `c')/`den') gen `varY' = `target' / `rho' } if "`sqrttrans'" != "" { gen `y' = sqrt(`num' / `den') gen `varY' = `target'/(4 * `rho') } else if ("`logtrans'" == "") & ("`sqrttrans'" == "") { gen `y' = `num' / `den' gen `varY' = `target' / `rho' } // end no logtrans or sqrttrans } // end no change if (`varcnt' == 4) { * work on log scale local logtrans logtrans gen `o1' = `num1' gen `e1' = `den1' gen `o2' = `num2' gen `e2' = `den2' replace `o1' = (`o2' / 10) if `o1' == 0 replace `o2' = (`o1' / 10) if `o2' == 0 gen `y' = log((`o2' / `e2') / (`o1' / `e1')) gen `meanSMR' = (`o1' + `o2') / (sqrt(`target') * `e2' + `e1' / sqrt(`target')) sum `meanSMR', meanonly local mu_meanSMR = r(mean) gen `varY' = (1 / (sqrt(`target') * `e2') + 1 / (`e1' / sqrt(`target'))) / `meanSMR' gen `g' = (sqrt(`target') + 1 / sqrt(`target')) / `mu_meanSMR' local target = log(`target') gen `rho' = `g' / `varY' } // end change smr } // end smr // mean if inlist("`estype'", substr("mean", 1, max(4,`lcmd'))) { // All other variables are created under "target" block above gen `varY' = 1 / `rho' } // end "mean" ******************** // naive Z scores ******************** gen `z' = (`y' - `target')/sqrt(`varY') **************************************************************************** // compute phi (the dispersion %) and tau (when overdispersion is additive) **************************************************************************** if ("`overdisp'" != "") { ********************* // Winsorize Z scores ********************* if `winsor' >= 50 { noi di as err "winsor(#) must be a value less that 50" exit 198 } gen `zwins' = . _winsor , z(`z') zwins(`zwins') winsor(`winsor') nobs(`nobs') sum `zwins' local phi = r(Var) * "phisig" if `phi' < 1 + 2 * sqrt(2/`nobs') { local phi = 1 } local phipct = round(100*(sqrt(`phi')-1),1) } // end phi if ("`overdisp'" == "add") { local q = `phi' * `nobs' gen `w' = 1/`varY' sum `w' local wsum = r(sum) gen `w2' = `w'^2 sum `w2' local wsum2 = r(sum) local a = `wsum2' / `wsum' local denom = `wsum' - `a' local tau = sqrt((`q'-(`nobs'- 1)) / `denom') local tau2 = `tau'^2 } // end add else if ("`overdisp'" != "add") { local tau2 = 0 } ************************************************** // generate variables for CIs (rhopoints, varplot) ************************************************** * check that Npoints are <= than current obs before setting obs larger if `nobs' < `npoints' { set obs `npoints' } if ("`xrangehigh'" == "") { sum `rho', meanonly local xrangehigh = ceil(r(max) * 1.01) } local xrangelow = 0 gen double `rhopoints' = 0 forvalues i = 1/`npoints' { replace `rhopoints' = round(`xrangelow' + (`i' * (`xrangehigh' - `xrangelow')) / `npoints', .1) in `i' } replace `rhopoints' = . if `rhopoints'==0 // prop and rate if inlist("`estype'", substr("prop", 1, max(4,`lcmd')), substr("rate", 1, max(4,`lcmd'))) { if (`varcnt' == 2) { if ("`logtrans'" != "") { gen `varplot' = 1/(`rhopoints' * `target' * (1 - `target')) local target = log(`target'/(1 - `target')) } // end logtrans if "`arcsintrans'" != "" { gen `varplot' = 1/(4 * `rhopoints') local target = asin(sqrt(`target')) } else if ("`logtrans'" == "") & ("`arcsintrans'" == "") { gen `varplot' = `target' * (1 - `target')/ `rhopoints' } // end not logtrans or arcsintrans } // end no change prop/rate if (`varcnt' == 4) { if ("`chtype'" == "diff") | ("`chtype'" == "") { gen `varplot' = 2 * `mu_pibar' * (1 - `mu_pibar') / `rhopoints' } // end diff if ("`chtype'" == "ratio") { local logtrans logtrans gen `varplot' = `g' / `rhopoints' } // end ratio if ("`chtype'" == "or") { local logtrans logtrans gen `varplot' = 2 / (`rhopoints' * `mu_pibar' * (1 - `mu_pibar')) } // end or } // end change prop/rate } // prop/rate // smr if inlist("`estype'", substr("smr", 1, max(3,`lcmd'))) { if (`varcnt' == 2) { if ("`logtrans'" != "") { gen `varplot' = `target' / `rhopoints' local target = log(`target') } if ("`sqrttrans'" != "") { gen `varplot' = `target' / (4 * `rhopoints') // ok for target == 1!!! local target = sqrt(`target') } else if ("`logtrans'" == "") & ("`sqrttrans'" == "") { gen `varplot' = `target' / `rhopoints' } } // end no change if (`varcnt' == 4) { local logtrans logtrans gen `varplot' = `g' / `rhopoints' } // end change } // end smr // mean if inlist("`estype'", substr("mean", 1, max(4,`lcmd'))) { gen `varplot' = 1 / `rhopoints' } // end mean **************************************************** // generate naive CIs **************************************************** gen z_test = `z' ******** // adjust for multiple tests if ("`madjust'" != "") { local pbon = (0.05 / `nobs') _qval , z(`z') nobs(`nobs') local pfdr = r(qval) local pval `pbon' `pfdr' 0.05 } local pcnt : word count `pval' forval i = 1/`pcnt' { tempvar ll`i' ul`i' } local i = 1 foreach p in `pval' { gen `ll`i'' = `target' + invnormal(`p') * sqrt(`varplot') gen `ul`i'' = `target' + invnormal(1-`p') * sqrt(`varplot') local i = `i' + 1 } ******************************************** // if overdisp is additive (random-effects) ******************************************** if ("`overdisp'" == "add") { ** replace Z replace `z' = (`y' - `target')/sqrt(`varY' + `tau2') // adjust for multiple tests if ("`madjust'" != "") { local pbon = (0.05 / `nobs') _qval , z(`z') nobs(`nobs') local pfdr = r(qval) local pval `pbon' `pfdr' 0.05 } local pcnt : word count `pval' local i = 1 foreach p in `pval' { replace `ll`i'' = `target' + invnormal(`p') * sqrt(`varplot' + `tau2') replace `ul`i'' = `target' + invnormal(1-`p') * sqrt(`varplot' + `tau2') local i = `i' + 1 } } // end "add" ********************************** // if target is a range ********************************** if ("`range1'" != "") { ** replace Z replace `z' = (`y' - `range2')/sqrt(`varY') if `y' > `range2' replace `z' = (`y' - `range1')/sqrt(`varY') if `y' < `range2' // adjust for multiple tests if ("`madjust'" != "") { local pbon = (0.05 / `nobs') _qval , z(`z') nobs(`nobs') local pfdr = r(qval) local pval `pbon' `pfdr' 0.05 } local pcnt : word count `pval' local i = 1 foreach p in `pval' { replace `ll`i'' = `range1' + invnormal(`p') * sqrt(`varplot') replace `ul`i'' = `range2' + invnormal(1-`p') * sqrt(`varplot') local i = `i' + 1 } if ("`logtrans'" != "") { forvalue i = 1/`pcnt' { replace `ll`i'' = log(`ll`i'') replace `ul`i'' = log(`ul`i'') } } } ********************************* // if overdisp is multiplicative ********************************* if ("`overdisp'" == "mult") { // z-scores are not updated here because CIs do not require new p-value forvalue i = 1/`pcnt' { replace `ll`i'' = `target' + (`ll`i'' - `target') * sqrt(`phi') replace `ul`i'' = `target' + (`ul`i'' - `target') * sqrt(`phi') } } ************************************ // Back transform to original scale ************************************ if ("`logtrans'" != "") { replace `y' = exp(`y') if `y' !=. local target = exp(`target') forvalue i = 1/`pcnt' { replace `ll`i'' = exp(`ll`i'') replace `ul`i'' = exp(`ul`i'') } } if ("`arcsintrans'" != "") { replace `y' = sin(`y')^2 local target = sin(`target')^2 forvalue i = 1/`pcnt' { replace `ll`i'' = sin(max(-_pi/2,`ll`i''))^2 replace `ul`i'' = sin(min(_pi/2,`ul`i''))^2 } } if ("`sqrttrans'" != "") { replace `y' = `y'^2 local target = `target'^2 forvalue i = 1/`pcnt' { replace `ll`i'' = max(0, `ll`i'')^2 replace `ul`i'' = `ul`i''^2 } } ************************************************ // Exact CIs for prop, rate and smr subcommands ************************************************ if !inlist("`estype'", substr("prop", 1, max(4,`lcmd')), substr("rate", 1, max(4,`lcmd')), substr("smr", 1, max(3,`lcmd'))) & (`varcnt' == 2) { if ("`exact'" != "") { noi di as err " 'exact' may only be specified with 'prop', 'rate' or 'smr' subcommands for cross-sectional analysis" exit 198 } } if !inlist("`estype'", substr("smr", 1, max(3,`lcmd'))) & (`varcnt' == 4) { if ("`exact'" != "") { noi di as err " 'exact' may only be specified with the 'smr' subcommand for longitudinal (change over two periods) analysis" exit 198 } } // Exact for "prop" and "rate" for cross-sectional analysis if inlist("`estype'", substr("prop", 1, max(4,`lcmd')), substr("rate", 1, max(4,`lcmd'))) { if ("`exact'" != "") & (`varcnt' == 2) { replace `rhopoints' = `rhopoints' + 1 // first loop is for LCLs local i = 1 foreach p in `pval' { gen `qbinom' = . forvalues ii = 1/`npoints' { local r = `rhopoints'[`ii'] mata : st_numscalar("k", invcdfbinomial(`r', `p', `target')) replace `qbinom' = scalar(k) in `ii' } gen `c' = (`p' - binomial(`rhopoints', `qbinom'-1, `target')) / binomialp(`rhopoints', `qbinom', `target') gen `x' = `qbinom' - 0.5 + `c' replace `x' = `rhopoints' if `x' > `rhopoints' replace `ll`i'' = `x' / `rhopoints' drop `qbinom' `c' `x' local i = `i' + 1 } // end pval // second loop is for UCLs local i = 1 foreach p in `pval' { gen `qbinom' = . forvalues ii = 1/`npoints' { local r = `rhopoints'[`ii'] mata : st_numscalar("k", invcdfbinomial(`r', 1-`p', `target')) replace `qbinom' = scalar(k) in `ii' } gen `c' = ((1-`p') - binomial(`rhopoints', `qbinom'-1, `target')) / binomialp(`rhopoints', `qbinom', `target') gen `x' = `qbinom' - 0.5 + `c' replace `x' = `rhopoints' if `x' > `rhopoints' replace `ul`i'' = `x' / `rhopoints' drop `qbinom' `c' `x' local i = `i' + 1 } // end pval } // end exact } // end prop/rate // Exact for "smr" cross-sectional if inlist("`estype'", substr("smr", 1, max(3,`lcmd'))) { if ("`exact'" != "") & (`varcnt' == 2) { // first loop is for LCLs local i = 1 foreach p in `pval' { gen `qpois' = . forvalues ii = 1/`npoints' { local r = `rhopoints'[`ii'] mata : st_numscalar("k", invcdfpoisson(`r' * `target', `p')) replace `qpois' = scalar(k) in `ii' } gen `c' = (`p' - poisson(`rhopoints' * `target', `qpois' - 1)) / poissonp(`rhopoints' * `target', `qpois') if `qpois' != 0 replace `c' = (`p' - 0) / poissonp(`rhopoints' * `target', `qpois') if `qpois' == 0 gen `x' = `qpois' - 0.5 + `c' replace `ll`i'' = `x' / `rhopoints' drop `qpois' `c' `x' local i = `i' + 1 } // end pval // second loop is for UCLs local i = 1 foreach p in `pval' { gen `qpois' = . forvalues ii = 1/`npoints' { local r = `rhopoints'[`ii'] mata : st_numscalar("k", invcdfpoisson(`r' * `target', 1 - `p')) replace `qpois' = scalar(k) in `ii' } gen `c' = ((1 - `p') - poisson(`rhopoints' * `target', `qpois' - 1)) / poissonp(`rhopoints' * `target', `qpois') if `qpois' != 0 replace `c' = ((1 - `p') - 0) / poissonp(`rhopoints' * `target', `qpois') if `qpois' == 0 gen `x' = `qpois' - 0.5 + `c' replace `ul`i'' = `x' / `rhopoints' drop `qpois' `c' `x' local i = `i' + 1 } // end pval } // end if } // // end smr cross-sectional // *** Exact SMR ratio (for change) *** // if inlist("`estype'", substr("smr", 1, max(3,`lcmd'))) { if ("`exact'" != "") & (`varcnt' == 4) { * work out conditional limits and then transform back local target2 = `target' / (1 + `target') * half the sample size of Binomial (O1+O2)/2 replace `rhopoints' = round(2 * `rhopoints') / 2 gen `rhopoints2' = 2 * `rhopoints' levelsof `rhopoints2', local(points) // first loop is for LCLs local i = 1 foreach p in `pval' { gen `qbinom' = . foreach ii of local points { mata : st_numscalar("k", invcdfbinomial(`ii', `p', `target2')) replace `qbinom' = scalar(k) if `rhopoints2' == `ii' } gen `c' = (`p' - binomial(`rhopoints2', `qbinom'-1, `target2')) / binomialp(`rhopoints2', `qbinom', `target2') gen `x' = `qbinom' - 0.5 + `c' replace `x' = `rhopoints2' if `x' > `rhopoints2' replace `ll`i'' = `x' / (`rhopoints2' - `x') drop `qbinom' `c' `x' local i = `i' + 1 } // end pval // second loop is for UCLs local i = 1 foreach p in `pval' { gen `qbinom' = . foreach ii of local points { mata : st_numscalar("k", invcdfbinomial(`ii', 1-`p', `target2')) replace `qbinom' = scalar(k) if `rhopoints2' == `ii' } gen `c' = (1-`p' - binomial(`rhopoints2', `qbinom'-1, `target2')) / binomialp(`rhopoints2', `qbinom', `target2') gen `x' = `qbinom' - 0.5 + `c' replace `x' = `rhopoints2' if `x' > `rhopoints2' replace `ul`i'' = `x' / (`rhopoints2' - `x') drop `qbinom' `c' `x' local i = `i' + 1 } // end pval } // end exact for change } // end SMR ******************************* //* figure *// ******************************* if ("`overdisp'" == "mult") { local note1 "Overdispersion: `phipct'%" local note2 "Winsorized: `winsor'%" } if ("`overdisp'" == "add") { if ("`ypercent'" != "") { local tau = `tau' * 100 } if ("`ratedenom'" != "") { local tau = `tau' * `ratedenom' } local rtau: display %-06.4f `tau' local note1 "Random Effects SD: `rtau'" local note2 "Winsorized: `winsor'%" } if ("`ypercent'" != "") { if !inlist("`estype'", substr("prop", 1, max(4,`lcmd')), substr("smr", 1, max(3,`lcmd'))) { noi di as err " 'ypercent' may only be specified with 'prop' or 'smr' subcommands" exit 198 } else { forvalue i = 1/`pcnt' { replace `ll`i'' = `ll`i'' * 100 replace `ul`i'' = `ul`i'' * 100 } local target = `target' * 100 replace `y' = `y' * 100 } } // end ypercent if ("`ratedenom'" != "") { if !inlist("`estype'", substr("rate", 1, max(4,`lcmd'))) { noi di as err " 'ratedenom()' may only be specified with the 'rate' subcommand" exit 198 } else { forvalue i = 1/`pcnt' { replace `ll`i'' = `ll`i'' * `ratedenom' replace `ul`i'' = `ul`i'' * `ratedenom' } } local target = `target' * `ratedenom' replace `rhopoints' = `rhopoints' / `ratedenom' replace `rho' = `rho' / `ratedenom' replace `y' = `y' * `ratedenom' } // end ratedenom // display range values if ("`range1'" != "") { if ("`ypercent'" != "") { local range1 = `range1' * 100 local range2 = `range2' * 100 } if ("`ratedenom'" != "") { local range1 = `range1' * `ratedenom' local range2 = `range2' * `ratedenom' } local target `range1' `range2' } // end ranges // scale y according to specified 'm' (based on Jones et al 2008 for SMR) if ("`m'" != "") { replace `y' = `y' * `m' local target = `m' forvalue i = 1/`pcnt' { replace `ll`i'' = `ll`i'' * `m' replace `ul`i'' = `ul`i'' * `m' } } // end m // truncate values at specified min on Y axis if ("`ymin'" !="") { forvalue i = 1/`pcnt' { replace `ll`i'' = . if `ll`i'' < `ymin' } replace `y' = . if `y' < `ymin' } // truncate values at specified max on Y axis if ("`ymax'" !="") { forvalue i = 1/`pcnt' { replace `ul`i'' = . if `ul`i'' > `ymax' } replace `y' = . if `y' > `ymax' } // default X and Y titles if inlist("`estype'", substr("smr", 1, max(3,`lcmd'))) { local ytitle ytitle(SMR) local xtitle xtitle(Expected) } if inlist("`estype'", substr("prop", 1, max(4,`lcmd'))) { local ytitle ytitle(Proportion) local xtitle xtitle(N) } if inlist("`estype'", substr("rate", 1, max(4,`lcmd'))) { local ytitle ytitle(Rate) local xtitle xtitle(N) } if inlist("`estype'", substr("mean", 1, max(4,`lcmd'))) { local ytitle ytitle(Mean) local xtitle xtitle(Precision) } // legend values local i = 1 foreach p in `pval' { local legci`i' = (1 - 2 * `p') * 100 local i = `i' + 1 } if ("`madjust'" == "") { local legci1: display %2.1f `legci1' local legci2: display %2.1f `legci2' local legci3: display %2.1f `legci3' } else if ("`madjust'" != "") { local legci1: display %2.0f 95 local legci2: display %2.0f 95 local legci3: display %2.0f 95 local adj1 " (Bonf. Adj)" local adj2 " (FDR Adj)" local adj3 " (One-sided)" } } // end quietly // generate figure if 1, 2 or 3 p-values are specified if (`pcnt' == 3) { twoway(line `ll1' `ul1' `rhopoints', sort lcolor(blue blue)) /// (line `ll2' `ul2' `rhopoints', sort lcolor(green green)) /// (line `ll3' `ul3' `rhopoints', sort lcolor(orange orange)) /// (scatter `y' `rho', mcolor(red) msize(small) yline(`target', lpattern(shortdash) lcolor(black) lwidth(medthick)) /// note("`note1'" "`note2'") legend(order(1 "`legci1'% CI `adj1'" 3 "`legci2'% CI `adj2'" 5 "`legci3'% CI `adj3'") pos(2) col(1) ring(0)) `ytitle' `xtitle' `figure') } if (`pcnt' == 2) { twoway(line `ll1' `ul1' `rhopoints', sort lcolor(blue blue)) /// (line `ll2' `ul2' `rhopoints', sort lcolor(green green)) /// (scatter `y' `rho', mcolor(red) msize(small) yline(`target', lpattern(shortdash) lcolor(black) lwidth(medthick)) /// note("`note1'" "`note2'") legend(order(1 "`legci1'% CI `adj'" 3 "`legci2'% CI") pos(2) col(1) ring(0)) `ytitle' `xtitle' `figure') } else if (`pcnt' == 1) { twoway(line `ll1' `ul1' `rhopoints' , sort lcolor(blue blue)) /// (scatter `y' `rho', mcolor(red) msize(small) yline(`target', lpattern(shortdash) lcolor(black) lwidth(medthick)) /// note("`note1'" "`note2'") legend(order(1 "`legci1'% CI") pos(2) col(1) ring(0)) `ytitle' `xtitle' `figure') } ****************** // saved results ****************** return scalar N = `nobs' if ("`range1'" != "") { return scalar target1 = `range1' return scalar target2 = `range2' } else if ("`range1'" == "") { return scalar target = `target' } if ("`overdisp'" != "") { return scalar phi = `phi' } if ("`overdisp'" == "add") { return scalar tau = `rtau' } return scalar winsor = `winsor' if ("`madjust'" != "") { return scalar pbon = `pbon' return scalar pfdr = `pfdr' } ******************** // save CIs to file ******************** if `"`saving'"' != "" { forvalue i = 1/`pcnt' { qui gen ll`i' = `ll`i'' qui gen ul`i' = `ul`i'' local ci "`ci' ll`i' ul`i'" } qui keep `ci' qui keep in 1 / `npoints' save `saving' } // end saving end */ capture program drop _qval program _qval, rclass version 11.0 syntax [varlist(default=none)] [, z(varname) nobs(numlist max=1)] tempvar pv rank fdr q_fdr qfdr // compute p-values only for the positive Z's (upper CIs) gen double `pv' = 2 - 2 * normal(abs(`z')) sum `pv', meanonly // set qval to 0.05 (one-sided test) if no p-values are < 0.05 if (r(min) >= 0.05) { return scalar qval = 0.05 } else { sort `pv' gen `rank' = _n gen `fdr' = (`rank' / `nobs') * 0.05 // non-ordered Q-FDR gen `q_fdr' = 1 * (`pv' * `nobs'/`rank') in 1/l gen `qfdr' = `q_fdr' // get cumulative minimum forvalues ii = 2 / `nobs' { replace `qfdr' = min(`qfdr', `qfdr'[_n + 1]) in 1/l } // find fdr at 1st q-value > 0.05 local i = 1 local ii = 2 while (`qfdr'[`i'] <= 0.05) & (`i' < `nobs' ) { local qval = `fdr'[`ii'] noi di "`qval'" local i = `i' + 1 local ii = `ii' + 1 } if "`qval'" == "" { local qval = 0.05 } return scalar qval = `qval' } // end else end capture program drop _winsor program _winsor, rclass version 11.0 syntax [varlist(default=none)] [, z(varname) zwins(varname) WINsor(numlist max=1) nobs(numlist max=1)] local p = (`winsor' / 100) local h = int(`p' * `nobs') sort `z' * upper range local up = `nobs' - `h' local uz = `z'[`up'] * lower range local low = `h' + 1 local lz = `z'[`low'] qui replace `zwins' = cond(`z' < `lz', `lz', cond(`z' > `uz', `uz', `z')) end version 11.0 mata : function invcdfbinomial(real scalar n, real scalar x, real scalar p) { real scalar mu real scalar sigma real scalar gamma real scalar z real scalar k if ( hasmissing((n,x,p)) ) return(.) if (n < 0) _error(3300) if ( (x<0)|(x>1) ) _error(3300) if ( (p<0)|(p>1) ) _error(3300) mu = n*p sigma = sqrt(n*p*(1-p)) gamma = ((1-p)-p)/sigma z = invnormal(x) k = floor(mu + sigma * (z + gamma*(z*z-1)/6) + 0.5) k = min((k,n)) if (binomial(n,k,p) >= x) while (binomial(n,k-1,p) > x) k-- else while (binomial(n,k,p) <= x) k++ return(k) } end version 11.0 mata : real scalar invcdfpoisson( real scalar m, real scalar x ) { real scalar mu real scalar sigma real scalar gamma real scalar z real scalar k if ( hasmissing((m,x)) ) return(.) if (m < 0) _error(3300) if ( (x<0)|(x>1) ) _error(3300) if (m == 0) return(0) if (x == 0) return(0) if (x == 1) return(.) mu = m sigma = sqrt(m) gamma = 1/sigma z = invnormal(x) k = round(mu + sigma * (z + gamma*(z*z-1)/6) + 0.5) k = max((0,k)) if (poisson(m,k) >= x) while ( k & (poisson(m,k-1)>x) ) k-- else while (poisson(m,k) <= x) k++ return(k) } end