options ps=120 ls=160 nodate pageno=1; dm log "clear;" continue; dm out "clear;" continue; ods html close; ods listing; %let outdir=C:\Documents\when to censor - composite events; libname out "C:\Documents\when to censor - composite events"; %let reps=1000; %let n=1000; %let Mmean=6; *create data - generate visit structure and then data underlying it on a daily level that is only seen at visits; %let maxfu=120; *reset datasets holding results; proc datasets library=work nolist; delete biases countlost: capt: cens: km: meas: prev: risk: one two; quit; data one; call streaminit(477); do sim=1 to &reps.; do id=1 to &n.; m=0; *m is number of months/units of time; lastm=0; *keep track of time between visits; do while (m<=&maxfu.); nextm=min(m+max(1,min(rand('poisson',&Mmean.),11)),&maxfu.); *next visit is average of Mmean months after current visit - bound at 1,11 to ensure no LTFU in complete data; if m>=&maxfu. then leave; *stop generating data after max 10 years of follow-up; output; lastm=m; *lag variables; m=nextm; end; end; end; run; /*proc means data=one min mean median max; var minterval; run;*/ *data set ONE contained visits, now generate data values that underlie those visits; ********************************************************************************************************************************************************; * SET UP MACROS; *simplify code to calculate a probability and generate a bernoulli random variable; %macro bern(var); rand('bernoulli',1/(1+exp(-(&var.)))); %mend bern; ********************************************************************************************************************************************************; ******** j=1 MEASURED, j=2 CAPTURED; %macro newtruth(risk,j); data two; set one; by sim id m; retain tmpy tmpj y j out; if first.id then do; /*define baseline variables:*/ tmpy=rand('uniform',0,120)*rand('bernoulli',&risk./100); tmpj=1+rand('bernoulli',&j./100); y=0; /*y is the outcome*/ j=0; end; *assign time-varying conditions; if m=120 or lastseen>=out then do; *if they do not meet definition of LTFU ever, they are admin censored; outcens=out; y=ytrue; j=jtrue; drop=0; end; else if defmet0)); *if first visit is last visit, person contributes no person-time to analysis...; merge cens1 (rename=(y=ytrue j=jtrue)) tmp; by sim id; defmet=lastseen+12; *this is when they meet the LTFU definition; if defmet>=120 or lastseen>=out then do; *if administratively censored or had the event before their last visit, person is not LTFU; outcens=out; y=ytrue; j=jtrue; drop=0; end; else if defmet=120 or lastseen>=out then do; *if they do not meet definition of LTFU ever, they are admin censored; outcensdef=out; outcenslast=out; y=ytrue; j=jtrue; drop=0; end; else if defmet. then ch1=-log(s1); if s2>. then ch2=-log(s2); ch=ch1+ch2; s=exp(-ch); r=1-s; if last.sim then output; keep sim outcens r; run; proc append base=kmtot_&scen. data=km; run; %end; ************************ keep track of prevalence of LTFU ************************; proc sql; create table countlost_tmp as select sim, sum(drop)/count(*) as percent from cens_flat group by sim; quit; proc append base=countlost data=countlost_tmp; run; %end; %biascalc(&event.,def); %biascalc(&event.,last); %biascalc(&event.,new); *reset datasets holding results; proc datasets library=work nolist; delete km&event._def km&event._last; quit; %if "&event."="tot" %then %do; proc datasets library=work nolist; delete km&event._new; quit; %end; %mend censorsetup; ********************************************************************************************************************************************************************************; *** 1: CENSORING IS RANDOM; ********************************************************************************************************************************************************************************; *event j1 (MEASURED EVENT) has higher cause-specific hazard than j2 (CAPTURED EVENT); *inputs to newtruth macro are 1: OR for period exposure-> J1, 2: OR for period exposure -> J2, 3: baseline period risk for J1, 4: baseline period risk for J2; %let pctdrop=40; %let constrisk=40; %macro wrapper; %global riskj1 riskj2 totrisk; data _null_; maxpct=(100-&constrisk.)/100; call symput("maxpct",strip(put(round(maxpct*100,5),3.))); run; %put maxpct=&maxpct.; %do pctcapt=5 %to &maxpct %by 5; /*hold risk of measured event constant and vary risk of captured event as a competing risk*/ data _null_; pct=&pctcapt./100; constrisk=&constrisk./100; riskj1=constrisk; riskj2=constrisk*(pct/(1-pct)); call symput("riskj1",strip(put(riskj1*100,3.))); call symput("riskj2",strip(put(riskj2*100,3.))); call symput("totrisk",strip(put((riskj1+riskj2)*100,3.))); run; %put "measured event " pctcapt=&pctcapt. constrisk=&constrisk. riskj1=&riskj1. riskj2=&riskj2. totrisk=&totrisk.; %newtruth(&totrisk.,&pctcapt.); *add date last seen; data cens1tot; set two; by sim; tmpdrop=rand('bernoulli',&pctdrop./100); censtmp=rand('uniform',0,120)*tmpdrop+120*(1-tmpdrop); run; * censorsetup macro should implement censoring, then apply the two censoring schemes - censor at definition and censor at last seen; %censorsetup(meas); %end; /*hold risk of captured event constant and vary risk of measured event as a competing risk*/ %do pctmeas=5 %to &maxpct %by 5; data _null_; pct=&pctmeas./100; constrisk=&constrisk./100; riskj1=constrisk*(pct/(1-pct)); riskj2=constrisk; call symput("riskj1",strip(put(riskj1*100,3.))); call symput("riskj2",strip(put(riskj2*100,3.))); call symput("totrisk",strip(put((riskj1+riskj2)*100,3.))); call symput("pctcapt",strip(put(100-&pctmeas.,3.))); run; %put "captured event " pctmeas=&pctmeas. pctcapt=&pctcapt. constrisk=&constrisk. riskj1=&riskj1. riskj2=&riskj2. totrisk=&totrisk.; %newtruth(&totrisk.,&pctcapt.); *add date last seen; data cens1tot; set two; by sim; tmpdrop=rand('bernoulli',&pctdrop./100); censtmp=rand('uniform',0,120)*tmpdrop+120*(1-tmpdrop); run; * censorsetup macro should implement censoring, then apply the two censoring schemes - censor at definition and censor at last seen; %censorsetup(capt); %end; %do pctcapt=5 %to 95 %by 5; data _null_; call symput("riskj1",strip(put(&constrisk.*(100-&pctcapt.)/100,3.))); call symput("riskj2",strip(put(&constrisk.*&pctcapt./100,3.))); call symput("totrisk",strip(put(&constrisk.,3.))); run; /*hold total risk constant and vary proportion of risk that is captured*/ %put "total risk " pctcapt=&pctcapt. constrisk=&constrisk. riskj1=&riskj1. riskj2=&riskj2. totrisk=&totrisk.; %newtruth(&constrisk.,&pctcapt.); *add date last seen; data cens1tot; set two; by sim; tmpdrop=rand('bernoulli',&pctdrop./100); censtmp=rand('uniform',0,120)*tmpdrop+120*(1-tmpdrop); run; %censorsetup(tot); %end; %mend wrapper; options nosource nonotes; %wrapper; options source notes; proc sort data=biases; by eventtype censstrategy; run; data anno; retain xsys '1' ysys '2' color 'black' line 1 size 30; function='move'; x=0; y=0; output; function='draw'; x=100; y=0; output; run; %macro graph(j); data graph_tot; if "&j"="total" then set biases (where=(eventtype="tot")); if "&j"="measured" then set biases (where=(eventtype="meas")); if "&j"="captured" then set biases (where=(eventtype="capt")); if censstrategy="def" then do; linelabel="At LTFU definition"; group=1; end; else if censstrategy="last" then do; linelabel="At last visit"; group=2; end; else if censstrategy="new" then do; linelabel="Hybrid"; group=3; end; xaxis_pctcapt=round(riskj2/totrisk,0.01); med_bias=med_bias*100; q1_bias=q1_bias*100; q3_bias=q3_bias*100; run; proc sort data=graph_tot; by group xaxis_pctcapt; run; proc sql noprint; select floor(min(r_bias)) into: lo from graph_tot; select ceil(max(r_bias)) into: hi from graph_tot; quit; title; goptions reset=all device=zgif ftext="Albany AMT" htext=12pt gsfname=grafout gsfmode=replace xmax=4 ymax=4 xpixels=4000 ypixels=4000; axis1 label=(angle=90 "bias") w=8 major=(w=8 h=18) minor=none offset=(0,0) order=(-2 to 2 by 1) origin=(22,17) pct; axis2 label=("pct capt") w=8 major=(w=8 h=7) minor=none offset=(0,0) order=(0 to 1 by 0.2) value=(angle=45 rotate=0); symbol1 c=grayd0 v=none i=sm25 w=16 l=1 mode=include; symbol2 c=grayd0 v=none i=sm25 w=16 l=22 mode=include; symbol3 c=gray70 v=none i=sm25 w=16 l=1 mode=include; filename grafout "&outdir.\risk40_ltfu20_&j..png"; proc gplot data=graph_tot; plot med_bias*xaxis_pctcapt=group / noframe nolegend annotate=anno haxis=axis2 vaxis=axis1;* legend=legend1; run; quit; %mend graph; %graph(total); %graph(measured); %graph(captured); /*proc export data=biases outfile="&outdir.\const risk 40 ltfu 40_20aug18.csv" dbms=csv replace;*/ /* run;*/ proc import file="&outdir.\const risk 40 ltfu 20_20aug18.csv" dbms=csv out=r40l20 replace; run; proc import file="&outdir.\const risk 40 ltfu 40_20aug18.csv" dbms=csv out=r40l40 replace; run; proc import file="&outdir.\const risk 15 ltfu 20_20aug18.csv" dbms=csv out=r15l20 replace; run; proc import file="&outdir.\const risk 15 ltfu 40_20aug18.csv" dbms=csv out=r15l40 replace; run; proc sort data=r15l20; by riskj2; run; data totbias; set r40l20 (in=a) r40l40 (in=b) r15l20 (in=c) r15l40 (in=d); where eventtype="tot"; if a or c then ltfu=20; else ltfu=40; retain avg_bias_def med_bias_def; if censstrategy="def" then do; avg_bias_def=avg_bias; med_bias_def=med_bias; end; if censstrategy="last" then do; avg_bias_last=avg_bias; med_bias_last=med_bias; if abs(med_bias_last)