/* Copyright © 2020 Erich J. Greene This code is distributed under the terms of Version 3 of the GNU Lesser General Public License (LGPL). It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License (https://www.gnu.org/licenses/gpl.txt) and the GNU Lesser General Public License https://www.gnu.org/licenses/lgpl.txt) for more details. */ /* Journals like .tiff but LaTeX can't process it; .png is vector and rescales well */ %let imgfmt=png; /* %let imgfmt=tiff; */ /* output directory -- you'll want to change this */ ods listing gpath="A:\STRIDE\ascertainment bias\paper_output" image_dpi=800; /* custom functions for numerical estimation */ proc fcmp outlib=work.fcmp.fns; /* solving for HR via Newton's method */ function newtHR(T,mu,lambda,gamma,k,Z); quadmu = mu*(mu - 3) + 3; theta = (3*(2 - mu)/(T*quadmu) - gamma)/2; phi = sqrt(6*k*Z/quadmu)/T; if (phi >= theta) then bigLamb = theta; else bigLamb = theta*(1 - sqrt(1 - phi**2/theta**2)); do until (iii >= 100 or abs(f) < 1e-12); iii+1; ratesum = bigLamb + gamma; x = T*ratesum; mux = mu*x; ex = exp(-x); emux = exp((mu-1)*x); Q = 1 - (emux - ex)/mux; f = bigLamb*Q/ratesum - k*Z; f1 = (gamma*(mux - emux + ex) + bigLamb*(x*((1-mu)*emux - ex) + emux - ex)) / (mux * ratesum**2); bigLamb = bigLamb - f/f1; end; return(if abs(f) < 1e-12 then bigLamb/lambda else .); endsub; /* calculating Q (as Qbar) */ function Qbar(x,mu); mux = mu*x; ex = exp(-x); emux = exp(x*(mu-1)); return (1 - (emux - ex)/mux); endsub; quit; options cmplib=work.fcmp; /* formats */ proc format; value outvers 1 = "full outcome def (Cats 1 and 2)" 2 = "restricted outcome def (Cat 1 only)" ; value defvers 1 = "protocol outcome definition (Type 1, 2a, or 2b SFI)" 2 = "revised outcome definition (Type 1 or 2a SFI)" ; run; /* summarized trial snapshot */ data snapshot(drop=arm); /* calculation of bias parameter B */ array all2b[2] all2bI all2bC (263 253); label all2bI = 'all self-reported Type 2b (Category 2) SFI in intervention' all2bC = 'all self-reported Type 2b (Category 2) SFI in control' ; array all2c[2] all2cI all2cC (526 613); label all2cI = 'all self-reported Type 2c (Category 3) SFI in intervention' all2cC = 'all self-reported Type 2c (Category 3) SFI in control' ; array rho[2] rhoi rhoc; label rhoi = 'rho ratio in intervention' rhoc = 'rho ratio in oontrol'; array varrho[2] varrhoi varrhoc; label varrhoi = 'variance of rho_i' varrhoc = 'variance of rho_c'; do arm=1 to 2; rho[arm] = all2b[arm]/(all2b[arm] + all2c[arm]); varrho[arm] = rho[arm]*(1-rho[arm])/(all2b[arm] + all2c[arm]); end; B = rhoi/rhoc; label B = 'bias inflation among susceptible events'; varB = B**2 * (varrhoi/rhoi**2 + varrhoc/rhoc**2); label varB = 'variance of B'; /* calculation of biasable fraction P */ first1_prot = 215; label first1_prot = 'control participants with self-reported SFI whose first event under protocol definition is Type 1'; first2a_prot = 55; label first2a_prot = 'control participants with self-reported SFI whose first event under protocol definition is Type 2a'; firstcat1_prot = first1_prot + first2a_prot; label firstcat1_prot = 'control participants with self-reported SFI whose first event under protocol definition is Type 1 or 2a (Category 1)'; first2b_prot = 206; label first2b_prot = 'control participants with self-reported SFI whose first event under protocol definition is Type 2b (Category 2)'; P = first2b_prot/(firstcat1_prot + first2b_prot); label P = 'fraction of control per protocol first self-reported events subject to ascertainment bias'; varP = P*(1-P)/(firstcat1_prot + first2b_prot); label varP = 'variance of P'; /* calculation of k */ k = 1 + P*(B-1); label k = 'bias inflation among all events'; vark = varP*((B-1)**2) + varB*(P**2); label vark = 'variance of k'; /* observed enrollment and event/death/loss rates */ rawNi = 2802; label rawNi = 'participants in intervention'; rawNc = 2649; label rawNc = 'participants in control'; obs_protr = 0.14787638055464; label obs_protr = 'observed control event rate per PYF under protocol definition'; obs_redefr = 0.08899643475603; label obs_redefr = 'observed control event rate per PYF under revised definition'; obs_d = 0.02558298736131; label obs_d = 'observed control death rate per PYF'; obs_w = 0.0173771234907; label obs_w = 'observed control withdrawal rate per PYF'; /* observed adjudication confirmation fractions */ array Afrac[3] Afrac1 Afrac2a Afrac2b (0.9661016949152542 0.6666666666666667 0.7712418300653595); label Afrac1 = 'confirmation fraction for self-reported Type 1 SFI' Afrac2a = 'confirmation fraction for self-reported Type 2a SFI' Afrac2b = 'confirmation fraction for self-reported Type 2b SFI' ; obs_protA = (Afrac1*first1_prot + Afrac2a*first2a_prot + Afrac2b*first2b_prot)/(firstcat1_prot + first2b_prot); label obs_protA = 'observed confirmation fraction under protocol definition'; first1_redef = 236; label first1_redef = 'control participants with self-reported SFI whose first event under revised definition is Type 1'; first2a_redef = 63; label first2a_redef = 'control participants with self-reported SFI whose first event under revised definition is Type 2a'; obs_redefA = (Afrac1*first1_redef + + Afrac2a*first2a_redef)/(first1_redef + first2a_redef); label obs_redefA = 'observed confirmation fraction under revised definition'; /* modeled variance inflation */ obs_protV = 1.00000141600359; label obs_protV = 'variance inflation under protocol definition'; obs_redefV = 1.04751955414456; label obs_redefV = 'variance inflation under revised definition'; run; /* sensitivity analyses */ data figB(keep=bias def power) figV(keep=vif def power) figA(keep=adj def power) figH(keep=H def power); set snapshot; za = probit(.975); T = 40; /* trial duration in months */ H = .8; /* hypothesized hazard ratio */ mu = 0.5; /* fraction of trial duration when enrollment closed */ W = (1-obs_w)**(T/12); /* overall withdrawal fraction */ array r[2] obs_protr obs_redefr; array V[2] obs_protV obs_redefV; array A[2] obs_protA obs_redefA; array Nraw[2] rawNi rawNc; array Neff[2,2] protNi protNc redefNi redefNc; array Lambda[2] protLambda redefLambda; array Gamma[2] protGamma redefGamma; array HL[2,2] protHLi protHLc redefHLi redefHLc; array HLG[2,2] protHLGi protHLGc redefHLGi redefHLGc; array X[2,2] protXi protXc redefXi redefXc; array eX[2,2] proteXi proteXc redefeXi redefeXc; array e2X[2,2] prote2Xi prote2Xc redefe2Xi redefe2Xc; array Etrue[2,2] protEitrue protEc redefEi redefEc; array Q[2,2] protQbarI protQbarC redefQbarI redefQbarC; do def=1 to 2; foo = -log(1-r[def]-obs_d)/(12*(r[def]+obs_d)); Lambda[def] = foo*r[def]; Gamma[def] = foo*obs_d; do arm=1 to 2; HL[def,arm] = Lambda[def]*((arm=2) + H*(arm=1)); HLG[def,arm] = HL[def,arm]+Gamma[def]; X[def,arm] = HLG[def,arm]*T; Q[def,arm] = Qbar(X[def,arm],mu); end; end; /* varying bias */ do def=1 to 2; do arm=1 to 2; Neff[def,arm] = Nraw[arm]*W/V[def]; Etrue[def,arm] = (A[def]*Neff[def,arm]*HL[def,arm]*Q[def,arm])/HLG[def,arm]; end; end; do bias=1 to 1.25 by .01; /* protocol */ def = 1; kay = 1 + P*(bias-1); protEi = kay*protEitrue; Z = HL[1,1]*Q[1,1]/HLG[1,1]; Heff = newtHR(T,mu,protLambda,protGamma,kay,Z); zprotpow = sqrt(protEc + protEi)*abs(log(Heff))/2 - za; power = probnorm(zprotpow); output figB; /* proposed */ def = 2; zredefpow = sqrt(redefEc + redefEi)*abs(log(H))/2 - za; power = probnorm(zredefpow); output figB; end; /* varying VIF */ do vif=1 to 1.5 by .01; do def=1 to 2; do arm=1 to 2; Neff[def,arm] = Nraw[arm]*W/vif; Etrue[def,arm] = (A[def]*Neff[def,arm]*HL[def,arm]*Q[def,arm])/HLG[def,arm]; end; end; /* protocol */ def = 1; protEi = k*protEitrue; Z = HL[1,1]*Q[1,1]/HLG[1,1]; Heff = newtHR(T,mu,protLambda,protGamma,k,Z); zprotpow = sqrt(protEc + protEi)*abs(log(Heff))/2 - za; power = probnorm(zprotpow); output figV; /* proposed */ def = 2; zredefpow = sqrt(redefEc + redefEi)*abs(log(H))/2 - za; power = probnorm(zredefpow); output figV; end; /* varying adjudication fraction */ do def=1 to 2; do arm=1 to 2; Neff[def,arm] = Nraw[arm]*W/V[def]; end; end; do adj=.5 to 1 by .01; do def=1 to 2; do arm=1 to 2; Etrue[def,arm] = (adj*Neff[def,arm]*HL[def,arm]*Q[def,arm])/HLG[def,arm]; end; end; /* protocol */ def = 1; protEi = k*protEitrue; Z = HL[1,1]*Q[1,1]/HLG[1,1]; Heff = newtHR(T,mu,protLambda,protGamma,k,Z); zprotpow = sqrt(protEc + protEi)*abs(log(Heff))/2 - za; power = probnorm(zprotpow); output figA; /* proposed */ def = 2; zredefpow = sqrt(redefEc + redefEi)*abs(log(H))/2 - za; power = probnorm(zredefpow); output figA; end; /* varying hypothesized hazard ratio */ do H=.7 to .9 by .002; do def=1 to 2; do arm=1 to 2; HL[def,arm] = Lambda[def]*((arm=2) + H*(arm=1)); HLG[def,arm] = HL[def,arm]+Gamma[def]; X[def,arm] = HLG[def,arm]*T; Q[def,arm] = Qbar(X[def,arm],mu); Neff[def,arm] = Nraw[arm]*W/V[def]; Etrue[def,arm] = (A[def]*Neff[def,arm]*HL[def,arm]*Q[def,arm])/HLG[def,arm]; end; end; /* protocol */ def = 1; protEi = k*protEitrue; Z = HL[1,1]*Q[1,1]/HLG[1,1]; Heff = newtHR(T,mu,protLambda,protGamma,k,Z); zprotpow = sqrt(protEc + protEi)*abs(log(Heff))/2 - za; power = probnorm(zprotpow); output figH; /* proposed */ def = 2; zredefpow = sqrt(redefEc + redefEi)*abs(log(H))/2 - za; power = probnorm(zredefpow); output figH; end; format power adj percentn6. def defvers.; run; /* Additional simulations */ /* -- common assumptions (from STRIDE design) */ data base; set snapshot(keep=P k); za = probit(.975); Nc = 1611; /* control enrollment */ Ni = 1611; /* intervention enrollment */ H = .8; /* hypothesized hazard ratio */ T = 40; /* trial duration in months */ mu = .5; /* fraction of trial duration when enrollment closed */ r = .148; /* events per PYF */ d = .025; /* deaths per PYF */ run; /* -- vary B and P */ data simPB; set base(drop=P k); lambda = (-1/12)*(r/(r+d))*log(1-r-d); /* control event hazard */ gamma = (-1/12)*(d/(r+d))*log(1-r-d); /* death hazard */ ratesum = lambda + gamma; hl = H*lambda; /* intervention event hazard = H * control event hazard */ hratesum = hl + gamma; QbarC = Qbar(T*ratesum,mu); QbarI = Qbar(T*hratesum,mu); Ec = Nc*lambda*QbarC/ratesum; Ei = Ni*hl*QbarI/hratesum; Z = hl*QbarI/hratesum; do frac=0 to 1 by .01; /* proposed */ redefEc = Ec*(1-frac); redefEi = Ei*(1-frac); zredefpow = sqrt(rounde(redefEc + redefEi,1e-10))*abs(log(H))/2 - za; redefpower = probnorm(zredefpow); /* protocol */ do bias=0.7 to 1.3 by .01; k = 1 + frac*(bias-1); protEc = Ec; protEi = Ei*k; Heff = newtHR(T,mu,lambda,gamma,k,Z); zprotpow = sqrt(protEc + protEi)*abs(log(Heff))/2 - za; protpower = probnorm(zprotpow); output; end; end; format protpower redefpower percentn10.; run; /* -- vary control rate and B */ data simRB; set base(drop=k r); do r = .025 to /*1*/.7 by .025; lambda = (-1/12)*(r/(r+d))*log(1-r-d); gamma = (-1/12)*(d/(r+d))*log(1-r-d); ratesum = lambda + gamma; hl = H*lambda; hratesum = hl + gamma; QbarC = Qbar(T*ratesum,mu); QbarI = Qbar(T*hratesum,mu); Ec = Nc*lambda*QbarC/ratesum; Ei = Ni*hl*QbarI/hratesum; Z = hl*QbarI/hratesum; /* proposed */ redefEc = Ec*(1-P); redefEi = Ei*(1-P); zredefpow = sqrt(rounde(redefEc + redefEi,1e-10))*abs(log(H))/2 - za; redefpower = probnorm(zredefpow); /* protocol */ do bias=0.7 to 1.3 by .01; k = 1 + P*(bias-1); protEc = Ec; protEi = Ei*k; Heff = newtHR(T,mu,lambda,gamma,k,Z); zprotpow = sqrt(protEc + protEi)*abs(log(Heff))/2 - za; protpower = probnorm(zprotpow); output; end; end; format protpower redefpower percentn10.; run; /* -- vary extension and B */ data simTB; set base(drop=k T mu); lambda = (-1/12)*(r/(r+d))*log(1-r-d); gamma = (-1/12)*(d/(r+d))*log(1-r-d); ratesum = lambda + gamma; hl = H*lambda; hratesum = hl + gamma; do T=20 to 160 by 1; mu = 20/T; QbarC = Qbar(T*ratesum,mu); QbarI = Qbar(T*hratesum,mu); Ec = Nc*lambda*QbarC/ratesum; Ei = Ni*hl*QbarI/hratesum; Z = hl*QbarI/hratesum; /* proposed */ redefEc = Ec*(1-P); redefEi = Ei*(1-P); zredefpow = sqrt(rounde(redefEc + redefEi,1e-10))*abs(log(H))/2 - za; redefpower = probnorm(zredefpow); /* protocol */ do bias=0.7 to 1.3 by .01; k = 1 + P*(bias-1); protEc = Ec; protEi = Ei*k; Heff = newtHR(T,mu,lambda,gamma,k,Z); zprotpow = sqrt(protEc + protEi)*abs(log(Heff))/2 - za; protpower = probnorm(zprotpow); output; end; end; format protpower redefpower percentn10.; run; /* FIGURES */ %macro cullbias(tail,extra=); data sub&tail; set sim&tail; if 0.95 <= round(bias,.01) <= 1.25; if round(bias,.05) = round(bias,.01); &extra; b = put(bias,5.2); bs = catx(' ','B =',b); bstr = catx(' ',"Protocol def",bs); format Heff 5.2; run; %mend; /* Figure 2 -- sensitivity power varying H */ ods graphics / width=4.5in height=3in noborder outputfmt=&imgfmt labelplacement=sa imagename="Fig2_sens_varyH" show; proc sgplot data=figH; xaxis minor minorcount=4 label='hypothesized hazard ratio' values=(.7 to .9 by .01) valueattrs=(size=6) labelattrs=(size=8); yaxis minor minorcount=4 label='power using observed data' values=(0 to 1 by .1) valueattrs=(size=6) labelattrs=(size=8) grid; refline .8 / axis=y lineattrs=(pattern=Dot); loess x=H y=power / group=def markerattrs=(size=3px) lineattrs=(thickness=1px); keylegend / title="" location=inside opaque position=bottomleft valueattrs=(size=8); run; /* Figure 3 -- sensitivity power varying B */ ods graphics / width=4.5in height=3in noborder outputfmt=&imgfmt labelplacement=sa imagename="Fig3_sens_varyB" show; proc sgplot data=figB; xaxis minor minorcount=4 label='inflation in Type 2b events (1 = no bias)' values=(1 to 1.25 by .05) valueattrs=(size=6) labelattrs=(size=8); yaxis minor minorcount=4 label='power using observed data' valueshint values=(0 to 1 by .1) valueattrs=(size=6) labelattrs=(size=8) grid; loess x=bias y=power / group=def markerattrs=(size=3px) lineattrs=(thickness=1px); keylegend / title="" location=inside opaque position=bottomleft valueattrs=(size=8); run; /* Figure 4 -- sensitivity power varying V */ ods graphics / width=4.5in height=3in noborder outputfmt=&imgfmt labelplacement=sa imagename="Fig4_sens_varyV" show; proc sgplot data=figV; xaxis minor minorcount=4 label='variance inflation (1 = no inflation)' values=(1 to 1.5 by .05) valueattrs=(size=6) labelattrs=(size=8); yaxis minor minorcount=4 label='power using observed data' valueshint values=(0 to 1 by .1) valueattrs=(size=6) labelattrs=(size=8) grid; loess x=vif y=power / group=def markerattrs=(size=3px) lineattrs=(thickness=1px); keylegend / title="" location=inside opaque position=bottomleft valueattrs=(size=8); run; /* Figure 5 -- sensitivity power varying A */ ods graphics / width=4.5in height=3in noborder outputfmt=&imgfmt labelplacement=sa imagename="Fig5_sens_varyA" show; proc sgplot data=figA; xaxis minor minorcount=4 label='overall adjudication confirmation fraction' values=(.5 to 1 by .05) valueattrs=(size=6) labelattrs=(size=8); yaxis minor minorcount=4 label='power using observed data' valueshint values=(0 to 1 by .1) valueattrs=(size=6) labelattrs=(size=8) grid; loess x=adj y=power / group=def markerattrs=(size=3px) lineattrs=(thickness=1px); keylegend / title="" location=inside opaque position=bottomright valueattrs=(size=8); run; /* Supplementary Figures 1 and 2 -- simulation varying B and P */ %cullbias(PB,extra=%str(if round(frac,.05) = round(frac,.01);)); ods graphics / width=4.5in height=3in noborder outputfmt=&imgfmt labelplacement=sa imagename="SuppFig1_sim_HvaryPB" show; proc sgplot data=subPB noautolegend; xaxis minor minorcount=4 label='proportion of Category 2 events' values=(0 to 1 by .1) valueattrs=(size=6) labelattrs=(size=8); yaxis minor minorcount=4 label='effective hazard ratio' values=(.75 to 1.05 by .05) grid valueattrs=(size=6) labelattrs=(size=8); series x=frac y=Heff / group=bs markers markerattrs=(size=3px) lineattrs=(thickness=1px pattern=Solid) transparency=.15 curvelabel curvelabelattrs=(size=6) smoothconnect; refline 1 / axis=y lineattrs=(pattern=Dot); run; ods graphics / width=4.5in height=3in noborder outputfmt=&imgfmt labelplacement=sa imagename="SuppFig2_sim_PWRvaryPB" show; proc sgplot data=subPB noautolegend nocycleattrs; xaxis minor minorcount=4 label='proportion of Category 2 events' values=(0 to 1 by .1) valueattrs=(size=6) labelattrs=(size=8); yaxis minor minorcount=4 label='projected power' values=(0 to 1 by .1) grid valueattrs=(size=6) labelattrs=(size=8); series x=frac y=redefpower / lineattrs=(thickness=1px pattern=LongDash color=black) markers markerattrs=(size=3px symbol=StarFilled color=black) transparency=0 legendlabel="%sysfunc(putn(1,outvers.))" name='alt'; series x=frac y=protpower / group=bs markers markerattrs=(size=3px) lineattrs=(thickness=1px pattern=Solid) transparency=.15 curvelabel curvelabelattrs=(size=6) smoothconnect curvelabelpos=max; run; /* Supplementary Figures 3 and 4 -- simulation varying B and r */ %macro rategrafs(cutoff); %cullbias(RB,extra=%str(if round(r,.01) <= &cutoff;)); ods graphics / width=4.5in height=3in noborder outputfmt=&imgfmt labelplacement=sa imagename="SuppFig3_sim_HvaryRB" show; proc sgplot data=subRB; xaxis minor minorcount=4 label='1-year control event rate (Cats 1 and 2 combined)' values=(0 to &cutoff by .05) valueattrs=(size=6) labelattrs=(size=8); yaxis minor minorcount=4 label='effective hazard ratio' values=(.7 to 1.7 by .1) grid valueattrs=(size=6) labelattrs=(size=8) valuesformat=data; series x=r y=Heff / group=bs markers markerattrs=(size=3px) lineattrs=(thickness=1px pattern=Solid) transparency=.15 curvelabel curvelabelattrs=(size=6) smoothconnect curvelabelpos=max; refline 1 / axis=y lineattrs=(pattern=Dot); run; ods graphics / width=4.5in height=3in noborder outputfmt=&imgfmt labelplacement=sa imagename="SuppFig4_sim_PWRvaryRB" show; proc sgplot data=subRB nocycleattrs; xaxis minor minorcount=4 label='1-year control event rate (Cats 1 and 2 combined)' values=(0 to &cutoff by .05) valueattrs=(size=6) labelattrs=(size=8); yaxis minor minorcount=4 label='projected power' values=(0 to 1 by .1) grid valueattrs=(size=6) labelattrs=(size=8); series x=r y=redefpower / lineattrs=(thickness=1px pattern=LongDash color=black) markers markerattrs=(size=3px symbol=StarFilled color=black) transparency=0 name='alt' legendlabel="%sysfunc(putn(2,outvers.))"; series x=r y=protpower / group=bs markers markerattrs=(size=3px) lineattrs=(thickness=1px pattern=Solid) transparency=.15 curvelabel curvelabelattrs=(size=6) smoothconnect curvelabelpos=min; run; %mend; %rategrafs(.7); /* Supplementary Figures 5 and 6 -- simulation varying B and T */ %cullbias(TB,extra=%str(if round(T,5) = round(T,1);)); ods graphics / width=4.5in height=3in noborder outputfmt=&imgfmt labelplacement=sa imagename="SuppFig5_sim_HvaryTB" show; proc sgplot data=subTB noautolegend; xaxis minor minorcount=3 label='trial duration (months)' values=(20 to 160 by 20) valueattrs=(size=6) labelattrs=(size=8); yaxis minor minorcount=4 label='effective hazard ratio' values=(.75 to 1.05 by .05) grid valueattrs=(size=6) labelattrs=(size=8); series x=T y=Heff / group=bs markers markerattrs=(size=3px) lineattrs=(thickness=1px pattern=Solid) transparency=.15 curvelabel curvelabelattrs=(size=6) smoothconnect; refline 1 / axis=y lineattrs=(pattern=Dot); run; ods graphics / width=4.5in height=3in noborder outputfmt=&imgfmt labelplacement=sa imagename="SuppFig6_sim_PWRvaryTB" show; proc sgplot data=subTB noautolegend nocycleattrs; xaxis minor minorcount=3 label='trial duration (months)' values=(20 to 160 by 20) valueattrs=(size=6) labelattrs=(size=8); yaxis minor minorcount=4 label='projected power' values=(0 to 1 by .1) grid valueattrs=(size=6) labelattrs=(size=8); series x=T y=redefpower / lineattrs=(thickness=1px pattern=LongDash color=black) markers markerattrs=(size=3px symbol=StarFilled color=black) transparency=0 name='alt' legendlabel="%sysfunc(putn(2,outvers.))"; series x=T y=protpower / group=bs markers markerattrs=(size=3px) lineattrs=(thickness=1px pattern=Solid) transparency=.15 curvelabel curvelabelattrs=(size=6) smoothconnect curvelabelpos=min; run;