*! version 1.2.2 15dec2022 JM. Domenech, R. Sesma /* dti: Diagnostics Tests (immediate command) For cross-sectional and case-control studies 4 elements are provided: a1 a0 b1 b0 for bayes theorem 2 elements (sensibility and specificity) or 4 elements (a1 a0 b1 b0) are provided */ program define dti, rclass version 12 syntax anything(id="argument numlist"), /* */ [ST(string) Wilson Exact WAld P(numlist min=1 >0 <100) /* */ m1(numlist integer max=1 >0) n(numlist integer max=1 >0) /* */ Level(numlist max=1 >50 <100) nst(string) /* */ _test(string) _vtest(string) _ref(string) _vref(string) /* */ _total(integer 0) _valid(integer 0)] *Study type if ("`st'"=="") local st = "cs" if ("`st'"!="cs" & "`st'"!="cc" & "`st'"!="bt") print_error "st() invalid -- invalid value" *Tokenize & test parameters tokenize `anything' local len : word count `anything' if (`len'!=2 & `len'!=4) print_error "argument numlist must have 2 or 4 elements" if (`len'==2 & "`st'"!="bt") print_error "2 elements numlist is for Bayes Theorem study type, st(bt)" if (`len'==2) { local se = `1'/100 local sp = `2'/100 confirm number `se' confirm number `sp' if (`se'<=0 | `se'>=1) print_error "sensitivity invalid -- out of range" if (`sp'<=0 | `sp'>=1) print_error "specificity invalid -- out of range" } if (`len'==4) { confirm integer number `1' confirm integer number `2' confirm integer number `3' confirm integer number `4' local a1 = `1' local a0 = `2' local b1 = `3' local b0 = `4' foreach i of numlist `a1' `a0' `b1' `b0' { if (`i'<0) print_error "'`i'' found where positive integer expected" } local n1 = `a1' + `b1' local n0 = `a0' + `b0' local m1t = `a1' + `a0' local m0 = `b1' + `b0' local nt = `a1' + `a0' + `b1' + `b0' } if ("`wilson'"=="" & "`exact'"=="" & "`wald'"=="") local method = "wilson" if ("`wilson'"!="" & ("`exact'"!="" | "`wald'"!="")) print_error "only one of wilson, exact, wald options is allowed" if ("`exact'"!="" & "`wald'"!="") print_error "only one of wilson, exact, wald options is allowed" if ("`wilson'"!="") local method = "wilson" if ("`exact'"!="") local method = "exact" if ("`wald'"!="") local method = "wald" if ("`level'"=="") local level 95 if (("`m1'"!="" | "`n'"!="") & ("`st'"!="cc")) print_error "m1(), n() option is only allowed for case-control studies, st(cc)" if (("`m1'"!="" & "`n'"=="") | ("`m1'"=="" & "`n'"!="")) print_error "missing option m1() or n()" if ("`m1'"!="" & "`n'"!="") { if (`m1'>=`n') print_error "n must be greater than m1" } if ("`st'"=="bt" & "`p'"=="") print_error "p() option is necessary for bayes theorem, st(bt)" *Print & compute results di if ("`st'"=="cs") di as res "DIAGNOSTIC TESTS: CROSS-SECTIONAL STUDY" if ("`st'"=="cc") di as res "DIAGNOSTIC TESTS: CASE-CONTROL STUDY" if ("`st'"=="bt") di as res "DIAGNOSTIC TESTS: BAYES THEOREM" if ("`nst'"!="") display as text "{bf:STUDY:} `nst'" if (`_total'!=0) { *Data call: cases summary di as txt "Valid cases: " as res `_valid' as txt " (" as res %5.1f 100*`_valid'/`_total' as txt "%)" di as txt "Total cases: " as res `_total' } di *Table data if ("`_ref'"=="") { local coltitle = "Reference Criterion" local col1 = "NonCases" local col2 = "Cases" local rowtitle = "Diagnostic Test" local row1 = "Positive" local row2 = "Negative" } else { * if possible, show variable label abbreviated (v1.2.0) local coltitle : variable label `_ref' local coltitle = cond("`coltitle'"=="","`_ref'","`coltitle'") local coltitle = abbrev("`coltitle'",19) tokenize `_vref' local col1: label (`_ref') `1' local col1 = abbrev("`col1'",9) local col2: label (`_ref') `2' local col2 = abbrev("`col2'",9) * if possible, show variable label abbreviated (v1.2.0) local rowtitle : variable label `_test' local rowtitle = cond("`rowtitle'"=="","`_test'","`rowtitle'") local rowtitle = abbrev("`rowtitle'",15) tokenize `_vtest' local row1: label (`_test') `2' local row1 = abbrev("`row1'",16) local row2: label (`_test') `1' local row2 = abbrev("`row2'",16) } if ("`st'"=="cs" | ("`st'"=="bt" & `len'==4)) { di as txt _col(18) "{c |}{center 21:{bf:`coltitle'}}{c |}" di as txt "{ralign 16:{bf:`rowtitle'}} {c |}{ralign 9:`col1'} {c |}{ralign 9:`col2'} {c |}" _col(45) "TOTAL" di as txt "{hline 17}{c +}{hline 10}{c +}{hline 10}{c +}{hline 10}" di as txt "{ralign 16:`row1'} {c |} " as res %8.0f `b1' " {c |} " %8.0f `a1' " {c |} " %8.0f `n1' di as txt "{ralign 16:`row2'} {c |} " as res %8.0f `b0' " {c |} " %8.0f `a0' " {c |} " %8.0f `n0' di as txt "{hline 17}{c +}{hline 10}{c +}{hline 10}{c +}{hline 10}" di as txt _col(12) "TOTAL {c |} " as res %8.0f `m0' " {c |} " %8.0f `m1t' " {c |} " %8.0f `nt' } if ("`st'"=="cc") { if ("`m1'"=="" & "`n'"=="") { di as txt _col(18) "{c |}{center 21:{bf:`coltitle'}}" di as txt "{ralign 16:{bf:`rowtitle'}} {c |}{ralign 9:`col1'} {c |}{ralign 9:`col2'}" di as txt "{hline 17}{c +}{hline 10}{c +}{hline 10}" di as txt "{ralign 16:`row1'} {c |} " as res %8.0f `b1' " {c |} " %8.0f `a1' di as txt "{ralign 16:`row2'} {c |} " as res %8.0f `b0' " {c |} " %8.0f `a0' di as txt "{hline 17}{c +}{hline 10}{c +}{hline 10}" di as txt _col(12) "TOTAL {c |} " as res %8.0f `m0' " {c |} " %8.0f `m1t' } else { di as txt _col(20) "{c |}{center 21:{bf:`coltitle'}}" _col(42) "{c |}" di as txt "{ralign 18:{bf:`rowtitle'}} {c |}{ralign 9:`col1'} {c |}{ralign 9:`col2'}" _col(42) "{c |}" di as txt "{hline 19}{c +}{hline 10}{c +}{hline 10}{c RT}" di as txt "{ralign 18:`row1'} {c |} " as res %8.0f `b1' " {c |} " %8.0f `a1' " {c |}" di as txt "{ralign 18:`row2'} {c |} " as res %8.0f `b0' " {c |} " %8.0f `a0' " {c |}" di as txt "{hline 19}{c +}{hline 10}{c +}{hline 10}{c RT}" di as txt _col(14) "TOTAL {c |} " as res %8.0f `m0' " {c |} " %8.0f `m1t' " {c |} " as txt _col(47) "TOTAL" di as txt _col(20) "{c LT}{hline 10}{c +}{hline 10}{c +}{hline 10}" di as txt " POPULATION SAMPLE {c |} " as res %8.0f (`n'-`m1') " {c |} " %8.0f `m1' " {c |} " %8.0f `n' } } if ("`st'"=="cs" | "`st'"=="cc") { tempname r pr quietly { cii `m1t' `a1', level(`level') `method' //Sensitivity local se = r(mean) matrix `r' = (100*r(mean), 100*r(lb), 100*r(ub)) cii `m0' `b0', level(`level') `method' //Specificity local sp = r(mean) matrix `r' = `r' \ (100*r(mean), 100*r(lb), 100*r(ub)) cii `m0' `b1', level(`level') `method' //False positive matrix `r' = `r' \ (100*r(mean), 100*r(lb), 100*r(ub)) cii `m1t' `a0', level(`level') `method' //False negative matrix `r' = `r' \ (100*r(mean), 100*r(lb), 100*r(ub)) csi `a1' `b1' `a0' `b0', level(`level') //Likelihood Ratio (+) matrix `r' = `r' \ (r(rr), r(lb_rr), r(ub_rr)) csi `a0' `b0' `a1' `b1', level(`level') //Likelihood Ratio (-) matrix `r' = `r' \ (r(rr), r(lb_rr), r(ub_rr)) local lcont = ("`st'"=="cs") | ("`st'"=="cc" & ("`m1'"!="" & "`n'"!="")) if ("`st'"=="cs") { csi `a1' `b1' `a0' `b0', level(`level') or //Odds ratio if (r(or)<.) { matrix `r' = `r' \ (r(or), r(lb_or), r(ub_or)) } else { * if or is not computable, neither is the ci matrix `r' = `r' \ (., ., .) } cii `nt' `m1t', level(`level') `method' //Prevalence matrix `r' = `r' \ (100*r(mean), 100*r(lb), 100*r(ub)) cii `n1' `a1', level(`level') `method' //Predictive value (+) matrix `r' = `r' \ (100*r(mean), 100*r(lb), 100*r(ub)) cii `n0' `b0', level(`level') `method' //Predictive value (-) matrix `r' = `r' \ (100*r(mean), 100*r(lb), 100*r(ub)) local t = `a1' + `b0' cii `nt' `t', level(`level') `method' //Efficiency matrix `r' = `r' \ (100*r(mean), 100*r(lb), 100*r(ub)) } if ("`st'"=="cc" & ("`m1'"!="" & "`n'"!="")) { cii `n' `m1', level(`level') `method' //Prevalence local prev = r(mean) matrix `r' = `r' \ (100*r(mean), 100*r(lb), 100*r(ub)) getpv `prev' `se' `sp' `m1t' `m0' `n', level(`level') ext //Predictive value (+), (-) matrix `r' = `r' \ (r(pvp), r(lbp), r(ubp)) matrix `r' = `r' \ (r(pvn), r(lbn), r(ubn)) local eff = (`prev'*`se'+(1-`prev')*`sp')*100 //Efficiency matrix `r' = `r' \ (`eff',.,.) } } di di as txt "{hline 19}{c TT}{hline 10}{c TT}{hline 28}" di as txt _col(20) "{c |} Estimate {c |} `level'% Confidence Interval" di as txt "{hline 19}{c +}{hline 10}{c +}{hline 28}" _c local m = proper("`method'") local c1 "Sensitivity" local c2 "Specificity" local c3 "False positive" local c4 "False negative" foreach i of numlist 1/4 { di as txt _newline "{ralign 18:`c`i''} {c |} " _c foreach j of numlist 1/3 { local v = `r'[`i',`j'] local col = cond(`j'==1,23,cond(`j'==2,35,46)) if (`j'==1) print_pct `v', col(`col') else print_pct `v', col(`col') nopercent if (`j'==1) di " {c |}" _c if (`j'==2) di as txt " to " _c } di as txt " (`m')" _c } local c5 "Likelihood Ratio +" local c6 "-" foreach i of numlist 5/6 { di as txt _newline "{ralign 18:`c`i''} {c |} " _c foreach j of numlist 1/3 { local v = `r'[`i',`j'] di as res %7.0g `v' _c if (`j'==1) di " {c |} " _c if (`j'==2) di as txt " to " _c } if (`r'[`i',1]<1) { *LR+, LR- inverses local sign = cond(`i'==5,"+","-") di as txt _newline "{ralign 18:inverse `sign'} {c |} " /* */ as res %7.0g 1/`r'[`i',1] " {c |} " %7.0g 1/`r'[`i',3] /* */ as txt " to " as res %7.0g 1/`r'[`i',2] _c } } if ("`st'"=="cs") { di as txt _newline "{ralign 18:Odds ratio} {c |} " /* */ as res %7.0g `r'[7,1] " {c |} " %7.0g `r'[7,2] /* */ as txt " to " as res %7.0g `r'[7,3] _c } if (`lcont') { di as txt _newline "{hline 19}{c +}{hline 10}{c +}{hline 28}" _c local c1 "Sample prevalence" local c2 "Predictive value +" local c3 "-" local c4 "Efficiency" local ini = cond("`st'"=="cs",8,7) local fin = rowsof(`r') local count 1 foreach i of numlist `ini'/`fin' { di as txt _newline "{ralign 18:`c`count''} {c |} " _c foreach j of numlist 1/3 { local v = `r'[`i',`j'] local col = cond(`j'==1,23,cond(`j'==2,35,46)) if (`v'<.) { if (`j'==1) print_pct `v', col(`col') else print_pct `v', col(`col') nopercent } if (`j'==1) di " {c |}" _c if (`j'==2 & `v'<.) di as txt " to " _c } if ("`st'"=="cs" | `i'==`ini') di as txt " (`m')" _c if ("`st'"=="cc" & `i'>`ini' & `i'<`fin') di as txt " (Asymp)*" _c local count = `count' + 1 } } di as txt _newline "{hline 19}{c BT}{hline 10}{c BT}{hline 28}" if ("`st'"=="cc" & `lcont') { di as txt "(*) Monsour, Evans & Kupper. Stat Med.1991;443-56." di as txt "(*) Assumed constant Sensibility and Specificity" } if ("`p'"!="") { di di as txt " Pre-test {c |}{rcenter 45:Predictive values of disease*}" di as txt " Probability {c |}{rcenter 45:(`level'% Asympt. Confidence Interval)}" di as txt " of disease {c |}{rcenter 22:Positive}{c |}{rcenter 22:Negative}" di as txt "{hline 13}{c +}{hline 22}{c +}{hline 22}" tempname pr local first 1 foreach i in `p' { getpv `i'/100 `se' `sp' `m1t' `m0' `nt', level(`level') if (`first') matrix `pr' = (`i',r(pvp),r(lbp),r(ubp),r(pvn),r(lbn),r(ubn)) else matrix `pr' = `pr' \ (`i',r(pvp),r(lbp),r(ubp),r(pvn),r(lbn),r(ubn)) local first 0 di as res _col(6) %5.0g `i' as txt "%" _col(14) "{c |}" /* */ as res _col(19) %6.0g r(pvp) as txt "%" _col(37) "{c |}" /* */ as res _col(40) %6.0g r(pvn) as txt "%" di as txt _col(14) "{c |}" _col(18) "(" as res %6.0g r(lbp) /* */ as txt " to " as res %6.0g r(ubp) as txt ")" _col(37) "{c |}" /* */ _col(39) "(" as res %6.0g r(lbn) as txt " to " as res %6.0g r(ubn) as txt ")" } di as txt "{hline 13}{c BT}{hline 22}{c BT}{hline 22}" di as txt "(*) Assumed constant Sensibility and Specificity" } } if ("`st'"=="bt") { if (`len'==4) { qui cii `m1t' `a1', level(95) wilson //Sensitivity local se = r(mean) qui cii `m0' `b0', level(95) wilson //Specificity local sp = r(mean) } local fp = (1-`sp') local fn = (1-`se') local lrp = `se'/(1-`sp') local lrn = (1-`se')/`sp' local se_pct = 100*`se' local sp_pct = 100*`sp' local fp_pct = 100*`fp' local fn_pct = 100*`fn' if (`len'==4) di di as txt "{ralign 14:Sensibility} = " _c print_pct `se_pct' di as txt "{ralign 22:Likelihood Ratio +} = " as res %7.0g `lrp' di as txt "{ralign 14:Specificity} = " _c print_pct `sp_pct' if (`lrp'>=1 & `lrn'<1) di as txt "{ralign 22:-} = " as res %7.0g `lrn' else di as txt "{ralign 22:inverse +} = " as res %7.0g 1/`lrp' di as txt "{ralign 14:False positive} = " _c print_pct `fp_pct' if (`lrp'>=1 & `lrn'<1) di as txt "{ralign 22:inverse -} = " as res %7.0g 1/`lrn' else di as txt "{ralign 22:-} = " as res %7.0g `lrn' di as txt "{ralign 14:False negative} = " _c print_pct `fn_pct' if (`lrp'<1 & `lrn'<1) di as txt "{ralign 22:inverse -} = " as res %7.0g 1/`lrn' else di as txt " " if (`len'==4) { * if available, print sample prevalence (v1.2.0) local prev = 100*(`m1t'/`nt') di "Sample prevalence = " _c print_pct `prev' di " " } di di as txt "(Pre-test Prob.){c |} (Post-test Probability) {c |} Correctly {c |}" di as txt " Hypothetical {c |} Predictive Values* {c |} classified {c |}" di as txt " Prevalence {c |} Positive {c |} Negative {c |}(Efficiency){c |}" di as txt "{hline 16}{c +}{hline 12}{c +}{hline 12}{c +}{hline 12}{c RT}" local z if (`len'==4) local z = `prev' tempname pr local first 1 foreach i in `z' `p' { getpv_bayes `i'/100 `se' `sp' * if available, print sample prevalence and results (v1.2.1) if (`first' & `len'==4) di as txt "Sample " as res %5.0g `i' as txt "%" _col(17) "{c |}" _c else di as res _col(7) %6.0g `i' as txt "%" _col(17) "{c |}" _c di as res _col(20) %7.0g r(pvp) as txt "%" _col(30) "{c |}" /* */ _col(33) as res %7.0g r(pvn) as txt "%" _col(43) "{c |}" /* */ _col(46) as res %7.0g r(eff) as txt "%" _col(56) "{c |}" if (`first') matrix `pr' = (`i',r(pvp),r(pvn),r(eff)) else matrix `pr' = `pr' \ (`i',r(pvp),r(pvn),r(eff)) local first 0 } di as txt "{hline 16}{c BT}{hline 12}{c BT}{hline 12}{c BT}{hline 12}{c BRC}" di as txt "(*) Assumed constant Sensibility and Specificity" } *Stored results if ("`st'"!="bt" | ("`st'"=="bt" & `len'==4)) { return scalar a1= `a1' return scalar b1= `b1' return scalar a0= `a0' return scalar b0= `b0' return scalar m1= `m1t' return scalar m0= `m0' if ("`st'"=="cs" | "`st'"=="bt") { return scalar n1= `n1' return scalar n0= `n0' return scalar n= `nt' } if ("`st'"=="cc" & "`m1'"!="" & "`n'"!="") { return scalar m1_sample= `m1' return scalar m0_sample= `n' - `m1' return scalar n_sample= `n' } } if ("`st'"=="cs" | "`st'"=="cc") { local i 1 foreach res in se sp fp fn lrp lrn { return scalar `res' = `r'[`i',1] return scalar lb_`res' = `r'[`i',2] return scalar ub_`res' = `r'[`i',3] local ++i } if ("`st'"=="cs") { return scalar or = `r'[7,1] return scalar lb_or = `r'[7,2] return scalar ub_or = `r'[7,3] } if ("`st'"=="cs" | ("`st'"=="cc" & ("`m1'"!="" & "`n'"!=""))) { local i = cond("`st'"=="cs",8,7) foreach res in pre pvp pvn eff { return scalar `res' = `r'[`i',1] if ("`res'"!="eff" | ("`res'"=="eff" & "`st'"=="cs")) { return scalar lb_`res' = `r'[`i',2] return scalar ub_`res' = `r'[`i',3] } local ++i } } if ("`p'"!="") { matrix colnames `pr' = prob positive lb_pos ub_pos negative lb_neg ub_neg return matrix P = `pr' } } if ("`st'"=="bt") { return scalar se = 100*`se' return scalar sp = 100*`sp' return scalar fp = 100*`fp' return scalar fn = 100*`fn' return scalar lrp = `lrp' return scalar lrn = `lrn' matrix colnames `pr' = prev pvp pvn eff return matrix P = `pr' } return scalar level = `level' end program define print_pct syntax anything, [col(numlist max=1) nopercent] local p = `anything' local fmt = "%5.1f" if (abs(`p')<10) { local fmt = "%5.2f" if (abs(`p')<1) { local fmt = "%5.0g" if (abs(`p')<0.01 & abs(`p')>0) { local fmt = "%5.3f" } } } *local fmt = cond(abs(`p')<10,cond(abs(`p')<1,"%5.0g","%5.2f"),"%5.1f") if ("`col'"=="") di as res `fmt' `p' _c else di as res _col(`col') `fmt' `p' _c if ("`percent'"=="") di as txt "%" _c end program define getpv, rclass version 12 syntax anything(id="argument numlist"), Level(numlist max=1 >50 <100) [ext noic] tempname sigp sep sign sen z temp *Predictive Values for a given prevalence by Monsour, Evans y Kupper, 1991 tokenize `anything' local p = `1' local se = `2' local sp = `3' local m1 = `4' local m0 = `5' local n = `6' scalar `z' = invnormal((`level'+100)/200) scalar `temp' = 0 if ("`ext'"!="") scalar `temp' = 1/(`n'*`p'*(1-`p')) scalar `sigp' = (1-`sp')*(1-`p')/(`se'*`p') return scalar pvp = 100*(1/(1+`sigp')) scalar `sign' = (1-`se')*`p'/(`sp'*(1-`p')) return scalar pvn = 100*(1/(1+`sign')) if ("`noic'"=="") { scalar `sep' = sqrt((1-`se')/(`m1'*`se') + (`sp')/(`m0'*(1-`sp')) + `temp') return scalar ubp = 100*(1/(1+(`sigp'*exp(-`z'*`sep')))) return scalar lbp = 100*(1/(1+(`sigp'*exp(`z'*`sep')))) scalar `sen' = sqrt(`se'/(`m1'*(1-`se')) + (1-`sp')/(`m0'*`sp') + `temp') return scalar ubn = 100*(1/(1+(`sign'*exp(-`z'*`sen')))) return scalar lbn = 100*(1/(1+(`sign'*exp(`z'*`sen')))) } end program define getpv_bayes, rclass version 12 syntax anything(id="argument numlist") tempname pvp pvn *Predictive Values for a given prevalence - Bayes theorem tokenize `anything' local p = `1' local se = `2' local sp = `3' scalar `pvp' = ((`p'*`se')/(`p'*`se'+(1-`p')*(1-`sp'))) scalar `pvn' = (((1-`p')*`sp')/((1-`p')*`sp'+`p'*(1-`se'))) return scalar pvp = 100*`pvp' return scalar pvn = 100*`pvn' return scalar eff = 100*(`p'*`se'+(1-`p')*`sp') end program define print_error args message display in red "`message'" exit 198 end