*! version 5.0.0 BO/PR/IW 04oct2021. program define nstage, rclass version 10.0 syntax , Nstage(int) ACcrue(numlist integer >1) ALpha(numlist >0 <1) ARms(numlist integer >1) HR0(string) HR1(string) Omega(numlist >0 <1) /// [ ARAtio(string) corr(real 0.6) noIterate INCludedroppedarms /// PRobs S(string) T(string) TStop(real 0) TOl(real 0.005) TUnit(int 1) SIMcorr(string) seed(int -1) /// OUTfile(string) NOFwer fwerreps(int 250000) esb(string) NONBINDing FWERcontrol(string) OLD ] if `fwerreps'<1E5 { di as err "fwerreps() should be at least 100000" exit 198 } local old = ("`old'" == "old") /* Estimate c as in Royston et al 2009. `corr' is correlation between (log) HRs on I and D outcomes or the correlation between survival times on I (ignoring D) and D outcomes if -simcorr- is specified. Use of `c' attenuates correlation between HRs on I-outcome at intermediate stage and D-outcome at final stage. */ if `corr' < 0 | `corr' > 1 { di as err "invalid corr(`corr'), must be in range [0, 1]" exit 198 } local c = 1.1 * `corr' global title1 "n-stage trial design version 5.0.0, 04 Oct 2021" global title2 "based on Royston et al. (2011) Trials 12:81 and Blenkinsop et al." global title3 "(2019) Clinical Trials 16(2)" local hline = length("$title1") /* Calculate sample size required */ samplesize, nstage(`nstage') accrue(`accrue') alpha(`alpha') arms(`arms') /// hr0(`hr0') hr1(`hr1') omega(`omega') aratio(`aratio') s(`s') t(`t') /// tstop(`tstop') tunit(`tunit') seed(`seed') fwerreps(`fwerreps') `iterate' old(`old') forvalues j=1/`nstage'{ local armsS`j' = r(armsS`j') local accrueS`j' = r(accrueS`j') local hr0S`j' = r(hr0S`j') local hr1S`j' = r(hr1S`j') local lndelS`j' = r(lndelS`j') local tS`j' = r(tS`j') local etotS`j' = r(etotS`j') local eS`j' = r(eS`j') local eexpS`j' = r(eexpS`j') local ntotS`j' = r(ntotS`j') local nS`j' = r(nS`j') local nexpS`j' = r(nexpS`j') local alphaS`j' = r(alphaS`j') local zalphaS`j' = r(zalphaS`j') local omegaS`j' = r(omegaS`j') local zomegaS`j' = r(zomegaS`j') local acc0S`j' = r(acc0S`j') local oaccS`j' = r(oaccS`j') local eS`j'un = r(eS`j'un) local eS`j'star = r(eS`j'star) } local median1 = r(median1) local median`nstage' = r(median`nstage') local have_D = r(have_D) local Rstar = r(Rstar) local fac = r(fac) *noi di in red "`lndelS1'" local deltaS1 = exp(`lndelS1') return scalar deltaS1 = `deltaS1' return scalar lndelS1 = `lndelS1' return scalar eS1un = `eS1un' return scalar eS1 = `eS1' return scalar eS1star = `eS1star' return scalar nS1 = `nS1' return scalar tS1 = `tS1' return scalar ntotS1 = `ntotS1' return scalar nexpS1 = `nexpS1' return scalar etotS1 = `etotS1' return scalar eexpS1 = `eexpS1' return scalar omegaS1 = `omegaS1' if `nstage' == 1 exit /* Calculate between-stage correlation */ tempname RH0 RH1 if "`esb'" != "" & `have_D' & "`simcorr'"=="" { /* Add condition that forces simulated correlation if esb specified when I!=D, to ensure correlation is based on D-D outcomes. */ local simcorr 250 local corr 0.6 } if "`simcorr'" != "" & `have_D' { di as txt _n "Simulations are used to estimate the correlation structure." di as txt "Depending on the number of replicates, specified by {cmd:simcorr(`simcorr')}," di as text "the results might take some minutes to appear. Progress is shown below." } if `have_D' di as text _n "Correlation assumed between hazard ratios on I and D: corr() = `corr'" local sampl local events forvalues i = 1 / `nstage' { local sampl `sampl' `ntotS`i'' local events `events' `eS`i'' } if `nstage' == 2 { local rH0 = min(sqrt(`eS1' / `eS`nstage''), 1) if `have_D' { if "`simcorr'" != "" { hrcorrnstage, accrue(`accrue') hr0(`hr0') hr1(`hr1') /// n(`sampl') e(`events') t(`t') aratio(`Rstar') /// nstage(`nstage') rep(`simcorr') rho(`corr') seed(`seed') /// savehr(`outfile') forvalues k = 0/1 { local rH`k' = r(corrln1`nstage'H`k') matrix `RH`k'' = (1, `rH`k'' \ `rH`k'', 1) if "`esb'" != ""{ matrix `RH`k'' = r(Corrhr_EB`k') forvalues j = 0/1 { forvalues m = 1 / `nstage' { local d`m'`j' = r(d`m'`j') if `j'==0 { // Store D-events at interim stages when I!=D return scalar D`m' = `d`m'`j'' } } } } } local pwalpha = binormal(`zalphaS1', `zalphaS`nstage'', `rH0') // overall pairwise alpha local pwomega = binormal(`zomegaS1', `zomegaS`nstage'', `rH1') // overall pairwise power if "`esb'" == "" { local bindingpwer = `pwalpha' local bindingomega = `pwomega' } } else { local i 1 local rH0 = `rH0' * `c' local alphaI `alphaS1' local omegaI `omegaS1' local alpha_min = `alphaS1' * `alphaS`nstage'' local alpha_max = min(`alphaI', `alphaS`nstage'') local omega_min = `omegaS1' * `omegaS`nstage'' local omega_max = min(`omegaS1', `omegaS`nstage'') matrix `RH0' = (1, `rH0' \ `rH0', 1) local pwalpha = binormal(`zalphaS1', `zalphaS`nstage'', `rH0') // overall pairwise alpha local pwomega = binormal(`zomegaS1', `zomegaS`nstage'', `rH0') // overall pairwise power if "`esb'" == "" { local bindingpwer = `pwalpha' local bindingomega = `pwomega' //local pwalpha = `alphaS`nstage'' // CHECK } } } else { local rH0 = sqrt(`eS1' / `eS`nstage'') matrix `RH0' = (1, `rH0' \ `rH0', 1) local pwalpha = binormal(`zalphaS1', `zalphaS`nstage'', `rH0') // overall pairwise alpha local pwomega = binormal(`zomegaS1', `zomegaS`nstage'', `rH0') // overall pairwise power } } else { matrix `RH0' = I(`nstage') matrix `RH1' = I(`nstage') if (`have_D') & ("`simcorr'" != "") { hrcorrnstage, accrue(`accrue') hr0(`hr0') hr1(`hr1') n(`sampl') /// e(`events') t(`t') aratio(`Rstar') nstage(`nstage') rep(`simcorr') /// rho(`corr') seed(`seed') savehr(`outfile') } local ns1 = `nstage' - 1 forvalues i = 1 / `ns1' { local i1 = `i' + 1 forvalues j = `i1' / `nstage' { local rH0 = min(sqrt(`eS`i'' / `eS`j''), 1) // could add line under here: if esb, rH0 = min(sqrt(di1/dj1),1)? local rH1 = `rH0' if (`have_D') & (`j' == `nstage') { if "`simcorr'" != "" { forvalues k = 0/1 { local rH`k' = r(corrln`i'`nstage'H`k') } } else { local rH0 = `rH0' * `c' local rH1 = `rH0' } } forvalues k = 0/1 { matrix `RH`k''[`i', `j'] = `rH`k'' matrix `RH`k''[`j', `i'] = `RH`k''[`i', `j'] } } if `have_D' & "`simcorr'" != "" & "`esb'" != ""{ forvalues k = 0/1 { matrix `RH`k'' = r(Corrhr_EB`k') } forvalues l = 0/1 { forvalues m = 1 / `nstage' { local d`m'`l' = r(d`m'`l') if `l'==0 return scalar D`m' = `d`m'`l'' // Store D-events at interim stages when I!=D } } } } tempname zalpha zomega local za `zalphaS1' local zo `zomegaS1' forvalues m = 2 / `nstage' { local za `za' , `zalphaS`m'' local zo `zo' , `zomegaS`m'' } matrix `zalpha' = (`za') matrix `zomega' = (`zo') // Using 2000 replicates for GHK evaluation of MVN integral local rep 2000 mata: mvnprob("`zalpha'", "`RH0'", `rep') local pwalpha = r(p) mata: mvnprob("`zomega'", "`RH1'", `rep') local pwomega = r(p) if `have_D' & "`esb'" == "" { local bindingpwer = `pwalpha' local bindingomega = `pwomega' //local pwalpha = `alphaS`nstage'' } } /* Calculate the FWER and probabilities */ if !`have_D' & "`fwercontrol'"!=""{ // If I=D and FWER control specified, assume non-binding boundaries local nonbinding nonbinding } if "`nofwer'"=="" | "`probs'"!="" { local K = `armsS1'-1 // 1 exp. arm -- calculate probs algebraically (unless efficacy boundaries or non-binding boundaries specified) if `K'==1 & "`esb'" == "" & "`nonbinding'" == ""{ noi di "Algebraic evaluation of operating characteristics" if `nstage'==2 { local p11 = `alphaS1' local p10 = 1-`p11' local p21 = `pwalpha' local p20 = 1-`p21' local fwerate = `pwalpha' // Added to allow FWERcontrol option for 2 stage, 1 arm trial if `have_D' local maxfwer = `alphaS`nstage'' // 2 stage, 2 arm design with I!=D, maxfwer is just equal to final stage alpha } else { forvalues j = 1/`nstage' { local jm1 = `j'-1 tempname R`j' z`j' matrix `R`j'' = `RH0'[1..`j', 1..`j'] local za `zalphaS1' forvalues m = 2 / `j' { local za `za' , `zalphaS`m'' } matrix `z`j'' = (`za') mata: mvnprob("`z`j''", "`R`j''", `rep') local p`j'1 = r(p) local p`j'0 = 1-`p`j'1' local fwerate = `pwalpha' if `have_D' local maxfwer = `alphaS`nstage'' if `have_D' local pwomega = `omegaS`nstage'' } } } * Else use nstagefwer subroutine else { if `have_D' local ineqd ineqd if "`esb'" != "" { if `fwerreps'!=250000 local fwerreps `fwerreps' else local fwerreps 1000000 nstagefwer, nstage(`nstage') arms(`armsS1') alpha(`alpha') omega(`omega') corr(`RH0') aratio(`Rstar') seed(`seed') /// reps(`fwerreps') `ineqd' esb(`esb') `nonbinding' local edrops = r(edrops) local esbstop = r(esbstop) if "`esbstop'"!="."{ return local esbstop = "`esbstop'" } local pwalpha = r(pwer) local pwomega = r(pwomega) } else { nstagefwer, nstage(`nstage') arms(`armsS1') alpha(`alpha') omega(`omega') corr(`RH0') aratio(`Rstar') seed(`seed') /// reps(`fwerreps') `ineqd' `nonbinding' } local fwerate = r(fwer) local se_fwerate = r(se_fwer) local fwomega = r(fwomega) local allomega = r(allomega) if "`nonbinding'" != ""{ local pwalpha = r(pwer) local pwomega = r(pwomega) } if `have_D' local maxpwomega = r(pwomega) if `have_D' local maxfwer = r(maxfwer) if `have_D' local mvnpmaxfwer = r(mvnpmaxfwer) if `have_D' local se_maxfwer = r(se_maxfwer) forvalues j = 1/`nstage' { if "`esb'" != "" { local E`j' = r(E`j') return scalar E`j' = `E`j'' local lndelE`j' = ln(`hr1S`j'') + invnormal(`E`j'') * sqrt(`fac' / `eS`j'') return scalar deltaE`j' = exp(`lndelE`j'') } forvalues k = 0/`K' { local p`j'`k' = r(p`j'`k') } } } /******************************************************************************* Control FWER *******************************************************************************/ if "`fwercontrol'" !=""{ local fwercontrolpct = `fwercontrol'*100 di as text " " di as text _n "Searching for design which controls the FWER at `fwercontrolpct'% ..." if `have_D' local ineqd ineqd local targetfwer = `fwercontrol' local count 0 local x1 = `alphaS`nstage'' if `have_D' { local diff = `maxfwer' - `targetfwer' local y1 = `maxfwer' } else { local diff = `fwerate' - `targetfwer' local y1 = `fwerate' } local aJ = `targetfwer'/(2*`K') // Starting value for final-stage signficance level, aJ qui while `count'<3{ local alpha = `alphaS1' local jm1 = `nstage' - 1 forvalues j = 2/`jm1' { local alpha `alpha' `alphaS`j'' } local alpha `alpha' `aJ' samplesize, nstage(`nstage') accrue(`accrue') alpha(`alpha') arms(`arms') /// hr0(`hr0') hr1(`hr1') omega(`omega') aratio(`aratio') s(`s') t(`t') /// tstop(`tstop') tunit(`tunit') seed(`seed') fwerreps(`fwerreps') `iterate' old(`old') forvalues j=1/`nstage'{ local armsS`j' = r(armsS`j') local accrueS`j' = r(accrueS`j') local hr0S`j' = r(hr0S`j') local hr1S`j' = r(hr1S`j') local lndelS`j' = r(lndelS`j') local tS`j' = r(tS`j') local etotS`j' = r(etotS`j') local eS`j' = r(eS`j') local eexpS`j' = r(eexpS`j') local ntotS`j' = r(ntotS`j') local nS`j' = r(nS`j') local nexpS`j' = r(nexpS`j') local alphaS`j' = r(alphaS`j') local zalphaS`j' = r(zalphaS`j') local omegaS`j' = r(omegaS`j') local zomegaS`j' = r(zomegaS`j') local acc0S`j' = r(acc0S`j') local oaccS`j' = r(oaccS`j') local eS`j'un = r(eS`j'un) local eS`j'star = r(eS`j'star) } local median1 = r(median1) local median`nstage' = r(median`nstage') *______________________________________________________________________ * Calculate Between-stage correlation * tempname RH0 RH1 if "`esb'" != "" & `have_D' & "`simcorr'"==""{ // NEW - Add condition that forces simulated correlation if esb specified when I!=D, to ensure correlation is based on D-D outcomes local simcorr = 250 local corr 0.6 } if "`simcorr'" != "" & `have_D' { di as txt "Simulations are carried out to estimate the correlation structure." di as txt "Depending on the number of replicates, the results might take some minutes to appear." di as txt "Progress is shown below." } local sampl local events forvalues i = 1 / `nstage' { local sampl `sampl' `ntotS`i'' local events `events' `eS`i'' } if `nstage' == 2 { local rH0 = min(sqrt(`eS1' / `eS`nstage''), 1) if `have_D' { if "`simcorr'" != "" { hrcorrnstage, accrue(`accrue') hr0(`hr0') hr1(`hr1') n(`sampl') e(`events') t(`t') /// aratio(`Rstar') nstage(`nstage') rep(`simcorr') rho(`corr') seed(`seed') savehr(`outfile') forvalues k = 0/1 { local rH`k' = r(corrln1`nstage'H`k') matrix `RH`k'' = (1, `rH`k'' \ `rH`k'', 1) if "`esb'" != ""{ matrix `RH`k'' = r(Corrhr_EB`k') forvalues j = 0/1 { forvalues m = 1 / `nstage' { local d`m'`j' = r(d`m'`j') if `j'==0 return scalar D`m' = `d`m'`j'' } } } } } else { local i 1 local rH0 = `rH0' * `c' matrix `RH0' = (1, `rH0' \ `rH0', 1) } } else { local rH0 = sqrt(`eS1' / `eS`nstage'') matrix `RH0' = (1, `rH0' \ `rH0', 1) } local pwalpha = binormal(`zalphaS1', `zalphaS`nstage'', `rH0') // overall pairwise alpha local pwomega = binormal(`zomegaS1', `zomegaS`nstage'', `rH0') // overall pairwise power if `have_D' & "`esb'" == "" { local bindingpwer = `pwalpha' local bindingomega = `pwomega' //local pwalpha = `alphaS`nstage'' // CHECK } } else { matrix `RH0' = I(`nstage') matrix `RH1' = I(`nstage') if (`have_D') & ("`simcorr'" != "") { hrcorrnstage, accrue(`accrue') hr0(`hr0') hr1(`hr1') n(`sampl') e(`events') t(`t') aratio(`Rstar') /// nstage(`nstage') rep(`simcorr') rho(`corr') seed(`seed') savehr(`outfile') } local ns1 = `nstage' - 1 forvalues i = 1 / `ns1' { local i1 = `i' + 1 forvalues j = `i1' / `nstage' { local rH0 = min(sqrt(`eS`i'' / `eS`j''), 1) local rH1 = `rH0' if (`have_D') & (`j' == `nstage') { if "`simcorr'" != "" { forvalues k = 0/1 { local rH`k' = r(corrln`i'`nstage'H`k') } } else { local rH0 = `rH0' * `c' local rH1 = `rH0' } } forvalues k = 0/1 { matrix `RH`k''[`i', `j'] = `rH`k'' matrix `RH`k''[`j', `i'] = `RH`k''[`i', `j'] } } } if `have_D' & "`simcorr'" != "" & "`esb'" != ""{ forvalues k = 0/1 { matrix `RH`k'' = r(Corrhr_EB`k') } forvalues l = 0/1 { forvalues m = 1 / `nstage' { local d`m'`l' = r(d`m'`l') if `l'==0 return scalar D`m' = `d`m'`l'' } } } tempname zalpha zomega local za `zalphaS1' local zo `zomegaS1' forvalues m = 2 / `nstage' { local za `za' , `zalphaS`m'' local zo `zo' , `zomegaS`m'' } matrix `zalpha' = (`za') matrix `zomega' = (`zo') local rep 2000 mata: mvnprob("`zalpha'", "`RH0'", `rep') local pwalpha = r(p) mata: mvnprob("`zomega'", "`RH1'", `rep') local pwomega = r(p) if `have_D' & "`esb'" == "" { local bindingpwer = `pwalpha' local bindingomega = `pwomega' } } *______________________________________________________________________ * Call nstagefwer to calculate fwer using alphaJ * if "`esb'"!="" { if `fwerreps'!=250000 local fwerreps `fwerreps' else local fwerreps 1000000 nstagefwer, nstage(`nstage') arms(`armsS1') alpha(`alpha') omega(`omega') corr(`RH0') aratio(`Rstar') seed(`seed') /// reps(`fwerreps') `ineqd' esb(`esb') `nonbinding' local esbstop = r(esbstop) if "`esbstop'"!="."{ return local esbstop = "`esbstop'" } local pwalpha = r(pwer) local pwomega = r(pwomega) } else{ nstagefwer, nstage(`nstage') arms(`armsS1') alpha(`alpha') omega(`omega') corr(`RH0') aratio(`Rstar') seed(`seed') /// reps(`fwerreps') `ineqd' `nonbinding' } local fwerate = r(fwer) local se_fwerate = r(se_fwer) local fwomega = r(fwomega) local allomega = r(allomega) if "`nonbinding'" != ""{ local pwalpha = r(pwer) local pwomega = r(pwomega) } if `have_D' local fwerate = r(maxfwer) if `have_D' local maxfwer = r(maxfwer) if `have_D' local mvnpmaxfwer = r(mvnpmaxfwer) if `have_D' local se_maxfwer = r(se_maxfwer) forvalues j = 1/`nstage' { if "`esb'" != "" { local E`j' = r(E`j') return scalar E`j' = `E`j'' local lndelE`j' = ln(`hr1S`j'') + invnormal(`E`j'') * sqrt(`fac' / `eS`j'') return scalar deltaE`j' = exp(`lndelE`j'') } forvalues k = 0/`K' { local p`j'`k' = r(p`j'`k') } } if `count'==0{ // Use linear interpolation to get close to aJ local x2 = `aJ' if `have_D' { local y2 = `maxfwer' } else { local y2 = `fwerate' } local y3 = `targetfwer' local x3 = ((`y2'-`y3')*`x1'+(`y3'-`y1')*`x2')/(`y2'-`y1') local aJ = `x3' } if `count'==1{ if `have_D' local diff = `targetfwer' - `maxfwer' else local diff = `targetfwer' - `fwerate' if `diff'>0 local ++count else local aJ = `aJ' + (`diff'/(`K')) } local ++count } return scalar aJ = `aJ' } *__________________________________________________________________ if "`probs'"!="" { local rownames local colnames forvalues j = 1/`nstage' { local rownames `rownames' pass_stage`j' local prb`j' `p`j'0' forvalues k = 1/`K' { local prb`j' `prb`j'', `p`j'`k'' } } forvalues k = 0/`K' { local colnames `colnames' P(`k'arms) } local prb `prb1' forvalues j = 2/`nstage' { local prb `prb' \ `prb`j'' } mat P = (`prb') mat rownames P = `rownames' mat colnames P = `colnames' return matrix P = P } } /******************************************************************************* Output *******************************************************************************/ if "`esb'" != ""{ local vw 7 local vx 10 local vy 6 local vz 9 } local ww 9 local sfl %-`ww's local sfc %~`ww's local sfr %`ww's local wx 14 local sxl %-`wx's local sxc %~`wx's local sxr %`wx's local wy 5 local syl %-`wy's local syc %~`wy's local syr %`wy's local wz 7 local szl %-`wz's local szc %~`wz's local szr %`wz's if "`esb'" != ""{ local nitem 10 local dup = 80 } else { local nitem 8 local dup = (`nitem'-1) * `ww' + `wx' } local ww3 = `ww' * 3 local ww4 = `ww' * 4 di as text _n(2) "$title1" _n "{hline `hline'}" di as text "Sample size for a " as res "`armsS1'" as text "-arm " as res "`nstage'" as txt "-stage trial with time-to-event outcome" di as text "$title2" di as text "$title3" _n "{hline `hline'}" if `have_D' { local ss s di as text _n "Median survival time (I-outcome): " as res round(`median1', 0.1) as text " time units" di as text "Median survival time (D-outcome): " as res round(`median`nstage'', 0.1) as text " time units" } else { di as text _n "Note: I outcome and D outcome are identical" di as text "Median survival time: " as res round(`median1', 0.1) as text " time units" } di as text _n "Operating characteristics" _n "{hline `dup'}" if "`esb'" != ""{ // new di as text "Stage" _col(9) "Alpha" _col(16) "Alpha" _col(23) "Power" _col(30) "HR{c |}H0" /// _col(37) "HR{c |}H1" _col(45) "Crit.HR" _col(54) "Crit.HR" _col(64) "Length**" _col(74) "Time**" di as text _col(8) "(LOB)*" _col(16) "(ESB)*" _col(46) "(LOB)" _col(55) "(ESB)" _n "{hline `dup'}" } else { di as text "Stage" _col(8) `syr' "Alpha(LOB)*" _col(23) "Power" _col(32) "HR{c |}H0" /// _col(41) "HR{c |}H1" `sfr' "Crit.HR" `sfr' "Length**" `sfr' "Time**" _n "{hline `dup'}" } forvalues m = 1 / `nstage' { local m1 = `m' - 1 if `m'==1 { local ts = `tS1' } else local ts = `tS`m'' - `tS`m1'' if "`esb'" != "" { di as text %-`vy's "`m'" %`vw'.4f `alphaS`m'' %`vw'.4f `E`m'' %`vw'.3f `omegaS`m'' /// %`vw'.3f as txt `hr0S`m'' %`vw'.3f as txt `hr1S`m'' as res %`vz'.3f /// exp(`lndelS`m'') %`vz'.3f as res exp(`lndelE`m'') %`vx'.3f as res `ts' %`vx'.3f as res `tS`m'' } else { di as text `sfl' "`m'" %`ww'.4f `alphaS`m'' %`ww'.3f `omegaS`m'' /// %`ww'.3f as txt `hr0S`m'' %`ww'.3f as txt `hr1S`m'' as res %`ww'.3f /// exp(`lndelS`m'') %`ww'.3f as res `ts' %`ww'.3f as res `tS`m'' } } if `nstage' > 1 { if `armsS1'>2 { di as text "{hline `dup'}" if `have_D' { if "`esb'"=="" di as text `sxl' "Pairwise Error Rate" _col(34) %`ww'.4f as res `pwalpha' _cont else di as text `sxl' "Max. Pairwise Error Rate" _col(34) %`ww'.4f as res `pwalpha' _cont di as text _col(55) "Pairwise Power" %`ww'.4f as res `pwomega' if "`nofwer'"=="" di as text "Max. Familywise Error Rate (SE)" _col(34) %`ww'.4f as res `maxfwer' as text " (" %5.4f as res `se_maxfwer' as text ")" _cont di _n as txt "{hline `dup'}" } else { di as text `sxl' "Pairwise Error Rate" _col(27) %`ww'.4f as res `pwalpha' _cont di as text _col(50) "Pairwise Power" %`ww'.4f as res `pwomega' if "`nofwer'"=="" di as text "Familywise Error Rate (SE)" _col(27) %`ww'.4f as res `fwerate' as text " (" %5.4f as res `se_fwerate' as text ")" _cont di _n as txt "{hline `dup'}" } } else { if `have_D' { di as text "{hline `dup'}" if "`esb'"=="" di as text `sxl' "Pairwise Error Rate" _col(21) %`ww'.4f as res `pwalpha' _cont else di as text `sxl' "Max. Pairwise Error Rate" _col(34) %`ww'.4f as res `pwalpha' _cont if "`esb'"=="" di as text _col(42) "Pairwise Power" %`ww'.4f as res `bindingomega' else di as text _col(55) "Pairwise Power" %`ww'.4f as res `pwomega' if "`nofwer'"=="" & "`esb'"=="" di as text "Max. Error Rate" _col(21) %`ww'.4f as res `alphaS`nstage'' else if "`nofwer'"=="" di as text "Max. Error Rate" _col(34) %`ww'.4f as res `maxfwer' } else { di as text "{hline `dup'}" di as text `sxl' "Pairwise Error Rate" %`ww'.4f as res `pwalpha' _cont di as text _col(37) "Pairwise Power" %`ww'.4f as res `pwomega' } di as txt "{hline `dup'}" } } if "`esb'" != "" di as text "LOB = lack of benefit; ESB = efficacy stopping boundary" else di as text "LOB = lack of benefit" if `tstop'>0 { di as text "Note: patient accrual stopped at time " %6.3f `tstop' } di as text " * All alphas are one-sided" if "`esbstop'"=="stop"{ di as text " Error rates assume trial is terminated once an efficacious arm is identified" } di as text " ** Length (duration of each stage) is expressed in " as res "`lperiod'" as txt " periods and" di as text " assumes survival times are exponentially distributed." di as text " Time is expressed in cumulative periods." if "`fwercontrol'" != "" { di as text _n "{bf:Note}: the value of alpha specified at the final stage has been" di as text "replaced with the value required to control the FWER at `fwercontrol'." } *if `have_D' di as text " *** Calculated under global null hypothesis for I and D outcomes" /* di as txt " ** Correlations between hazard ratios estimated internally by the program" if `have_D' { di as txt " assuming corr(), correlation between survival times on I & D, is " as res %4.2f `corr' } */ di as text _n(1) "Sample size and number of events" local nitem 7 local dup = `nitem' * `ww' local dup2 = int((`ww' * (`nitem' - 1) / 2 - length("Stage 1")) / 2 - 0.5) local dup3 = 4 * `ww' local dup4 = 2*`dup2' + length("Stage 1") forvalues m = 1/`nstage' { di as text _dup(`dup2') _col(12) "{c -}" "Stage `m'" _dup(`dup2') "{c -}" di as text _skip(`ww') `sfr' "Overall" `sfr' "Control" `sfr' "Exper." di as text `sfl' "Arms" as txt %`ww'.0f `armsS`m'' %`ww'.0f 1 %`ww'.0f `armsS`m''-1 di as text `sfl' "Acc. rate" as txt %`ww'.0f `oaccS`m'' %`ww'.0f `acc0S`m'' %`ww'.0f `acc0S`m''*(`armsS`m''-1)*`Rstar' di as text `sfl' "Patients*" as res %`ww'.0f `ntotS`m'' %`ww'.0f `nS`m'' %`ww'.0f `nexpS`m'' di as text `sfl' "Events**" as res %`ww'.0f `etotS`m'' %`ww'.0f `eS`m'' %`ww'.0f `eexpS`m'' } di as text _col(12) _dup(`dup4') "{c -}" if `Rstar'!=1 { di as text "`Rstar' patients allocated to each E arm for every 1 to control arm." } di as text " * Patients are cumulative across stages" di as txt " ** Events are cumulative across stages, but are only displayed" di as txt " for those arms to which patients are still being recruited" tempname nstage1 scalar `nstage1' = `nstage' - 1 if `nstage'==2 { if `have_D' { di as text " ** Events are for I-outcome at stage 1, D-outcome at stage 2" } else di as text " ** Events are for the same outcome at stages 1 and 2" } else { if `have_D' { di as text " ** Events are for I-outcome at stages 1 to " `nstage1' ", D-outcome at stage `nstage'" } else di as text " ** Events are for the same outcome at all `nstage' stages" } if "`iterate'" == "noiterate" { di as text _n "[Note: computations carried out under H0, sample sizes may be too low.]" } local wy 8 if "`probs'"!="" { local K = `armsS1'-1 local dup2 = `wy' * (`armsS1' + 1) di as text _n "Probability of k experimental arms passing each stage under global null hypothesis" di as text _dup(`dup2') "{c -}" di as text %-`wy's "k(#arms)" _cont forvalues k = 0 / `K' { di as txt %`wy'.0f `k' _cont } di di as text _dup(`dup2') "{c -}" _cont forvalues j = 1 / `nstage' { di _n as text %-`wy's "Stage `j'" _cont forvalues k = 0 / `K' { di as res %`wy'.3f `p`j'`k'' _cont } } di _n as text _dup(`dup2') "{c -}" } forvalues i = 2 / `nstage' { return scalar deltaS`i' = exp(`lndelS`i'') return scalar eS`i'un = `eS`i'un' return scalar eS`i' = `eS`i'' return scalar eS`i'star = `eS`i'star' return scalar nS`i' = `nS`i'' return scalar tS`i' = `tS`i'' return scalar ntotS`i' = `ntotS`i'' return scalar nexpS`i' = `nexpS`i'' return scalar etotS`i' = `etotS`i'' return scalar eexpS`i' = `eexpS`i'' return scalar omegaS`i' = `omegaS`i'' } return scalar alpha = `pwalpha' if "`pwomega'" == "" return scalar omega = `pwomega' return matrix R = `RH0' if `have_D' & "`esb'" == "" { return scalar bindingpwer = `bindingpwer' return scalar bindingomega = `bindingomega' } if "`nofwer'"=="" & `armsS2'==2 { return scalar fwer = `fwerate' if "`nonbinding'"!="" return scalar se_fwer = `se_fwerate' return scalar pwomega = `pwomega' if `have_D' return scalar maxfwer = `maxfwer' if "`esb'"!="" if `have_D' return scalar se_maxfwer = `se_maxfwer' } if "`nofwer'"=="" & `armsS1'>2 { return scalar fwer = `fwerate' return scalar se_fwer = `se_fwerate' if `have_D' return scalar maxfwer = `maxfwer' if `have_D' return scalar mvnpmaxfwer = `mvnpmaxfwer' if `have_D' return scalar se_maxfwer = `se_maxfwer' return scalar pwomega = `pwomega' return scalar fwomega = `fwomega' return scalar allomega = `allomega' } noi di as txt _n "NSTAGE has completed successfully." end mata: void mvnprob(string scalar xx, string scalar vv, real scalar reps) { /* Assumes Hammersley sequences are to be generated, without antithetics. */ real vector opt x = st_matrix(xx) // row or col vector of args at which probability is required V = st_matrix(vv) // variance-covariance matrix opt = (2, reps, 1, 0) // 2 for Hammersley p = ghk( x, V, opt, rank=.) st_numscalar("r(p)", p) } end ******************************************************************************************** ******************************************************************************************** * v 1.0.0 PR 29aug2008. * based on v 1.0.0 PR 02May2001. cap program drop evfromtin4 program define evfromtin4, rclass /* Determine number of events expected given time and hazard or median survival. Assumes exponential survival. Hazard/median may have one or more values (latter for two or more stages). */ version 7 syntax , Accrual(string) TImes(string) [ Hazard(string) Median(string) TStop(string) ] if "`tstop'"!="" { confirm num `tstop' if `tstop' <= 0 { di as err "tstop() must be positive" exit 198 } } if "`median'`hazard'" == "" { di as err "must supply hazard() or median()" exit 198 } local nstage: word count `times' forvalues i = 1 / `nstage' { local t`i': word `i' of `times' confirm number `t`i'' local acc`i': word `i' of `accrual' confirm number `acc`i'' } if "`tstop'" != "" { if `tstop' >= `t`nstage'' { di as txt "[tstop occurs after end of trial, ignored]" local tstop } } if `nstage' > 1 { forvalues i = 1 / 2 { if "`hazard'" == "" { local median`i': word `i' of `median' local hazard`i' = ln(2) / `median`i'' } else { local hazard`i': word `i' of `hazard' local median`i' = ln(2) / `hazard`i'' } } } else { if "`hazard'" == "" { local median1: word 1 of `median' local hazard1 = ln(2) / `median1' } else { local hazard1: word 1 of `hazard' local median1 = ln(2) / `hazard1' } } /* d`j' = interval between time j and time j-1 N`j' = number at risk at time t`j' e`j' = cumulative number of events by t`j' If tstop exists: a = stage number just before tstop (i.e. time t`a' < tstop). a could be 0, if tstop occurs in (0, t1]. dstop = interval between tstop and t`a' */ local t0 0 local a -1 forvalues j = 1 / `nstage' { local j1 = `j' - 1 local d`j' = `t`j'' - `t`j1'' *noi di in red "j=`j' tj=`t`j'' tj1=`t`j1'' dj=`d`j''" /* if `d`j'' < 0 { *di as err "Time `j' must not be less than time `j1'" local errmess "Time `j' must not be less than time `j1'" di as err `"`errmess'"' return local errmess `errmess' exit 198 } */ if "`tstop'" != "" { if `tstop' > `t`j1'' & `tstop' <= `t`j'' { if reldif(`tstop', `t`j'') < 0.00001 { // stopping recruitment at `t`j'' local a `j' local dstop 0 } else { local a `j1' local dstop = `tstop' - `t`a'' } } } } // Note that Stage 1 assumes hazard `hazard1' if `nstage' == 1 { local haz `hazard1' } else local haz `hazard2' // Find number of events by t`nstage' local Nj1 0 local ej1 0 if `a'==0 { // tstop <= t1 F `dstop' `haz' local f = `tstop' - r(F) / `haz' local Ntstop = `acc1' * (`tstop' - `f') local etstop = `acc1' * `f' F "`t`nstage''-`tstop'" `haz' local e`nstage' = `etstop' + `Ntstop' * r(F) di "Stage `nstage': events = " %8.0g `e`nstage'' return scalar e`nstage' = `e`nstage'' return scalar atrisk = `Ntstop' exit } forvalues j = 1 / `nstage' { local j1 = `j' - 1 F `d`j'' `haz' local F = r(F) // F(d_j) local f = `d`j'' - `F' / `haz' // f(d_j) local Nj = (1 - `F') * `Nj1' + `acc`j'' * (`d`j'' - `f') // number at risk at t_j local ej = `ej1' + `acc`j'' * `f' + `Nj1' * `F' // events at t_j *if `j'==1 { noi di in red "j=1 ej=" `ej' " atrisk = " `Nj' } if `j' == `a' { // Recruitment stops at tstop if `dstop' > 0 { F `dstop' `haz' local F = r(F) local f = `dstop' - `F' / `haz' local jp1 = `j' + 1 local Ntstop = `Nj' * (1 - `F') + `acc`jp1'' * (`dstop' - `f') local etstop = `ej' + `acc`jp1'' * `f' + `Nj' * `F' } else { local Ntstop `Nj' local etstop `ej' } F "`t`nstage''-`tstop'" `haz' local ej = `etstop' + `Ntstop' * r(F) local Nj1 = `Ntstop' continue, break } local Nj1 `Nj' local ej1 `ej' } di "Stage `nstage': events = " %8.0g `ej' return scalar e`nstage' = `ej' return scalar atrisk = `Nj1' end ******************************************************************************************** ******************************************************************************************** * v 1.0.3 PR 18may2009. cap program drop timetoevn4 program define timetoevn4, rclass /* Determine time to given number of events assuming exponential survival. Hazard/median may have one or two values (latter for two stages). */ version 7 syntax , Accrual(string) Events(string) [ Hazard(string) Median(string) Tol(real 0.0001) /* */ TStop(real 0) ] if "`median'`hazard'" == "" { di as err "must supply hazard() or median()" exit 198 } local nstage: word count `events' forvalues i = 1 / `nstage' { local ev`i': word `i' of `events' local acc`i': word `i' of `accrual' cap confirm num `acc`i'' if c(rc) { di as err `"invalid accrual value, "`acc`i''""' exit 198 } } if `nstage' > 1 { forvalues i = 1 / 2 { if "`hazard'" == "" { local median`i': word `i' of `median' local hazard`i' = ln(2) / `median`i'' } else { local hazard`i': word `i' of `hazard' local median`i' = ln(2) / `hazard`i'' } } } else { if "`hazard'" == "" { local median1: word 1 of `median' local hazard1 = ln(2) / `median1' } else { local hazard1: word 1 of `hazard' local median1 = ln(2) / `hazard1' } } // Stage 1 - initial guess at time 1 (t1) is median1 local g `ev1' local t1 `median1' // Newton-Raphson iterative scheme to find time corresponding to ev1 events local i 0 quietly while abs(`g')>`tol' & `i'<100 { // `g' is difference between actual and target events if `tstop'==0 | `tstop' >= `t1' { cap evfromtin4, accrual(`acc1') times(`t1') hazard(`hazard1') if _rc { local errmess = r(errmess) di as err `"`r(errmess)'"' return local errmess `errmess' exit 498 } local g = `ev1' - r(e1) F `t1' `hazard1' local gprime = -`acc1' * r(F) } else { cap evfromtin4, accrual(`acc1') times(`t1') hazard(`hazard1') tstop(`tstop') if _rc { local errmess = r(errmess) di as err `"`r(errmess)'"' return local errmess `errmess' exit 498 } local g = `ev1' - r(e1) F `tstop' `hazard1' local Ntstop = `acc1' * r(F) / `hazard1' // no. at risk at tstop F "`t1'-`tstop'" `hazard1' // F(t1 - tstop) local gprime = -`Ntstop' * `hazard1' * (1 - r(F)) } if abs(`gprime')<1e-10 { local errmess "design is infeasible - insufficient patients to accrue `ev1' events in stage 1" di as err `"`errmess'"' return local errmess `errmess' exit 198 } local t1 = `t1' - `g'/`gprime' local ++i } return scalar t1 = `t1' return scalar err1 = `g' return scalar iter1 = `i' di "Stage 1: t = " %8.0g `t1' if `nstage' > 1 & `tstop' > 0 & `tstop' < `t1' { local errmess "design is infeasible - tstop occurs in stage 1 (too early - before final stage)" di as err `"`errmess'"' return local errmess `errmess' exit 198 } if `nstage'==1 { exit } local accrueS local tS forvalues m = 2 / `nstage' { local m1 = `m' - 1 local accrueS `accrueS' `acc`m1'' local tS `tS' `t`m1'' if `m' < `nstage' { /* Computing t`m', assuming have just successfully computed times t1,...,t`m1'. Compute number of I-events in (0, t(m-1)] with accrual rates 1, ..., m-1. In this version, we don't handle the possibility that tstop occurs during the first m-1 stages. */ // Get events up to stage m1 = `m'-1 qui evfromtin4, accrual(`accrueS') times(`tS') hazard(`hazard1' `hazard1') local ev21 = r(e`m1') if `ev21' >= `ev`m'' { di "Stage `m': already expecting `ev21' events in (0," %8.0g `t`m1'' "]" local t`m' = `t`m1'' return scalar t`m' = `t`m1'' return scalar ev21 = `ev21' continue // exit doesn't exit the m loop! } local g `ev`m'' local t`m' = `t`m1'' + 0.5 * `median1' local i 0 quietly while abs(`g') > `tol' & `i' < 500 { local dt`m' = `t`m'' - `t`m1'' if `dt`m'' <= 0 { local t`m' = 1.001 * `t`m1'' local dt`m' = `t`m'' - `t`m1'' } evfromtin4, accrual(`accrueS' `acc`m'') times(`tS' `t`m'') hazard(`hazard1' `hazard1') local atrisk = r(atrisk) // no. at risk at stage `m1' local g = `ev`m'' - r(e`m') // discrepancy function whose solution for r(e`m') at g = 0 is sought F `dt`m'' `hazard1' local gprime = -`acc`m'' * r(F) - `atrisk' * `hazard1' * (1 - r(F)) if abs(`gprime') < 1e-10 { local errmess "design is infeasible - insufficient patients to accrue `ev`m'' events in stage `m'" di as err `"`errmess'"' return local errmess `errmess' exit 498 } local t`m' = `t`m'' - `g' / `gprime' local ++i } return scalar t`m' = `t`m'' return scalar err`m' = `g' return scalar iter`m' = `i' di "Stage `m': t = " %8.0g `t`m'' if `tstop' > 0 & `tstop' < `t`m'' { local errmess "design is infeasible - tstop occurs in stage `m' (too early - before final stage)" di as err `"`errmess'"' return local errmess `errmess' exit 498 } } else /* `m' == `nstage' */ { // Get D-events up to stage m - 1 = `m1' qui evfromtin4, accrual(`accrueS') times(`tS') hazard(`hazard2' `hazard2') local ev21 = r(e`m1') if `ev21' >= `ev`m'' { di "Stage `m': already expecting `ev21' events in (0," %8.0g `t`m1'' ")" return scalar t`m' = `t`m1'' return scalar ev21 = `ev21' return local enough enough continue, break // exit } local g `ev`m'' local t`m' = `t`m1'' + 0.5 * `median2' // starting value local i 0 quietly while abs(`g') > `tol' & `i' < 500 { local dt`m' = `t`m'' - `t`m1'' if `dt`m'' <= 0 { local t`m' = 1.001 * `t`m1'' local dt`m' = `t`m'' - `t`m1'' } if `tstop' == 0 | (`tstop' > 0 & `tstop' >= `t`m'') { evfromtin4, accrual(`accrueS' `acc`m'') times(`tS' `t`m'') hazard(`hazard2' `hazard2') local atrisk = r(atrisk) // no. at risk at stage `m1' local g = `ev`m'' - r(e`m') // discrepancy function to be optimized to zero F `dt`m'' `hazard2' local gprime = -`acc`m'' * r(F) - `atrisk' * `hazard2' * (1 - r(F)) } else { // recruitment ceases between stage m-1 and stage m evfromtin4, accrual(`accrueS' `acc`m'') times(`tS' `t`m'') hazard(`hazard2' `hazard2') tstop(`tstop') local g = `ev`m'' - r(e`m') local atrisk = r(atrisk) // no. at risk at tstop F "`t`m'' - `tstop'" `hazard2' local gprime = -`atrisk' * `hazard2' * (1 - r(F)) } if abs(`gprime') < 1e-10 { di as err "Design is infeasible - insufficient patients to accrue `ev`m'' events in stage `m'" exit 498 } local t`m' = `t`m'' - `g' / `gprime' local ++i } return scalar t`m' = `t`m'' return scalar err`m' = `g' return scalar iter`m' = `i' di "Stage `m': t = " %8.0g `t`m'' } } end program define F, rclass * Evaluate exponential distribution function at time t with hazard h args t h return scalar F=(1-exp(-(`h')*(`t'))) end /* hrcorrnstage for calculating between stage correlation using simulation. v 1.3 BO/PR 10Oct13. }*/ program define hrcorrnstage, rclass version 10.0 syntax, accrue(string) t(string) HR0(string) HR1(string) nstage(int) rep(int) /// [ARAtio(string) Rho(string) ALpha(string) Omega(string) n(string) e(string) seed(int -1) /// INToutcome(string) CI(int 95) savehr(string) HYPothesis(string)] if `seed'>0 set seed `seed' if `nstage' <= 1 { di as err "nstage must be more than 1" exit 198 } if `ci' >= 100 { di as err "ci must be smaller than 100" exit 198 } if `"`rho'"' != "" { confirm num `rho' if `rho' > 1 { di as err "rho must be smaller than 1" exit 198 } } if `"`alpha'"' == "" & `"`omega'"' == "" & `"`n'"' == "" & `"`e'"' == "" { di as err "specify alpha and omega or n and e" exit 198 } if (`"`alpha'"' != "" & `"`omega'"' == "") | (`"`alpha'"' == "" & `"`omega'"' != "") { di as err "omega and alpha must be specified together" exit 198 } if (`"`n'"' != "" & `"`e'"' == "") | (`"`n'"' == "" & `"`e'"' != "") { di as err "n and e must be specified together" exit 198 } local arm forvalues k = 1 / `nstage' { local arm `arm' 2 } // Assign values of accrue n e for all stages local opts accrue n e local nopts: word count `opts' tokenize `opts' forvalues i = 1 / `nopts' { local opt ``i'' forvalues j = 1 / `nstage' { local stage S`j' local `opt'`stage': word `j' of ``opt'' confirm number ``opt'`stage'' } } forvalues k = 1 / `nstage' { local n`k' = ceil(`nS`k'') local e`k' = ceil(`eS`k'') } local item: word count `hr0' local opts2 t hr0 hr1 local nopts2: word count `opts2' tokenize `opts2' forvalues i = 1 / `nopts2' { local opt2 ``i'' forvalues j = 1 / `item' { local stage `j' local `opt2'`stage': word `j' of ``opt2'' confirm number ``opt2'`stage'' } } * Rstar is allocation ratio to E arm: #pts to 1E for 1 pt to C if "`aratio'" != "" { confirm num `aratio' local Rstar `aratio' } else local Rstar 1 local p = 1/(1+`Rstar') local intstage = `nstage'-1 *local hrlist forvalues j = 0/1 { local hrlistH`j' forvalues k = 1 / `nstage' { local hrlistH`j' `hrlistH`j'' hr`k'H`j' } } *local hroslist forvalues j = 0/1 { local hroslistH`j' forvalues k = 1 / `intstage' { local hroslistH`j' `hroslistH`j'' hros`k'H`j' } } preserve tempname tmp tempfile output tempvar x1H0 x2H0 x1H1 x2H1 x3H0 x3H1 y1H0 y1H1 y2H0 y2H1 group lamb10 lamb20 lamb11 lamb21 /// xtot1H0 xtot1H1 stage surid1H0 surid1H1 osH0 osH1 surid1_osH0 surid1_osH1 xtot1_x2H0 xtot1_x2H1 d10 d11 local current 0 local postf forvalues j=1/`nstage'{ forvalues i=0/1 { local postf "`postf' d`j'`i'" } } postfile `tmp' `hrlistH0' `hrlistH1' `hroslistH0' `hroslistH1' `postf' using `"`output'"' qui forvalues i=1/`rep' { local aid `i' drop _all set obs `n1' * STAGE 1 gen byte `group' = _n>(_N*`p') local lambda20 = log(2)/`t2' local lambda30 = log(2)/`t1' local lambda21 = (log(2)*`hr12')/`t2' local lambda31 = (log(2)*`hr11')/`t1' if "`intoutcome'" == "" | "`intoutcome'"=="I" { local lo0 =`lambda30' - `lambda20' local hi0 =`lambda30' bisect hazmini X `lambda20' `rho' returns exp Lambda=`lambda30' from `lo0' to `hi0' local lambda10 = $S_1 local lo1 =`lambda31'-`lambda21' local hi1 =`lambda31' bisect hazmini X `lambda21' `rho' returns exp Lambda=`lambda31' from `lo1' to `hi1' local lambda11 = $S_1 mkbilogn `lamb10' `lamb20',r(`rho') /* lamb10 and lamb20 are correlated lognormals */ /*The following commands generate PS and OS under H0 and H1*/ forval i = 1/2{ gen `x`i'H0' = -ln(normal(ln(`lamb`i'0')))*(1/`lambda`i'0') /*Generating PS and OS under H0*/ } forval i = 1/2 { gen `x`i'H1' = -ln(normal(ln(`lamb`i'0')))*(1/`lambda`i'0') if `group' == 0 } mkbilogn `lamb11' `lamb21',r(`rho') /* lamb11 and lamb21 are correlated lognormals */ forval i = 1/2 { replace `x`i'H1' = -ln(normal(ln(`lamb`i'1')))*(1/`lambda`i'1') if `group' == 1 /*Generating PS and OS under H1*/ } forval i = 0/1 { gen `x3H`i'' = min(`x1H`i'',`x2H`i'') /*Generating PFS and OS under H0 and H1*/ } } else if "`intoutcome'" == "D" { gen `x2H0' = -ln(1-uniform())/`lambda20' /* exp dist under H0*/ gen `x2H1' = -ln(1-uniform())/`lambda20' if `group' == 0 /* exp dist with given hazard */ replace `x2H1' = -ln(1-uniform())/`lambda21' if `group' == 1 /* exp dist with given hazard */ forval i = 0/1 { gen `x3H`i''=`x2H`i'' /* It is needed for later stages because x3 will be modified later in stage 1 due to censoring*/ } } gen `stage'=1 forval i = 0/1 { gen `y1H`i'' = (`n1'/`accrueS1')*uniform() /*arrival times*/ gen `xtot1H`i'' = `x3H`i'' + `y1H`i'' sort `group' `xtot1H`i'' local timeev1H`i' = `xtot1H`i''[`e1'] gen byte `surid1H`i'' = (`xtot1H`i'') <= (`timeev1H`i'') replace `x3H`i'' = `timeev1H`i''-`y1H`i'' if `surid1H`i'' == 0 /*admin. censoring*/ replace `x3H`i'' = 0 if `x3H`i'' < 0 stset `x3H`i'', failure(`surid1H`i'') logranksb _t _d, by(`group') local expec1 = r(expected2) local obs1 = r(observed2) local expec0 = r(expected1) local obs0 = r(observed1) local hr1H`i' = (`obs1'/`expec1')/(`obs0'/`expec0') gen `osH`i'' = `x2H`i'' gen `xtot1_x2H`i'' = `osH`i'' + `y1H`i'' gen byte `surid1_osH`i'' = (`xtot1_x2H`i'') <= (`timeev1H`i'') *Find corresponding # D events when interim triggered by I events * count if `surid1_osH`i''==1 & `group'==0 local `d1`i'' = r(N) scalar d1`i' = r(N) *************************************************** replace `osH`i'' = `timeev1H`i''-`y1H`i'' if `surid1_osH`i'' == 0 /*admin. censoring*/ replace `osH`i'' = 0 if `osH`i'' < 0 stset `osH`i'', failure(`surid1_osH`i'') logranksb _t _d, by(`group') local expec1 = r(expected2) local obs1 = r(observed2) local expec0 = r(expected1) local obs0 = r(observed1) local hros1H`i' = (`obs1'/`expec1')/(`obs0'/`expec0') gen `y2H`i'' = . } /* STAGE 2 to the penultimate stage. It uses survival times simulated in Stage 1 but cuts off after a higher number of events and runs survival analysis need to add extra patients accrued after Stage 1. */ if `intstage'>1 { forvalues k = 2 / `intstage' { tempvar group2 x4H0 x4H1 x5H0 x5H1 la40 la50 la41 la51 y4H0 y4H1 y5H0 y5H1 stage2 xtot`k'H0 xtot`k'H1 /// surid`k'H0 surid`k'H1 osH0 osH1 surid`k'_osH0 surid`k'_osH1 xtot`k'_x2H0 xtot`k'_x2H1 d`k'0 d`k'1 set obs `n`k'' local j=`k'-1 local ni`k'=`n`k''-`n`j'' gen `group2' = 0 if _n < (`ni`k''*`p') | _n == (`ni`k''*`p') replace `group2' = 1 if _n < (`ni`k'') & _n > (`ni`k''*`p') | _n == (`ni`k'') if "`intoutcome'"=="" | "`intoutcome'"=="I" { local lam40 = `lambda10' local lam41 = `lambda11' local lam50 = `lambda20' local lam51 = `lambda21' mkbilogn `la40' `la50',r(`rho') // la40 and la50 are correlated lognormals // The following commands generate PS and OS under H0 and H1 forval i = 4/5{ gen `x`i'H0' = -ln(normal(ln(`la`i'0')))*(1/`lam`i'0') // Generating PS and OS under H0 } forval i=4/5{ gen `x`i'H1' = -ln(normal(ln(`la`i'0')))*(1/`lam`i'0') if `group2' == 0 } mkbilogn `la41' `la51',r(`rho') // lamb10 and lamb20 are correlated lognormals forval i=4/5{ replace `x`i'H1' = -ln(normal(ln(`la`i'1')))*(1/`lam`i'1') if `group2' == 1 } forval i = 0/1 { gen `y4H`i'' = `timeev`j'H`i''+((`ni`k''/`accrueS`k'')*uniform()) if _n<(`ni`k'') | _n == (`ni`k'') gen `y5H`i'' = . if _n < (`ni`k'') | _n == (`ni`k'') } gen `stage2' = `k' if _n < (`ni`k'') | _n == (`ni`k'') local nmax = max(`ni`k'',`n`j'') drop if _n > `nmax' if `n`j''>=`ni`k'' { stack `group' `x1H0' `x1H1' `x2H0' `x2H1' `y1H0' `y1H1' `y2H0' `y2H1' `stage' /// `group2' `x4H0' `x4H1' `x5H0' `x5H1' `y5H0' `y5H1' `y4H0' `y4H1' `stage2', /// into(`group' `x1H0' `x1H1' `x2H0' `x2H1' `y1H0' `y1H1' `y2H0' `y2H1' `stage') clear } else { stack `group2' `x4H0' `x4H1' `x5H0' `x5H1' `y5H0' `y5H1' `y4H0' `y4H1' `stage2' /// `group' `x1H0' `x1H1' `x2H0' `x2H1' `y1H0' `y1H1' `y2H0' `y2H1' `stage', /// into(`group' `x1H0' `x1H1' `x2H0' `x2H1' `y1H0' `y1H1' `y2H0' `y2H1' `stage') clear } drop if _n > `n`k'' forval i = 0/1 { replace `y1H`i'' = `y2H`i'' if `y1H`i'' == . gen `x3H`i'' = min(`x1H`i'',`x2H`i'') /*Generating PFS and OS under H0 and H1*/ } } else if "`intoutcome'"=="D" { gen `x5H0' = -ln(1-uniform())/`lambda20' gen `x5H1' = -ln(1-uniform())/`lambda20' if `group2' == 0 /* exp dist with given hazard */ replace `x5H1' = -ln(1-uniform())/`lambda21' if `group2' == 1 /* exp dist with given hazard */ forval i = 0/1 { gen `y4H`i'' = `timeev`j'H`i''+((`ni`k''/`accrueS`k'')*uniform()) if _n<(`ni`k'') | _n == (`ni`k'') gen `y5H`i'' = . if _n<(`ni`k'') | _n==(`ni`k'') } gen `stage2'=`k' if _n<(`ni`k'') | _n == (`ni`k'') local nmax = max(`ni`k'',`n`j'') drop if _n > `nmax' if `n`j''>=`ni`k'' { stack `group' `x2H0' `x2H1' `y1H0' `y1H1' `y2H0' `y2H1' `stage' /// `group2' `x5H0' `x5H1' `y5H0' `y5H1' `y4H0' `y4H1' `stage2', /// into(`group' `x2H0' `x2H1' `y1H0' `y1H1' `y2H0' `y2H1' `stage') clear } else { stack `group2' `x5H0' `x5H1' `y5H0' `y5H1' `y4H0' `y4H1' `stage2' /// `group' `x2H0' `x2H1' `y1H0' `y1H1' `y2H0' `y2H1' `stage', /// into(`group' `x2H0' `x2H1' `y1H0' `y1H1' `y2H0' `y2H1' `stage') clear } drop if _n > `n`k'' forval i = 0/1 { replace `y1H`i'' = `y2H`i'' if `y1H`i'' == . gen `x3H`i'' = `x2H`i'' /* It is needed for later stages because x3 will be modified later in stage 1 due to censoring*/ } } forval i = 0/1 { gen `xtot`k'H`i'' = `x3H`i''+`y1H`i'' sort `group' `xtot`k'H`i'' local timeev`k'H`i' = `xtot`k'H`i''[`e`k''] gen byte `surid`k'H`i'' = (`xtot`k'H`i'') <= (`timeev`k'H`i'') replace `x3H`i'' = `timeev`k'H`i''-`y1H`i'' if `surid`k'H`i''==0 /*admin. censoring*/ replace `x3H`i''=0 if `x3H`i''<0 stset `x3H`i'', failure(`surid`k'H`i'') logranksb _t _d, by(`group') local expec1 = r(expected2) local obs1 = r(observed2) local expec0 = r(expected1) local obs0 = r(observed1) local hr`k'H`i' = (`obs1'/`expec1')/(`obs0'/`expec0') gen `osH`i''=`x2H`i'' gen `xtot`k'_x2H`i'' = `osH`i''+`y1H`i'' gen byte `surid`k'_osH`i'' = (`xtot`k'_x2H`i'') <= (`timeev`k'H`i'') // Find corresponding # D-events when interim k triggered by I-events count if `surid`k'_osH`i''==1 & `group'==0 local `d`k'`i'' = r(N) scalar d`k'`i' = r(N) *************************************************** replace `osH`i'' = `timeev`k'H`i''-`y1H`i'' if `surid`k'_osH`i'' == 0 /*admin. censoring*/ replace `osH`i'' = 0 if `osH`i'' < 0 stset `osH`i'', failure(`surid`k'_osH`i'') logranksb _t _d, by(`group') local expec1=r(expected2) local obs1=r(observed2) local expec0=r(expected1) local obs0=r(observed1) local hros`k'H`i' = (`obs1'/`expec1')/(`obs0'/`expec0') } } } /* Last stage: anaylsis, i.e calculating the HR on primary outcome. */ tempvar group2 x5H0 x5H1 y4H0 y4H1 y5H0 y5H1 stage`nstage' xtot`nstage'H0 xtot`nstage'H1 surid`nstage'H0 surid`nstage'H1 d`nstage'0 d`nstage'1 set obs `n`nstage'' local nD = `n`nstage'' - `n`intstage'' gen `group2' = 0 if _n < (`nD'*`p') | _n == (`nD'*`p') replace `group2' = 1 if _n < (`nD') & _n > (`nD'*`p') | _n == (`nD') gen `x5H0' = -ln(1-uniform())/`lambda20' /* exp dist under the null hypothesis */ gen `x5H1' = -ln(1-uniform())/`lambda20' if `group2'==0 /* exp dist under the H1 */ replace `x5H1' = -ln(1-uniform())/`lambda21' if `group2'==1 /* exp dist under the H1 */ forval i = 0/1 { gen `y4H`i'' = `timeev`intstage'H`i''+((`nD'/`accrueS`nstage'')*uniform()) if _n < (`nD') | _n == (`nD') gen `y5H`i'' = . if _n < (`nD') | _n == (`nD') } gen `stage`nstage'' = `nstage' if _n < (`nD') | _n == (`nD') local nDmax = max(`n`intstage'',`nD') drop if _n > `nDmax' if `n`intstage'' >= `nD' { stack `group' `x2H0' `x2H1' `y1H0' `y1H1' `y2H0' `y2H1' `stage' /// `group2' `x5H0' `x5H1' `y5H0' `y5H1' `y4H0' `y4H1' `stage`nstage'', /// into(`group' `x2H0' `x2H1' `y1H0' `y1H1' `y2H0' `y2H1' `stage') clear } else { stack `group2' `x5H0' `x5H1' `y5H0' `y5H1' `y4H0' `y4H1' `stage`nstage'' /// `group' `x2H0' `x2H1' `y1H0' `y1H1' `y2H0' `y2H1' `stage', /// into(`group' `x2H0' `x2H1' `y1H0' `y1H1' `y2H0' `y2H1' `stage') clear } drop if _n > `n`nstage'' forval i = 0/1 { replace `y1H`i'' = `y2H`i'' if `y1H`i'' == . gen `xtot`nstage'H`i'' = (`y1H`i''+`x2H`i'') sort `group' `xtot`nstage'H`i'' local timeev`nstage'H`i' = `xtot`nstage'H`i''[`e`nstage''] gen byte `surid`nstage'H`i'' = (`xtot`nstage'H`i'') <= (`timeev`nstage'H`i'') * # D-events at final stage - should equal eJ input by user/nstage* count if `surid`nstage'H`i''==1 & `group'==0 local `d`nstage'`i'' = r(N) scalar d`nstage'`i' = r(N) *************************************************** replace `x2H`i''=`timeev`nstage'H`i'' - `y1H`i'' if `surid`nstage'H`i''==0 /*admin censoring*/ stset `x2H`i'', failure(`surid`nstage'H`i'') logranksb _t _d, by(`group') local chi31=r(chi2) local expec1=r(expected2) local obs1=r(observed2) local expec0=r(expected1) local obs0=r(observed1) local hr`nstage'H`i' = (`obs1'/`expec1')/(`obs0'/`expec0') } *local hrsp local par1 "(" local par2 ")" forvalues j = 0/1 { local hrspH`j' forvalues k = 1 / `nstage' { local hrspH`j' `hrspH`j'' `par1'`hr`k'H`j''`par2' } } *local hrossp local par1 "(" local par2 ")" forvalues j = 0/1 { local hrosspH`j' forvalues k = 1 / `intstage' { local hrosspH`j' `hrosspH`j'' `par1'`hros`k'H`j''`par2' } } local to_post // NEW forvalues j=1/`nstage'{ forvalues i=0/1 { local to_post "`to_post' (d`j'`i')" } } di "to_post = " `to_post' // NEW post `tmp' `hrspH0' `hrspH1' `hrosspH0' `hrosspH1' `to_post' // NEW local aidperc = round(100*`aid'/`rep',0.5) *if mod(`aid', 500) == 0 noi di as txt ".. `aidperc'%", _cont if mod(`aidperc', 2) == 0 & mod(`aidperc', 10) != 0 & `aidperc' != `current' { noi di as text . _cont local current = `aidperc' } if mod(`aidperc', 10) == 0 & `aidperc' != `current' { noi di as txt "`aidperc'%" _cont local current = `aidperc' } *noi di as text "." _cont } postclose `tmp' use `"`output'"', clear /* need to see whether clear is needed or use preserve-restore command*/ * Get mean # OS events at each stage from postfile * forvalues j=1/`nstage' { forvalues i=0/1 { summ d`j'`i', meanonly local d`j'`i' = r(mean) local d`j'`i' = ceil(`d`j'`i'') } } if "`savehr'" != "" { label var hr`nstage'H0 "Estimated HR on primary outcome at the final stage under H0" label var hr`nstage'H1 "Estimated HR on primary outcome at the final stage under H1" forvalues j=1/`intstage' { label var hr`j'H0 "Estimated HR on intermidate outcome at stage `j' under H0" label var hr`j'H1 "Estimated HR on intermidate outcome at stage `j' under H1" label var hros`j'H0 "Estimated HR on primary outcome at stage `j' under H0" label var hros`j'H1 "Estimated HR on primary outcome at stage `j' under H1" } if "`intoutcome'"=="D" { drop `hroslistH0' `hroslistH1' } qui save "`savehr'", replace } forvalues j = 0/1 { forvalues v = 1 / `nstage' { tempvar lnhr`v'H`j' gen `lnhr`v'H`j'' = log(hr`v'H`j') } } forvalues j = 0/1 { local lnhrlistH`j' forvalues w = 1 / `nstage' { local lnhrlistH`j' `lnhrlistH`j'' `lnhr`w'H`j'' } } forvalues j = 0/1 { tempname corrhrH`j' corrlnhrH`j' qui cor `hrlistH`j'' matrix `corrhrH`j'' = r(C) qui cor `lnhrlistH`j'' matrix `corrlnhrH`j'' = r(C) forvalues h = 1 / `intstage' { local h1 = `h' + 1 forvalues hh = `h1' / `nstage' { local cor`hh'`h'H`j' = `corrhrH`j''[`hh',`h'] local cor`h'`hh'H`j' = `cor`hh'`h'H`j'' local corln`hh'`h'H`j' = `corrlnhrH`j''[`hh',`h'] local corln`h'`hh'H`j' = `corln`hh'`h'H`j'' } } } if "`intoutcome'"=="" | "`intoutcome'"=="I" { forvalues j = 0/1 { forvalues f = 1 / `intstage' { qui corr hr`f'H`j' hros`f'H`j' local corrfix`f'H`j' = r(rho) local c`f'H`j' = (1.1*`corrfix`f'H`j'') /* This formula has been suggested in Royston et al (2009) MAMS Stop Guide Stat. Med. paper */ } } } forvalues j = 0/1 { tempname corrRIH`j' matrix `corrRIH`j'' = I(`nstage') forvalues m = 1 / `nstage' { return scalar d`m'`j' = `d`m'`j'' // Saves D-events at interim analyses based on I-events local m1 = `m'+1 forvalues n = `m1' / `nstage' { local cor_RI`m'`n'H`j' = sqrt(`e`m''/`e`n'') /* RI stands for Royston & Isham */ matrix `corrRIH`j''[`n',`m'] = `cor_RI`m'`n'H`j'' matrix `corrRIH`j''[`m',`n'] = `cor_RI`m'`n'H`j'' } if ("`intoutcome'"=="" | "`intoutcome'"=="I") & `m'<`nstage' { matrix `corrRIH`j''[`nstage',`m']= `c`m'H`j''*`cor_RI`m'`nstage'H`j'' matrix `corrRIH`j''[`m',`nstage']= `c`m'H`j''*`cor_RI`m'`nstage'H`j'' } } } forvalues j = 0/1 { local loghrlistH`j' forvalues k = 1 / `nstage' { local loghrlistH`j' `loghrlistH`j'' loghr`k'H`j' } } forvalues j = 0/1 { matrix rownames `corrRIH`j'' = `hrlistH`j'' matrix colnames `corrRIH`j'' = `hrlistH`j'' matrix rownames `corrlnhrH`j'' = `loghrlistH`j'' matrix colnames `corrlnhrH`j'' = `loghrlistH`j'' } forvalues k = 0/1 { forvalues i = 1 / `intstage' { local i1 = `i' + 1 forvalues j = `i1' / `nstage' { return scalar corr`i'`j'H`k' = `cor`i'`j'H`k'' return scalar corrln`i'`j'H`k' = `corln`i'`j'H`k'' if "`intoutcome'" == "D" { return scalar corr_RI`i'`j'H`k'=`cor_RI`i'`j'H`k'' } if "`intoutcome'" == "" | "`intoutcome'"=="I" { return scalar c`i'H`k'=`c`i'H`k'' } } } } * Generate correlation matrix based on D-events for ESB when I!=D * if "`intoutcome'"=="" | "`intoutcome'"=="I" { forvalues j = 0/1 { tempname corrEB`j' matrix `corrEB`j'' = I(`nstage') forvalues m = 1 / `nstage' { local m1 = `m'+1 forvalues n = `m1' / `nstage' { local cor_EB`m'`n'H`j' = sqrt(`d`m'`j''/`d`n'`j'') /* RI stands for Royston & Isham */ matrix `corrEB`j''[`n',`m'] = `cor_EB`m'`n'H`j'' matrix `corrEB`j''[`m',`n'] = `cor_EB`m'`n'H`j'' } } } } forvalues j = 0/1 { return matrix CorrhrH`j' = `corrhrH`j'' return matrix CorrlnhrH`j' = `corrlnhrH`j'' return matrix Corrhr_RIH`j' = `corrRIH`j'' return matrix Corrhr_EB`j' = `corrEB`j'' //NEW } clear erase `output' restore end // of hrcorrnstage ** version 1.0.0 PR 06mar2004. program define hazmin, rclass version 8 /* Calculates hazard for minimum of 2 correlated exponentials with correlation rho in original bivariate normals. */ syntax , L1(string) L2(string) Rho(string) [Median] tokenize `l1' `l2' `rho' while "`1'"!="" { local 1=(`1') confirm number `1' mac shift } if `rho'<=0 | `rho'>=1 { di as err "rho() must be between 0 and 1" exit 198 } if `l1'<=0 | `l2'<=0 { di as err "l1() and l2() must be positive" exit 198 } tempname b0 b1 power beta lambda scalar `b0'=8.848 scalar `b1'=-8.063 scalar `power'=0.1 if "`median'"!="" { /* convert medians to hazards */ forvalues j=1/2 { local l`j'=ln(2)/`l`j'' } } if `l1'<`l2' { local lambda1 `l2' local lambda2 `l1' } else { local lambda1 `l1' local lambda2 `l2' } scalar `beta'=`b0'+`b1'*(`lambda2'/`lambda1')^`power' scalar `lambda'=`lambda1'+`lambda2'*(1-`rho')^`beta' di as text "Lambda (hazard) = " `lambda' return scalar beta=`beta' return scalar lambda=`lambda' end ** version 1.0.0 PR 06mar2004. program define hazmini version 8 /* Evaluates lambda (hazard of min of 2 correlated exponentials) for given l1, l2, rho, assuming nomedian. */ * Args 1=l1, 2=l2, 3=rho. Stores result in scalar Lambda. qui hazmin, l1(`1') l2(`2') rho(`3') scalar Lambda=r(lambda) end /* mkbilogn2 using mkbilogn.ado version 1.1.1 spj revised 27 Jan 98 STB-48 sg105 Syntax: mkbilogn var1 var2, r(#) m1(#) s1(#) m2(#) s2(#) [defaults #=0.5,0,1,0,1; respectively]. */ program define mkbilogn version 5 local varlist "req new min(2) max(2)" local options "Rho(real 0.5) m1(real 0) m2(real 0) s1(real 1) s2(real 1)" parse "`*'" if `rho' < -1 | `rho' >= 1 { di in red "Need rho s.t. -1 <= rho < 1" exit 198 /* need rho ~= 1 or Cholesky fails */ } if `s1' <=0 | `s2' <= 0 { di in red "Std. dev. must be positive" exit 198 } parse "`varlist'", parse(" ") di "Creating 2 r.v.s X1 X2 s.t. x1=log(X1), x2=log(X2) are bivariate" di " Normal with mean(x1) = `m1' ; mean(x2) = `m2' ; s.d.(x1) = `s1' ;" di " s.d.(x2) = `s2' ; corr(x1,x2) = `rho' " /* Method of creation based on Stata FAQ at http://www.stata.com/support/faqs/stat/mvnorm.html */ tempname a1 a2 A P var1 var2 c1 c2 lnc1 lnc2 matrix `P' = (1,`rho'\ `rho',1) matrix `A' = cholesky(`P') matrix colnames `A' = `lnc1' `lnc2' quietly { gen `c1' = exp(invnorm(uniform())) gen `c2' = exp(invnorm(uniform())) gen `lnc1' = ln(`c1') gen `lnc2' = ln(`c2') matrix `a1' = `A'[1,.] matrix score `var1' = `a1' matrix `a2' = `A'[2,.] matrix score `var2' = `a2' replace `1' = exp(`s1'*`var1' + `m1') replace `2' = exp(`s2'*`var2' + `m2') } end /* Note: mean,variances,corr refer to mean,variances,corr of the log of the variables. Mean of lognormal distribution: exp[mu + (sig_sq/2)] Variance of lognormal distribution: [exp(2*mu)]*[exp(2*sig_sq) - exp(sig_sq)] */ * version 1.0 SB 06Mar2004 * version 7.0.3 18sep2000 program define logranksb /* timevar [deadvar] [, by(group) t0(t0) id(tvid)] */, rclass version 6 syntax varlist(min=1 max=2) [if] [in] [fw iw] [, /* */ BY(varlist) CHECK Detail ID(varname) LOGRANK /* */ MAT(string) T0(varname) noTItle /* */ STrata(varlist) TVid(varname) trend] tokenize `varlist' if `"`tvid'"' != `""' { local id `"`tvid'"' local tvid } if `"`strata'"' != `""' { if `"`detail'"' != `""' { if `"`check'"' != `""' { tempname v V vi Vi local matopt `"mat(`vi' `Vi')"' } tempvar touse sv j mark `touse' `if' `in' [`weight'`exp'] markout `touse' `varlist' `t0' markout `touse' `by' `strata', strok sort `touse' `strata' qui { by `touse' `strata': /* */ gen `sv'=cond(_n==1 & `touse',1,0) replace `sv' = sum(`sv') qui gen long `j' = _n compress `j' `sv' } if `"`t0'"' != `""' { local t0opt `"t0(`t0')"' } if `"`id'"' != `""' { local idopt `"id(`id')"' } local nsv = `sv'[_N] Title `"`title'"' `"`strata'"' local i 1 while `i' <= `nsv' { qui sum `j' if `sv'==`i' local x = `strata'[_result(5)] di _n in gr `"-> `strata' = `x'"' logrank `varlist' [`weight'`exp'] /* */ if `touse' & `sv'==`i' /* */ , by(`by') `t0opt' `idopt' `matopt' notitle if `"`check'"' != `""' { if `i'==1 { mat `v' = `vi' mat `V' = `Vi' } else { mat `v' = `v' + `vi' mat `V' = `V' + `Vi' } } local i = `i' + 1 } di di in gr `"-> Total"' logrank `varlist' [`weight'`exp'] if `touse' /* */ , by(`by') `t0opt' `idopt' strata(`strata') /* */ `trend' notitle if `"`check'"' != `""' { mat `V' = syminv(`V') mat `V' = `v'*`V' mat `V' = `V'*`v' ' di `"CHECK: "' `V'[1,1] global S_7 = `V'[1,1] } exit } } local t1 `"`1'"' if `"`2'"'==`""' { tempvar dead qui gen byte `dead' = 1 } else local dead `"`2'"' tempvar touse mark `touse' `if' `in' [`weight'`exp'] markout `touse' `t1' `dead' markout `touse' `by' `strata', strok if `"`t0'"'!=`""' & `"`id'"'!=`""' { local id } if `"`t0'"'==`""' & `"`id'"'==`""' { tempvar t0 qui gen byte `t0' = 0 } else if `"`t0'"' != `""' { markout `touse' `t0' } else if `"`id'"'!=`""' { markout `touse' `id' quietly { sort `touse' `id' `t1' local ty : type `t1' by `touse' `id': gen `ty' `t0' = /* */ cond(_n==1,0,`t1'[_n-1]) } capture assert `t1'>`t0' if _rc { di in red `"repeated records at same `t1' within `id'"' exit 498 } } capture assert `t1'>0 if `touse' if _rc { di in red `"survival time `t1' <= 0"' exit 498 } capture assert `t0'>=0 if `touse' if _rc { di in red `"entry time `t0' < 0"' exit 498 } capture assert `t1'>`t0' if `touse' if _rc { di in red `"entry time `t0' >= survival time `t1'"' exit 498 } capture assert `dead'==0 if `touse' if _rc==0 { di in red `"no test possible because there are no failures"' exit 2000 } preserve if `"`weight'"' != `""' { tempvar w quietly gen double `w' `exp' if `touse' local wv `"`w'"' } else local w 1 tempfile lister tempvar op g n d quietly { keep if `touse' keep `wv' `t0' `t1' `by' `strata' `dead' sort `by' by `by': gen long `g' = 1 if _n==1 replace `g' = sum(`g') local ng = `g'[_N] save `"`lister'"' local N = _N expand 2 gen byte `op' = 3/*add*/ in 1/`N' replace `t1' = `t0' in 1/`N' drop `t0' local N = `N' + 1 replace `op' = cond(`dead'==0,2/*cens*/,1/*death*/) in `N'/l if `"`strata'"'!=`""' { local bystr `"by `strata':"' } sort `strata' `t1' `op' `by' `bystr' gen double `n' = sum(cond(`op'==3,`w',-`w')) by `strata' `t1': gen `d' = sum(`w'*(`op'==1)) local i 1 while `i' <= `ng' { tempvar ni di `bystr' gen double `ni' = /* */ sum(cond(`g'==`i', cond(`op'==3,`w',-`w'), 0)) by `strata' `t1': gen double `di' = /* */ sum(cond(`g'==`i', `w'*(`op'==1), 0)) local nlist `"`nlist' `ni'"' local dlist `"`dlist' `di'"' local i = `i' + 1 } by `strata' `t1': keep if _n==_N tempvar newn `bystr' gen double `newn' = `n'[_n-1] drop `n' rename `newn' `n' local i 1 while `i' <= `ng' { local ni : word `i' of `nlist' `bystr' gen double `newn' = `ni'[_n-1] if _n>1 drop `ni' rename `newn' `ni' local i = `i' + 1 } drop if `d'==0 tempvar wi tempname w wo v mat `w' = J(1,`ng',0) mat `wo' = `w' local i 1 while `i' <= `ng' { local ni : word `i' of `nlist' local di : word `i' of `dlist' gen double `wi' = sum(`ni'*`d'/`n') mat `w'[1,`i'] = `wi'[_N] drop `wi' summ `di' mat `wo'[1,`i'] = _result(18) local i = `i' + 1 } } tempname V mat `V' = J(`ng',`ng',0) local i 1 tempvar cons qui gen double `cons' = `d'*(`n'-`d')/(`n'*`n'*(`n'-1)) while `i' <= `ng' { local ni : word `i' of `nlist' local di : word `i' of `dlist' gen double `wi' = sum( `ni'*(`n'-`ni')*`cons' ) mat `V'[`i',`i'] = `V'[`i',`i'] + `wi'[_N] drop `wi' local j 1 while `j' < `i' { local nj : word `j' of `nlist' local dj : word `j' of `dlist' gen double `wi' = sum( -`ni'*`nj'*`cons') mat `V'[`i',`j'] = `V'[`i',`j'] + `wi'[_N] mat `V'[`j',`i'] = `V'[`i',`j'] drop `wi' local j = `j' + 1 } local i = `i' + 1 } tempname v mat `v' = `w' - `wo' if `"`mat'"' != `""' { parse `"`mat'"', parse(`" "') mat `1' = `v' mat `2' = `V' } tempname mV mv mat `mV' = `V' mat `mv' = `v' /* `mV' is the covariate matrix */ /* `mv' is Z matrix */ mat `V' = syminv(`V') tempname wt mat `V' = `v' * `V' mat `V' = `V' * `v' ' quietly { use `"`lister'"', clear by `by': keep if _n==1 keep `g' `by' sort `g' tempvar grp X gen str50 `grp' = `""' local second 0 parse `"`by'"', parse(`" "') while `"`1'"' != `""' { if `second' { local ttlsub = abbrev("`1'",8) local ttl `"`ttl', `ttlsub'"' local ttlsub replace `grp' = `grp' + `", "' } else { if `"`2'"'=="" { local ttl = abbrev(`"`1'"', 12) } else local ttl = abbrev(`"`1'"', 8) local second 1 } local ty : type `1' if substr(`"`ty'"',1,3)==`"str"' { replace `grp' = `grp' + /* */ trim(substr(trim(`1'),1,30)) } else { local vlab : value label `1' if `"`vlab'"' != `""' { decode `1', gen(`X') maxlen(30) replace `grp' = `grp' + trim(`X') drop `X' } else replace `grp' = `grp'+trim(string(`1')) } mac shift } compress `grp' } local len1 = length(`"`ttl'"') local ty : type `grp' local len2 = substr(`"`ty'"',4,.) local len = max(`len1', `len2', 5) + 1 Title `"`title'"' `"`strata'"' di in smcl in gr _n _col(`len') `" {c |} Events Events"' local pad = `len' - `len1' if `"`strata'"'==`""' { local dup `" expected"' } else local dup `"expected(*)"' di in smcl in gr `"`ttl'"' _skip(`pad') `"{c |} observed `dup'"' di in smcl in gr "{hline `len'}{c +}{hline 25}" local sum 0 local i 1 while `i' <= _N { local x = `grp'[`i'] local pad = `len' - length(`"`x'"') di in smcl in gr `"`x'"' _skip(`pad') "{c |}" in ye /* */ %10.0g `wo'[1,`i'] `" "' %10.2f `w'[1,`i'] ret scalar observed`i' = `wo'[1,`i'] ret scalar expected`i' = `w'[1,`i'] local sum = `sum' + `wo'[1,`i'] local i = `i' + 1 } di in smcl in gr "{hline `len'}{c +}{hline 25}" local pad = `len' - 5 di in smcl in gr `"Total"' _skip(`pad') `"{c |}"' in ye %10.0g `sum' `" "' %10.2f `sum' if `"`strata'"' != `""' { di in gr _n `"(*) sum over calculations within `strata'"' } local pad = `len' + 7 return scalar df = colsof(`w') - 1 return scalar chi2 = `V'[1,1] /* double save */ global S_1 `"`by'"' global S_5 = colsof(`w') - 1 global S_6 = `V'[1,1] /* double save ends */ local pad1 = `pad' - ($S_5>=10) di _n in gr _col(`pad1') `"chi2($S_5) = "' in ye %10.2f `V'[1,1] di in gr _col(`pad') `"Pr>chi2 = "' in ye %10.4f chiprob($S_5, `V'[1,1]) if "`trend'"~="" { if _N<=2 { di in red "trend test requires 3 or more groups" exit 198 } _sttrend `mV' `mv' `by' `pad1' `pad' return add } end program define Title /*