*! version 0.2.0, Brent McSharry, 11dec2017 * Tabular CUSUM chart with O-E (VLAD) for binary outcomes *!this program was written with great help from the excellent textbook: Statistical Methods for Hospital Monitoring with R *!written by Anthony Morton, Kerrie Mengersen, Michael Whitby, Geoffrey Playford program define tabcusum, sortpreserve version 10.1 syntax varlist(min=2 max=2 numeric) [if] [in] , Predicted(varname numeric) [OR(real 1.5) Threshold(real 2.9) /* */ HEADstart(real 0.5) XLABEL(passthru) NOTE(passthru) TITLE(passthru) SUBtitle(passthru) XTitle(passthru) XAXis(passthru) /* */ SHAdearea(real 0) LIMits(numlist missingokay >0 <100 sort) noTRUNClimits] marksample touse qui { replace `touse' = 0 if missing(`predicted') count if `touse' local count `r(N)' if (`count'<2) { di as error "insufficient obs" return 2000 } if (`headstart' <0 | `headstart'>1) { di as error "headstart represents a multiplier to the threshold for where to begin after reset and must be between 0 and 1" return 198 } tokenize `varlist' local observed `1' capture assert missing(`observed') | inlist(`observed', 0, 1) if _rc != 0 { di "The observed outcome can only have values of missing, 0 or 1" error 7 } gsort -`touse' `2' tempvar sequenceVar gen `sequenceVar' = _n in 1/`count' set obs `=_N+1' label variable `sequenceVar' "Case No." replace `sequenceVar' = 0 in l replace `touse' = 1 in l gsort -`touse' `sequenceVar' local ++count if `shadearea'>0 { tempvar shadeY gen `shadeY' = `threshold' if `sequenceVar' >= `shadearea' local grapharea (area `shadeY' `sequenceVar', color(gs13) lwidth(none)) local lorder 3 } else { local lorder 2 } //create rasprt tempvar si Tihalf signal cumulative gen `si' = ln(cond(`observed',`or',1)/(1-`predicted' +(`or' * `predicted'))) in 2/`count' gen float `Tihalf' = 0 in 1 local reset = `threshold' * `headstart' replace `Tihalf' = max(`si' + cond(`Tihalf'[_n-1] < `threshold',`Tihalf'[_n-1],`reset'),0) in 2/`count' gen byte `signal' = `Tihalf'>`threshold' in 1/`count' gen float `cumulative' = 0 in 1 replace `cumulative' = sum(`signal'[_n-1]) in 2/`count' sum `signal', meanonly forvalues i=0/`r(sum)' { tempvar l`i' gen float `l`i'' = `Tihalf' if `cumulative'==`i' replace `l`i'' = `reset' if missing(`l`i'') & !missing(`l`i''[_n+1]) local graphline `graphline' `l`i'' local pstyle `pstyle' p1 } if `r(sum)'>0 { local legend legend(order(`lorder') label(`lorder' "CUSUM signal") ring(0) position(10) region(lwidth(none))) local resetY yline(`reset', lwidth(thin) lpattern(dot) lcolor(gs8)) } else { local legend legend(off) } capture noisily twoway `grapharea' (line `graphline' `sequenceVar', pstyle(`pstyle') `xaxis'), /* */ `xlabel' ytitle("Tabular CUSUM") yline(`threshold', lpattern(shortdash)) `resetY' yscale(range(0 `threshold')) /* */ legend(off) `xtitle' graphregion(margin(tiny))/* */ name(sprt, replace) nodraw //create o-e now resets are sorted tempvar oe generate `oe' = sum(`observed' - `predicted') in 1/`count' replace `signal' = cond(`signal'==1,`oe',.) sum `oe', meanonly local minoe `r(min)' local maxoe `r(max)' if ("`shadeY'" != "") { replace `shadeY' = ceil(`r(max)'+1) if !missing(`shadeY') local shade (area `shadeY' `sequenceVar', color(gs13) lwidth(none) base(`=floor(`r(min)'-1)')) } if ("`limits'"!="") { replace `touse' = 0 in 1 forvalues i = 1(1)`=wordcount("`limits'")' { local l:word `i' of `limits' tempvar lb`i' ub`i' qui gen `lb`i'' = . qui gen `ub`i'' = . mata:getCometTails("`predicted'","`touse'", "`lb`i''", "`ub`i''", `l') if ("`trunclimits'"==""){ replace `lb`i'' = . if `lb`i'' < `minoe' replace `ub`i'' = . if `ub`i'' > `maxoe' } local linepat: word `i' of longdash dash shortdash dot shortdash_dot dash_dot longdash_dot local linepat lpattern(`linepat') lcolor(gs8) lwidth(thin) local bounds `bounds' (line `ub`i'' `sequenceVar', `linepat') (line `lb`i'' `sequenceVar', `linepat') } } capture noisily twoway `shade' (line `oe' `sequenceVar', `xaxis') (scatter `signal' `sequenceVar', msymbol(T) msize(large) mcolor(red)) `bounds', /* */ `xlabel' ytitle("O-E CUSUM") `legend' name(oe, replace) nodraw xtitle("") graphregion(margin(tiny)) } if "`title'" == "" { local title:variable label `observed' if "`title'"=="" { local title `observed' } local title title(`"`title'"') } capture noi graph combine oe sprt, cols(1) `note' `title' `subtitle' capture graph drop oe sprt qui drop in 1 end mata: mata set matastrict on class CusumFinder { public real scalar p, i, approx, maxIts public real scalar LbBeta(), UbBeta() public real scalar BinarySearch() void new() } void CusumFinder::new(){ approx = 0.0001; maxIts = 200; } real scalar CusumFinder::BinarySearch( //pointer(real scalar function) scalar delegate, real scalar ub, real scalar target, real scalar min, real scalar max) { real scalar half, k, val, argLen; argLen = args(); for(k=0;k= .) { return(.); } if ((abs(val-target)<=approx)){ return(half) } if (val