*! 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 /* <strata> */ local title `"`1'"' local strata `"`2'"' if `"`title'"' == `""' { if `"`strata'"'==`""' { di _n(2) in smcl in gr /* */ `"{title:Log-rank test for equality of survivor functions}"' } else di _n(2) in smcl in gr /* */ `"{title:Stratified log-rank test for equality of survivor functions}"' } end * version 1.1.0 10/14/93 STB-16 ssi5 program define bisect version 3.1 parse "`*'", parse(" X=") local toload "exp1" while "`1'"!="=" & "`1'"!="returns" & "`1'"!="" { if "`1'"=="X" { if "`toload'"=="exp2" { di in red "X may be specifed only once" exit 198 } local toload "exp2" } else { local `toload' "``toload'' `1'" } mac shift } if "`toload'"!="exp2" { di in red "X must be specified" exit 198 } if "`1'"=="" { error 198 } if "`1'"=="returns" { if "`2'"=="macro" { local type "`3'" local rtnval } else if "`2'"=="exp" { local rtnval "`3'" local type } else error 198 mac shift 3 local progttl "`exp1' X `exp2'" local inline "*" } else { local progttl = substr("`exp2'",2,.) local progttl "`exp1'X`progttl'" local callout "*" } if "`1'" != "=" { di in red "=exp required" exit 198 } mac shift local toload "mval" while "`1'"!="" { if "`1'"=="to" { local toload "mhi" } else if "`1'"=="from" { local toload "mlo" } else if "`1'"=="tol" { local toload "mtol" } else { local `toload' "``toload''`1'" } mac shift } if "`mval'"=="" { di in red "= must be followed by expression" exit 198 } if "`mhi'"=="" { di in red "to specifier requried" exit 198 } if "`mlo'"=="" { di in red "from specifier required" exit 198 } tempname flo fhi val lo hi hold ftry tol if "`mtol'"=="" { scalar `tol' = 1e-6 } else scalar `tol' = `mtol' scalar `val' = `mval' scalar `lo' = `mlo' scalar `hi' = `mhi' scalar `tol' = max(`tol'*abs(`val'),`tol') if `lo'>`hi' { scalar `hold' = `lo' scalar `lo' = `hi' scalar `hi' = `hold' } local ttl : di "Find `progttl'=f() == c=" `val' ", |f()-c|<" `tol' local skip = int((72-length("`ttl'"))/2) di in gr _n _skip(`skip') "`ttl'" _n `callout' quietly `exp1' `lo' `exp2' `callout' scalar `flo' = ${`type'} `rtnval' - `val' `inline' scalar `flo' = (`exp1' `lo' `exp2') - `val' `callout' quietly `exp1' `hi' `exp2' `callout' scalar `fhi' = ${`type'} `rtnval' - `val' `inline' scalar `fhi' = (`exp1' `hi' `exp2') - `val' di in gr /* */ _col(17) "lower" _col(39) "upper" _n /* */ "iteration" _col(17) "bound" _col(27) "f()-c" /* */ _col(39) "bound" _col(49) "f()-c" /* */ _col(59) "midpoint" _col(72) "f()-c" _n /* */ _dup(76) "-" /* verify we bound the solution */ if `flo'*`fhi'>0 | `flo'*`fhi'==. { di in gr %8.0f 0 ". " %9.0g `lo' " " %9.0g `flo' /* */ " " %9.0g `hi' " " %9.0g `fhi' display in red "range does not bound solution" exit 409 } if `flo'==0 { scalar `fhi' = `flo' scalar `hi' = `lo' } else if `fhi'==0 { scalar `flo' = `fhi' scalar `lo' = `hi' } local try = (`lo'+`hi')/2 `callout' quietly `exp1' `try' `exp2' `callout' scalar `ftry' = ${`type'} `rtnval' - `val' `inline' scalar `ftry' = (`exp1' `try' `exp2') - `val' local iter -1 while abs(`ftry')>`tol' { local iter=`iter'+1 di in gr %8.0f `iter' ". " %9.0g `lo' " " %9.0g `flo' /* */ " " %9.0g `hi' " " %9.0g `fhi' " " /* */ in ye %9.0g `try' " " %9.0g `ftry' if `flo'*`ftry'<0 { scalar `fhi' = `ftry' scalar `hi'=`try' } else { scalar `flo' = `ftry' scalar `lo' = `try' } local try = (`lo'+`hi')/2 `callout' quietly `exp1' `try' `exp2' `callout' scalar `ftry' = ${`type'} `rtnval' - `val' `inline' scalar `ftry' = (`exp1' `try' `exp2') - `val' } di in ye _col(58) %9.0g `try' " " %9.0g `ftry' global S_1 = `try' end * subroutine for calculating FWER * DB v0.09 program define nstagefwer, rclass version 10 /* Calculate familywise error rate (FWER) under global null of a multi-arm multi-stage trial v0.01 - I=D v0.02 - add correlation matrix to syntax & alter calculation to allow for I!=D v0.03 - use preserve and restore instead of tempvars v0.04 - calculate P(k arms reaching stage j) (see nstage probs option) v0.05 - calculate probs under H1, also P(k arms passing final stage) v0.06 - add set seed option v0.07 - remove probs calculation under H1 - not useful information v0.08 - output SE for FWER & make probs calculation default v0.09 - calculate maximum fwer when I!=D v0.10 - allow efficacy bounds with separate or simultaneous stopping rule - estimate three measures of power for multi-arm designs - allow calculation of operating characteristics under non-binding futility boundaries */ syntax, nstage(int) arms(int) alpha(string) omega(string) corr(name) aratio(real) /// [reps(int 250000) seed(int -1) ineqd esb(string) NONBINDing] if `seed'>0 set seed `seed' local J = `nstage' // # stages local Jm1 = `J'-1 // # interim stages local K = `arms'-1 // # E arms local A = `aratio' // Allocation ratio mat def S = `corr' // correlation between stages under H0 // Stagewise sig. levels and power levels NEW forvalues j = 1/`J' { local alpha`j' : word `j' of `alpha' local omega`j' : word `j' of `omega' } // Define efficacy boundaries (if specified by user) if "`esb'"!=""{ gettoken rule opts : esb, parse(" =,") forvalues j = 1/`J' { local t`j' = S[`J',`j']^2 // Obtain information times for interim analyses return scalar t`j' = `t`j'' } gettoken rule opts : esb, parse(" =,") if "`rule'"=="hp" { if "`opts'"!="" { gettoken 1 2: opts, parse(",") if "`2'"=="stop" local esbstop = "`2'" if "`2'"!=""{ local pval = "`1'" if "`pval'"=="," local pval = "" gettoken 1 2: 2, parse(" =,") if "`2'"=="stop" local esbstop = "`2'" } else { local pval = "`1'" } } if "`pval'"!="" { gettoken eq pval : pval, parse(" =") forvalues j = 1/`J' { local E`j' `pval' } } else { forvalues j = 1/`J' { local E`j' = 0.0005 } } } else if "`rule'"=="obf" { if "`opts'"!=""{ gettoken 1 2: opts, parse(",") local esbstop = "`2'" } forvalues j = 1/`J' { local E`j'= 2*(1 - normal(invnormal(1 - `alpha`J''/2)/sqrt(`t`j''))) // Approx. to O'Brien-Fleming rule - Lan & DeMets 1983 if round(`E`j'',0.001)>round(`alpha`J'',0.001){ di as error "Warning: efficacy boundary for stage `j' is larger than the final stage significance level" } } } else if "`rule'"=="custom" { if "`opts'"!=""{ gettoken eq pval : opts, parse(" =") forvalues j = 1/`J' { local E`j' : word `j' of `pval' if `j'!=`J'{ capture confirm num `E`j'' if c(rc) { di as error "no custom efficacy bound specified for stage `j'" exit 198 } } } if "`E`J''"!=""{ capture confirm num `E`J'' if c(rc)==0 { di as error "cannot specify efficacy bound for final stage" } } capture confirm num `E`Jm1'' if c(rc) { gettoken E`Jm1' stop : E`Jm1', parse(",") local esbstop = "stop" } } else di as error "p values for custom stopping rule must be specified" } else { di as error "efficacy boundary incorrectly specified" exit 198 } forvalues j = 1/`Jm1' { // Check efficacy bounds are non-decreasing local jm1 = `j'-1 local E0 = 0 if `E`j''<`E`jm1'' { di as error "p-values must be non-decreasing" exit 198 } return scalar E`j' = `E`j'' } return local esbstop = "`esbstop'" } // Correlation matrix between arms ( = A/A+1) matrix A = I(`K') forvalues j = 1/`K' { mat def A[`j',`j'] = 1 forvalues k = 1/`K' { if `j'!=`k' mat def A[`j',`k'] = `A'/(`A'+1) } } // Generate correlated standard normal RVs preserve drop _all forvalues k = 0/`K' { local X`k' x1`k' local sd 1 forvalues j = 2/`J' { local X`k' `X`k'' x`j'`k' local sd `sd' \ 1 } qui drawnorm `X`k'', corr(S) sd(`sd') n(`reps') double } forvalues j = 1/`J' { forvalues k = 1/`K' { gen double z`j'`k' = sqrt(`A'/(`A'+1))*x`j'0+sqrt(1/(`A'+1))*x`j'`k' } } // Flag arms which pass each stage k and any which are dropped for efficacy forvalues k = 1/`K' { scalar pass0`k'=1 scalar powerpass0`k'=1 if "`esb'"!="" { scalar edrop0`k'=0 } forvalues j = 1/`J' { local jm1 = `j'-1 gen byte pass`j'`k' = (z`j'`k'>invnormal(1-`alpha`j'') & pass`jm1'`k'==1) gen byte powerpass`j'`k' = (z`j'`k'>invnormal(1-`omega`j'') & powerpass`jm1'`k'==1) if "`nonbinding'"!="" & `j'!=`J'{ qui replace pass`j'`k' = 1 qui replace powerpass`j'`k' = 1 } if "`esb'"!="" & `j'!=`J'{ qui gen byte edrop`j'`k' = cond(edrop`jm1'`k'==1, 1, /// cond(z`j'`k'>invnormal(1-`E`j'') & pass`jm1'`k'==1, 1, 0)) qui replace pass`j'`k' = 0 if edrop`j'`k'==1 qui replace powerpass`j'`k' = 0 if edrop`j'`k'==1 } } } // Apply simultaneous efficacy stopping rule if specified if "`esbstop'"!=""{ forvalues j=1/`Jm1'{ forvalues i=`j'/`J'{ forvalues k=1/`K'{ forvalues l=1/`K'{ if `l'!=`k'{ qui replace pass`i'`l' = 0 if edrop`j'`k'==1 qui replace powerpass`i'`l' = 0 if edrop`j'`k'==1 } } } } } } // Count the number of arms which were dropped for efficacy at some stage prior to stage J if "`esb'"!=""{ local esum = 0 forvalues k = 1/`K' { qui count if edrop`Jm1'`k'==1 local esum = `esum'+r(N) } return scalar esum = `esum' egen byte edrop`Jm1' = rowtotal(edrop`Jm1'*) qui count if edrop`Jm1'>0 local edrops = r(N) return scalar edrops = `edrops' } // Number of arms passing stage j forvalues j = 1/`J' { if "`esb'"!=""{ egen byte npass`j' = rowtotal(pass`j'* edrop`jm1'*) egen byte powernpass`j' = rowtotal(powerpass`j'* edrop`jm1'*) } else { egen byte npass`j' = rowtotal(pass`j'*) egen byte powernpass`j' = rowtotal(powerpass`j'*) } } // Probability of k arms passing stage j forvalues j = 1/`J' { forvalues k = 0/`K' { qui count if npass`j'==`k' local p`j'`k' = r(N)/`reps' return scalar p`j'`k' = `p`j'`k'' } } // Pairwise type I error rate local sum = 0 forvalues k = 1/`K' { qui count if pass`J'`k'==1 local sum = `sum'+r(N) } local pwer = `sum'/(`K'*`reps') if "`esb'"!="" local pwer = (`sum'+`esum')/(`K'*`reps') // Recalculate pwer if dropping for efficacy specified return scalar pwer = `pwer' // FWER qui count if npass`J'>0 local fwer = r(N)/`reps' local se_fwer = sqrt(`fwer'*(1-`fwer')/`reps') local ll_fwer = `fwer'-invnormal(0.975)*`se_fwer' local ul_fwer = `fwer'+invnormal(0.975)*`se_fwer' return scalar fwer = `fwer' return scalar se_fwer = `se_fwer' return scalar ll_fwer = `ll_fwer' return scalar ul_fwer = `ul_fwer' // Per-pair power NEW local sum = 0 forvalues k = 1/`K' { qui count if powerpass`J'`k'==1 // # trials in which arm k passes final stage local sum = `sum'+r(N) // Add totals for all arms } local pwomega = `sum'/(`K'*`reps') if "`esb'"!="" { local pwomega = (`sum'+`esum')/(`K'*`reps') } return scalar pwomega = `pwomega' // Any-pair power NEW qui count if powernpass`J'>0 local fwomega = r(N)/`reps' local se_fwomega = sqrt(`fwomega'*(1-`fwomega')/`reps') local ll_fwomega = `fwomega'-invnormal(0.975)*`se_fwomega' local ul_fwomega = `fwomega'+invnormal(0.975)*`se_fwomega' return scalar fwomega = `fwomega' return scalar se_fwomega = `se_fwomega' return scalar ll_fwomega = `ll_fwomega' return scalar ul_fwomega = `ul_fwomega' // All-pair power NEW qui count if powernpass`J'==`K' local allomega = r(N)/`reps' return scalar allomega = `allomega' // ------------------ Maximum FWER if I not equal D if "`ineqd'"!="" { cap drop edrop* cap drop pass* cap drop npass* cap drop power* forvalues k = 1/`K' { scalar pass0`k'=1 scalar powerpass0`k'=1 if "`esb'"!="" { scalar edrop0`k'=0 } forvalues j = 1/`J' { local jm1 = `j'-1 if `j'!=`J'{ // Up to stage J, all arms pass every stage or are only checked against efficacy bounds (if specified) if "`esb'"!="" { qui gen byte edrop`j'`k' = cond(edrop`jm1'`k'==1, 1, /// cond(z`j'`k'>invnormal(1-`E`j'') & pass`jm1'`k'==1, 1, 0)) qui gen byte pass`j'`k' = 1 if edrop`j'`k'==0 qui gen byte powerpass`j'`k' = 1 if edrop`j'`k'==0 } else { qui gen byte pass`j'`k' = 1 qui gen byte powerpass`j'`k' = 1 } } else { // At final stage J, only check against futility bound, conditional on arm having passed stage J-1 qui gen byte pass`j'`k' = (z`j'`k'>invnormal(1-`alpha`j'') & pass`jm1'`k'==1) qui gen byte powerpass`j'`k' = (z`j'`k'>invnormal(1-`omega`j'') & powerpass`jm1'`k'==1) } } } if "`esb'"!=""{ local Jm1 = `J'-1 local esum = 0 forvalues k = 1/`K' { qui count if edrop`Jm1'`k'==1 local esum = `esum'+r(N) } return scalar esum = `esum' egen byte edrop`Jm1' = rowtotal(edrop`Jm1'*) qui count if edrop`Jm1'>0 // # trials with >1 arm dropped for efficacy local edrops = r(N) return scalar edrops = `edrops' } // Max PWER local pwer = `alpha`J'' // No ESBs, max PWER = alphaJ if "`esb'"!=""{ // With ESBs, max PWER based on final stage simulation (plus arms dropped for efficacy early) local sum = 0 forvalues k = 1/`K' { qui count if pass`J'`k'==1 local sum = `sum'+r(N) local pwer = (`sum'+`esum')/(`K'*`reps') // Recalculate pwer if stopping arms for efficacy allowed } } return scalar pwer = `pwer' // Max FWER if "`esb'"!="" egen byte npass`J' = rowtotal(pass`J'* edrop`Jm1'*) else egen byte npass`J' = rowtotal(pass`J'*) qui count if npass`J' > 0 // Count # arms that pass per trial & # trials with >1 arm reaching stage J local maxfwer = r(N)/`reps' local se_maxfwer = sqrt(`maxfwer'*(1-`maxfwer')/`reps') return scalar maxfwer = `maxfwer' return scalar se_maxfwer = `se_maxfwer' // Any-pair power if "`esb'"!="" egen byte powernpass`J' = rowtotal(powerpass`J'* edrop`Jm1'*) else egen byte powernpass`J' = rowtotal(powerpass`J'*) qui count if powernpass`J'>0 local fwomega = r(N)/`reps' return scalar fwomega = `fwomega' // Per-pair power local sum = 0 forvalues k = 1/`K' { qui count if powerpass`J'`k'==1 local sum = `sum'+r(N) } local pwomega = `sum'/(`K'*`reps') if "`esb'"!=""{ local pwomega = (`sum'+`esum')/(`K'*`reps') // Recalculate pwer if dropping for efficacy specified } return scalar pwomega = `pwomega' // All-pair power qui count if powernpass`J'==`K' local allomega = r(N)/`reps' return scalar allomega = `allomega' restore local z = invnormal(1-`alpha`J'') // Vector of z values local z1ma `z' forvalues k = 2/`K' { local z1ma `z1ma', `z' } tempname Z matrix `Z' = (`z1ma') tempname A mat `A' = A local rep = 5000 mata: mvnprob("`Z'", "`A'", `rep') local mvnpmaxfwer = 1-r(p) return scalar mvnpmaxfwer = `mvnpmaxfwer' } end * Subroutine for calculating the sample size program define samplesize, rclass syntax, Nstage(int) ACcrue(string) ALpha(string) ARms(string) HR0(string) HR1(string) /// Omega(string) [ARAtio(string) S(string) T(string) TStop(real 0) TUnit(int 1) seed(int -1) /// fwerreps(int 1000000) noIterate old(int 0)] if "`t'"=="" { di as err "must supply t(), the time for survival prob specified in s()" exit 198 } if `tstop'>0 { local Tstop tstop(`tstop') } else local Tstop if `nstage' < 1 { di as err "nstage must be 1 or more" exit 198 } * 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 if (`tunit'<1 | `tunit'>7) local tunit 7 tokenize `""one year" "6 months" "one quarter (3 months)" "one month" "one week" "one day" "unspecified""' local lperiod ``tunit'' // t, (optionally) s, hr0 and hr1 are provided for stages 1 and final. Fill in values for intermediate stages. if "`s'" == "" { local s = cond(`nstage'==1, "0.5", "0.5 0.5") } // Tidy up check on number of parameters in each option local maxval = cond(`nstage' > 1, 2, 1) local nopt: word count `s' local errcount 0 if `nopt' > `maxval' { local ++errcount local error`errcount' "too many items specified for s() - maximum is `maxval'" } local opts hr0 hr1 t s local nopts: word count `opts' tokenize `opts' forvalues i = 1 / `nopts' { local opt ``i'' local nopt: word count ``opt'' if `nopt' > `maxval' { local ++errcount local error`errcount' "too many items specified for `opt'() - maximum is `maxval'" } if (`nopt' == 0) | (`nstage' > 1 & `nopt' < 2) { local ++errcount local error`errcount' "must specify `maxval' value(s) for `opt'()" } local stage S1 local `opt'`stage': word 1 of ``opt'' cap confirm number ``opt'`stage'' if c(rc) { local ++errcount local error`errcount' "'``opt'`stage''' found where number expected" } if "`opt'" == "t" { local surv : word 1 of `s' cap confirm number `surv' if c(rc) { local ++errcount local error`errcount' "'`surv'' found where number expected" } else local hazard1 = -ln(`surv') / ``opt'`stage'' } if `nstage' > 1 { local stage S`nstage' local `opt'`stage': word 2 of ``opt'' cap confirm number ``opt'`stage'' if c(rc) { local ++errcount local error`errcount' "'``opt'`stage''' found where number expected" } else { if "`opt'" == "t" { local surv : word 2 of `s' cap confirm number `surv' if c(rc) { local ++errcount local error`errcount' "'`surv'' found where number expected" } else local hazard`nstage' = -ln(`surv') / ``opt'`stage'' } if `nstage' > 2 { local ns1 = `nstage' - 1 forvalues j = 2 / `ns1' { if "`opt'" == "t" { local hazard`j' `hazard1' } else local `opt'S`j' ``opt'S1' } } } } } if `errcount' > 0 { forvalues i = 1 / `errcount' { di as err "`error`i''" } exit 198 } // Determine if hazard ratio(s) are > 1, and set sign accordingly. forvalues i = 1 / `nstage' { if (`hr1S`i'' > `hr0S`i'') local sign`i' -1 else local sign`i' 1 } local median1 = ln(2) / `hazard1' local median`nstage' = ln(2) / `hazard`nstage'' // Signal if have separate I- and D-outcomes according to equality of hazards local have_D = reldif(`hazard1', `hazard`nstage'') > 0.0001 // Assign values of accrue alpha arms omega for all stages if `nstage' > 1 { local es s } **************************************** local opts accrue alpha arms omega local nopts: word count `opts' tokenize `opts' forvalues i = 1 / `nopts' { local opt ``i'' local nopt: word count ``opt'' if `nopt' > `nstage' { local ++errcount local error`errcount' "too many items specified for `opt'() - `nstage' value`es' required" } if `nopt'<`nstage' { local ++errcount local error`errcount' "must specify `nstage' value`es' for `opt'()" } forvalues j = 1 / `nstage' { local stage S`j' local `opt'`stage': word `j' of ``opt'' cap confirm number ``opt'`stage'' if c(rc) { local ++errcount local error`errcount' "'``opt'`stage''' found where number expected" } } } // New: Check number of arms in each stage is the same or less as the previous stage // Error checking of arms() improved, PR 23sep2021. forvalues i = 1 / `nstage' { capture confirm integer number `armsS`i'' if _rc { local ++errcount local error`errcount' "invalid number of arms at stage `i'" *exit 198 } else if `i' > 1 { local im1 = `i'-1 if `armsS`i'' > `armsS`im1''{ local ++errcount local error`errcount' "number of arms in stage `i' must not exceed number in stage `im1'" } } } if `errcount' > 0 { forvalues i = 1 / `errcount' { di as err "`error`i''" } exit 198 } ** End of error checking and preliminary calculation ********** ****************************************** // Compute control arm accrual from overall accrual, #arms and allocation ratio. forvalues i = 1 / `nstage' { local A `accrueS`i'' // total accrual rate local K = `armsS`i'' - 1 // #exp arms local acc0S`i' = `A' / (1 + `Rstar' * `K') // control accrual rate ** local acc1S`i' = `Rstar'*`acc0S`i'' // exp. accrual rate ** local oaccS`i' `A' // overall accrual rate } local fac = 1 + 1 / `Rstar' /* replaces factor 2 in expressions for eS1, eS`nstage' etc. */ // Compute e_S1 and initial e_S`nstage' from alpha1, HR0_0 local zalphaS1 = invnormal(`alphaS1') local zomegaS1 = invnormal(`omegaS1') local zomega = -`zomegaS1' * Stage 1 * Compute initial est of control arm S1 I-events rounded up to nearest integer local eS1 = int(1 + `fac' * ((`zomegaS1' - `zalphaS1')/(ln(`hr0S1') - ln(`hr1S1')))^2) * Update eS1 and ln(deltaS1) to achieve desired power local eS1un `eS1' local haz0S1 = `hazard1' * `hr0S1' local haz1S1 = `hazard1' * `hr1S1' local iter 0 local done 0 quietly while !`done' { * Compute delta_S1 local lndelS1 = ln(`hr0S1') + `sign1' * `zalphaS1' * sqrt(`fac' / `eS1') * Compute time for stage 1 cap timetoevn4, accrual(`acc0S1') events(`eS1') hazard(`hazard1') `Tstop' if _rc { local errmess = r(errmess) di as err `"`r(errmess)'"' return local errmess `errmess' exit 498 } local tS1 = r(t1) if "`iterate'" == "noiterate" { * Calcs done under H0, no iteration necessary if `Rstar' == 1 { local eS1star `eS1' local zomega `zomegaS1' } else { local eS1star = int(1 + `Rstar' * `eS1') if !`old' local lndelS1 = ln(`hr0S1') + `sign1' * `zalphaS1' * sqrt(1 / `eS1' + 1 / `eS1star') /* This is added by Babak following Ian's suggestion on 24 March 2021 */ local zomega = `sign1' * (`lndelS1' - ln(`hr1S1'))/sqrt(1 / `eS1' + 1 / `eS1star') } local done 1 } else { * Compute I-events at end of stage 1 in experimental arm under H1. evfromtin4, accrual(`acc1S1') times(`tS1') hazard(`haz1S1') `Tstop' local eS1star = int(1+r(e1)) if "`olddef'" != "" { local zomega = `sign1' * (`lndelS1' - ln(`hr1S1')) / sqrt(4 / (`eS1' + `eS1star')) } else { // This was added by Babak following Ian's suggestion on 24 March 2021 if !`old' local lndelS1 = ln(`hr0S1') + `sign1' * `zalphaS1' * sqrt(1 / `eS1' + 1 / `eS1star') local zomega = `sign1' * (`lndelS1' - ln(`hr1S1')) / sqrt(1 / `eS1' + 1 / `eS1star') } * Update eS1 if `sign1'*`zomega' >= `sign1'*`zomegaS1' { local done 1 } else { local eS1 = `sign1' + `eS1' local ++iter } } } local omegaS1=normal(`zomega') local zomegaS1 = `zomega' * nS1 is total number of control-arm patients, stage 1 if `tstop'>0 { local nS1 = `acc0S1' * min(`tstop',`tS1') } else { local nS1 = `acc0S1' * `tS1' } if `nS1' < `eS1' { di as err "Design infeasible, accrual period too short" exit 498 } // Round off numbers of patients in experimental and control arms to nearest integer local nS1 = round(`nS1') // total patients, C local nexpS1 = round(`nS1' * (`armsS1' - 1) * `Rstar') // total patients, all E local ntotS1 = `nS1' + `nexpS1' // total patients, C+all E local etotS1 = `eS1' + (`armsS1' - 1) * `eS1star' // total I-events, C+all E local eexpS1 = `etotS1' - `eS1' // total S1-events, all E *noi di in red "3144:" `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 { // FWER + 95% CI for 1-stage design if "`nofwer'"=="" { mat def R = (1) nstagefwer, nstage(`nstage') arms(`armsS1') alpha(`alpha') omega(`omega') corr(R) aratio(`Rstar') /// seed(`seed') reps(`fwerreps') local fwerate = r(fwer) local se_fwerate = r(se_fwer) return scalar fwer = r(fwer) return scalar se_fwer = r(se_fwer) } local ww 9 * left-justified string format local sfl %-`ww's * centered string format local sfc %~`ww's * right-justified string format local sfr %`ww's local nitem 6 local dup = `nitem' * `ww' di as text _n "$title1" _n "{hline `hline'}" di as text "Sample size for a " as res "`armsS1'" as text "-arm " as res "1" as txt "-stage trial with time-to-event outcome" di as text "$title2" di as text "$title3" _n "{hline `hline'}" di as text _n "Median survival time: " as res round(`median1', 0.1) as text " time units" di as text _n "Operating characteristics" _n "{hline `dup'}" di as text %`ww'.3f "Alpha(1S)" `sfr' "Power" _col(23) "HR{c |}H0" _col(32) "HR{c |}H1" `sfr' "Crit. HR" `sfr' "Duration" _n "{hline `dup'}" di as text %`ww'.3f `alphaS1' %`ww'.3f `omegaS1' %`ww'.3f `hr0S1' %`ww'.3f `hr1S1' %`ww'.3f as res exp(`lndelS1') %`ww'.3f as res `tS1' _n as txt "{hline `dup'}" if `tstop' > 0 { di as text "Patient accrual stopped at time " %6.3f `tstop' } if "`nofwer'"=="" & `armsS1'>2 di as text "Familywise error rate (SE) = " as res %4.3f `fwerate' /// as text " (" as res %4.3f `se_fwerate' as text ")" di _n as text "Duration is expressed in " as res "`lperiod'" as txt " periods and assumes" di as text "assumes survival times are exponentially distributed. Time is" di as text "expressed in cumulative periods." local nitem 4 local dup = `nitem' * `ww' di as text _n "Sample size and number of events" _n "{hline `dup'}" di as text _skip(`ww') `sfr' "Overall" `sfr' "Control" `sfr' "Exper." _n "{hline `dup'}" di as text `sfl' "Arms" as txt %`ww'.0f `armsS1' %`ww'.0f 1 %`ww'.0f `armsS1'-1 di as text `sfl' "Acc. rate" as txt %`ww'.0f `oaccS1' %`ww'.0f `acc0S1' %`ww'.0f `acc0S1'*(`armsS1'-1)*`Rstar' di as text `sfl' "Patients" as res %`ww'.0f `ntotS1' %`ww'.0f `nS1' %`ww'.0f `nexpS1' di as text `sfl' "Events" as res %`ww'.0f `etotS1' %`ww'.0f `eS1' %`ww'.0f `eexpS1' as txt _n "{hline `dup'}" if `Rstar' != 1 { di as text "`Rstar' patients allocated to each E arm for every 1 to control arm." } if "`iterate'" == "noiterate" { if `hr1S1'<1 di as text _n "[Note: computations carried out under H0. Sample sizes may be too low.]" if `hr1S1'>1 di as text _n "[Note: computations carried out under H0. Sample sizes may be too high.]" } exit // one-stage trial } /* Stages 2 - n Update events and HR cutoffs to achieve desired power */ forvalues i = 2 / `nstage' { local zalphaS`i' = invnormal(`alphaS`i'') local zomegaS`i' = invnormal(`omegaS`i'') local zomega`i' = -`zomegaS`i'' // initialisation of achieved power to a low value local eS`i' = int(1 + `fac' * ((`zomegaS`i'' - `zalphaS`i'') / (ln(`hr0S`i'') - ln(`hr1S`i'')))^2) local eS`i'un `eS`i'' local haz0S`i' = `hazard`i'' * `hr0S`i'' local haz1S`i' = `hazard`i'' * `hr1S`i'' } forvalues m = 2 / `nstage' { local acc0S local acc1S forvalues i = 1 / `m' { local acc0S `acc0S' `acc0S`i'' local acc1S `acc1S' `acc1S`i'' } // Start iterative procedure to determine required control arm events local iter 0 local done 0 quietly while !`done' { // Compute log delta`m', HR threshold for stage `m'. local lndelS`m' = ln(`hr0S`m'') + `sign`m'' * `zalphaS`m'' * sqrt(`fac' / `eS`m'') // Compute times to end of stages 1,...,`m' based on events in control arm (hazard haz0*) local eS forvalues i = 1 / `m' { local eS `eS' `eS`i'' } cap timetoevn4, accrual(`acc0S') events(`eS') hazard(`hazard1' `hazard`m'') `Tstop' if _rc { di as err `"`r(errmess)'"' exit 498 } if "`r(enough)'" == "enough" { // Got enough events at stage m-1. Upgrade power. local m1 = `m' - 1 local eS`m' = r(ev21) local eS`m'star = int(1 + `Rstar' * `eS`m'') // total events in 1 exp arm local tS`m' = r(t`m') local zomega`m' = `sign`m'' * (`lndelS`m'' - ln(`hr1S`m'')) / sqrt(1 / `eS`m'' + 1 / `eS`m'star') noi di as txt _n "[Note: stage `m1' already provides " as res round(`eS`m'') /// as txt " events, more than enough for stage " `m' "." noi di as txt "Upgrading power for stage `m' from " as res %5.3f `omegaS`m'' /// as txt " to " as res %5.3f normal(`zomega`m'') /// as txt ". Stage `m' has zero length.]" local done 1 } else { local tS forvalues i = 1 / `m' { local tS`i' = r(t`i') local tS `tS' `tS`i'' } if "`iterate'" == "noiterate" { * Calcs done under H0, no iteration necessary if `Rstar' == 1 { local eS`m'star `eS`m'' local zomega`m' `zomegaS`m'' } else { local eS`m'star = int(1+`Rstar' * `eS`m'') if "`olddef'"!="" { local zomega`m' = `sign`m'' * (`lndelS`m'' - ln(`hr1S`m'')) / sqrt(4 / (`eS`m'' + `eS`m'star')) } else { // This was added by Babak following Ian's suggestion on 24 March 2021 if !`old' local lndelS`m' = ln(`hr0S`m'') + `sign`m'' * `zalphaS`m'' * sqrt(1 / `eS`m'' + 1 / `eS`m'star') local zomega`m' = `sign`m'' * (`lndelS`m'' - ln(`hr1S`m'')) / sqrt(1 / `eS`m'' + 1 / `eS`m'star') } } local done 1 } else { /* Compute eS`m'star, total events at end of stage `m' in experimental arm (hazard haz1*). (Events for stages 1,...,`m'-1 are not relevant, hence hazard is entered as "."). */ evfromtin4, accrual(`acc1S') times(`tS') hazard(. `haz1S`m'') `Tstop' local eS`m'star = int(1+r(e`m')) // total events in 1 exp arm if "`olddef'"!="" { local zomega`m' = `sign`m'' * (`lndelS`m'' - ln(`hr1S`m'')) / sqrt(4 / (`eS`m'' + `eS`m'star')) } else { // This was added by Babak following Ian's suggestion on 24 March 2021 if !`old' local lndelS`m' = ln(`hr0S`m'') + `sign`m'' * `zalphaS`m'' * sqrt(1 / `eS`m'' + 1 / `eS`m'star') local zomega`m' = `sign`m'' * (`lndelS`m'' - ln(`hr1S`m'')) / sqrt(1 / `eS`m'' + 1 / `eS`m'star') } * Update eS`m' if `sign`m''*`zomega`m'' >= `sign`m''*`zomegaS`m'' { local done 1 } else { local eS`m' = `sign`m''+`eS`m'' local ++iter } } } } local omegaS`m' = normal(`zomega`m'') local zomegaS`m' = `zomega`m'' } /* nS`nstage' is total number of control-arm patients, stage 1 + stage 2 ntotS`nstage' is total patients, C + all E nexpS`nstage' is total patients, all E tSt`i' is accrual time in stage `i', i = 1 , ... , nstage */ local tSt1 `tS1' forvalues i = 2 / `nstage' { local im1 = `i' - 1 local tSt`i' = `tS`i'' - `tS`im1'' } if `tstop'>0 { if `tstop'<`tS1' { // accrual stops in stage 1 local tSt1 `tstop' } forvalues i = 2 / `nstage' { local im1 = `i' - 1 if `tstop' < `tS`i'' { // accrual stops in stage `i' local tSt`i' = max(0, `tstop' - `tS`im1'') } } } * Run for each stage local nS1 = `acc0S1' * `tSt1' local nexpS1 = `Rstar' * (`armsS1' - 1) * `acc0S1' * `tSt1' forvalues i = 2 / `nstage' { local j = `i' - 1 local nS`i' = `nS`j'' + `acc0S`i'' * `tSt`i'' if `nS`i''<`eS`i'' { di as err "Design infeasible - fewer patients (`nS`i'') than events (`eS`i'') in stage `i'" exit 498 } local nexpS`i' = `nexpS`j'' + `Rstar' * ((`armsS`i'' - 1) * `acc0S`i'' * `tSt`i'') } // Round off numbers of patients in experimental and control arms to nearest integer forvalues i = 1 / `nstage' { local nexpS`i' = round(`nexpS`i'') local nS`i' = round(`nS`i'') local ntotS`i' = `nS`i'' + `nexpS`i'' } /* Compute etotS`nstage' = total events in all experimental arms. First, compute e1S`nstage'drop = S`nstage'-events in 'dropped' arm (no accrual beyond stage 1) */ if `tstop'>0 { local ts = min(`tS1', `tstop') } forvalues m = 2 / `nstage' { local m1 = `m' - 1 // Hazards are appropriate to experimental arms local haz `haz1S1' `haz1S`m'' local acc1S local tS forvalues i = 1 / `m' { local acc1S `acc1S' `acc1S`i'' local tS `tS' `tS`i'' } local eexpS`m' 0 // no. of events in exp arm in stage `m' if "`includedroppedarms'" != "" { /* Compute number of events in experimental "dropped" arms. This involves notionally stopping recruitment at end of stage 1, 2, ... */ forvalues i = 1 / `m1' { local j = `i' + 1 if `armsS`i'' > `armsS`j'' { qui evfromtin4, accrual(`acc1S') times(`tS') hazard(`haz') tstop(`tS`i'') local edrop`i' = r(e`m') local eexpS`m' = `eexpS`m'' + (`armsS`i'' - `armsS`j'') * `edrop`i'' } } } local eexpS`m' = `eexpS`m'' + (`armsS`m'' - 1) * `eS`m'star' local etotS`m' = `eS`m'' + `eexpS`m'' /* total S`nstage'-events, C+all E */ } forvalues j=1/`nstage'{ return scalar armsS`j' = `armsS`j'' return scalar accrueS`j' = `accrueS`j'' return scalar hr0S`j' = `hr0S`j'' return scalar hr1S`j' = `hr1S`j'' return scalar lndelS`j' = `lndelS`j'' return scalar tS`j' = `tS`j'' return scalar eS`j' = `eS`j'' return scalar etotS`j' = `etotS`j'' return scalar eexpS`j' = `eexpS`j'' return scalar ntotS`j' = `ntotS`j'' return scalar nS`j' = `nS`j'' return scalar nexpS`j' = `nexpS`j'' return scalar alphaS`j' = `alphaS`j'' return scalar omegaS`j' = `omegaS`j'' return scalar zalphaS`j' = `zalphaS`j'' return scalar zomegaS`j' = `zomegaS`j'' return scalar acc0S`j' = `acc0S`j'' return scalar oaccS`j' = `oaccS`j'' return scalar eS`j'un = `eS`j'un' return scalar eS`j'star = `eS`j'star' } return scalar median1 = `median1' return scalar median`nstage' = `median`nstage'' return scalar Rstar = `Rstar' return scalar have_D = `have_D' return scalar fac = `fac' end exit Notes and history ----------------- * renamed to nstage_beta and minor tidying IW 15feb2022 * nstageipr PR 21sep2021 based on nstagei IW fix 24mar2021 * nstagei IW fix 24mar2021 * nstage v4.0 28may2019. * based on stage2, v. 1.0.0 SB 17Mar2004. * Allows for the specification of efficacy stopping bounds * Amends the correlation structure with efficacy stopping bounds when I!=D * Allows specification of binding/non-binding stopping boundaries * Includes option to find the alphaJ which controls the FWER /* Determine number of events for stages 1 to n. Requires input of stage 1 to n-1 and stage n parameters. Determine time to given number of events assuming exponential survival. Hazard/median may have one or more values (latter for n stages). Tstop() (optional) is time of end of patient recruitment. Note that hidden option includedroppedarms is for compatibility with earlier versions of nstage (although the calculations have been updated in this release). The view is that reporting the actual number of events for the remaining arms is a better option than than trying to estimate how many events would have occurred in dropped arms. Main changes in v4.0 --- Allow for the specification of efficacy boundaries in calculation of the error rates - Haybittle-Peto - O'Brien Fleming alpha-spending approximation - Custom p-values --- Choice of which stopping approach to take with efficacy boundaries - Separate stopping rule: Continue with remaining arms - Simultaneous stopping rule: Terminate trial --- Subroutine added for controlling the FWER at the specified level --- Option added to assume non-binding futility stopping boundaries in calculation of the error rates --- Re-formatted nstage output */ a l ` d