**************************************************************************************************** * PROGRAM OVERVIEW **************************************************************************************************** * * PROGRAM: ms_nvrexposed.sas * * Created (mm/dd/yyyy): 02/22/2018 * Last modified: 03/27/2018 * Version: 1.1 * *-------------------------------------------------------------------------------------------------- * PURPOSE: * This macro will compute nvrexposed cohort. * * Program inputs: * -_Denom&Group. created by ms_cidadenom * * Program outputs: * -The _PtsMasterList_nvrexp * * PARAMETERS: * * Programming Notes: * * * *-------------------------------------------------------------------------------------------------- * CONTACT INFO: * Mini-Sentinel Coordinating Center * info@mini-sentinel.org * *-------------------------------------------------------------------------------------------------- * CHANGE LOG: * * Version Date Initials Comment (reference external documentation when available) * ------- -------- -------- --------------------------------------------------------------- * 1.1 03/27/18 AP Fixed bug when requested geographic strata (QRP-562) * ****************************************************************************************************; %macro ms_nvrexposed(); %put =====> MACRO CALLED: ms_nvrexposed v1.1; %IF "&NEVEREXPOSEDCOHORT." EQ "Y" %then %do; *From the denominator file, remove the patients that have an evidence of index-defining code; proc sort nodupkey data = _GroupIndex (keep = patid) out = _indexPts; by PatID; run; *Delete _groupindex patients from denominator file; %ms_delpatients(datafile=_nvrexp&Group., ptsfile=_indexPts, Outfile=_nvrexp&Group.); *add deathdt; data _nvrexp&Group.; merge _nvrexp&Group. (in = a) DeathExtract(in=b); *Already sorted by PatID; by PatId; if a; enr_end_equal_death = 0; if &censor_dth.=1 then do; if b and DeathDt <= Enr_End then do; EnrEpisodeCensoredAtDeath=1; if enr_end = deathdt then enr_end_equal_death = 1; /*For censoring table*/ if Enr_end < Enr_Start then delete; end; end; run; *take the 1st enrollment span per PATID and set the index date to the 1st eligible day.; proc sort data=_nvrexp&Group.; by patid DenomEnrStart DenomEnrEnd; run; data _PtsMasterlist_nvrexp; set _nvrexp&Group.; by patid DenomEnrStart DenomEnrEnd; if first.patid; length IndexDt 4.; format IndexDt EpisodeEndDt date9.; IndexDt = DenomEnrStart; EpisodeEndDt = Enr_End; run; *Truncate episodes with IOT/FUT; proc sql noprint; create table _IOT_nvrexp as select distinct Epi.PatId, Epi.IndexDt, min(Trunk.Adate) as TrunkDate format date9. from _PtsMasterlist_nvrexp as Epi, _GroupWashForTrunk as trunk where Epi.Patid = trunk.Patid and %MS_PeriodsOverlap(period1=Epi.IndexDt Epi.EpisodeEndDt, period2=trunk.ADate) group by Epi.Patid, Epi.IndexDt order by Epi.Patid, Epi.IndexDt; quit; *Add looking period related variables; proc sql noprint undo_policy=none; create table _PtsMasterlist_nvrexp as select dat.*, look.LookEnd as IndexDtLookEndDt, Look.Look as IndexLook from _PtsMasterlist_nvrexp as dat, looks as look where look.LookStart<=dat.IndexDt<=look.LookEnd order by Patid,IndexDt; quit; *Create a patient profile for never exposed group; data _PtsMasterlist_nvrexp; merge _PtsMasterlist_nvrexp (in = nvrexp) _IOT_nvrexp(in=iot); by Patid IndexDt; if nvrexp; OrigEpisEndDt = EpisodeEndDt; length T2fupwashper Type blackoutper IndexLook 8. EpisodeType $3 T2CohortDef $2 EndFollowUpType $20; /*Group name*/ length Group $40; Group = cat("&ITGROUP","_nvrexp"); *Define ITT; if &itt.=1 then EpisodeType="ITT"; else EpisodeType="EPI"; *COHORTDEF, FUPWASHER, BLACKOUT, TYPE, EndFollowUpType; T2CohortDef = "&COHORTDEF."; /*set it same value as exposed group, even though it is always 01 in never exposed group*/ T2fupwashper = &FUPWASHPER.; blackoutper = &blackoutper.; Type = &Type.; EndFollowUpType = ""; *Adjust EpisodeEndDt if EpisodeEndDt > enddate; %if &censor_qryend = 1 %then %do; if EpisodeEndDt > &enddate. then EpisodeEndDt = &enddate.; %end; /*Adjust EpisodeEndDt if EpisodeEndDt > DeathDt*/ if EnrEpisodeCensoredAtDeath = 1 and EpisodeEndDt > DeathDt then EpisodeEnddt = deathdt; *Trucate episode at IOT; *Only adjust for IOT when IOT prior to EpisodeEndDate. IOT after EpisodeEndDt can occur if Epiosde shortened to occur on EndDate; if iot and TrunkDate and trunkdate <= EpisodeEndDt then EpisodeEndDt=TrunkDate; *blackoutper; if blackoutper =. then blackoutper=0; *If ITT episodes are created; %IF %EVAL(&itt.>=1) %THEN %DO; EpisodeEndDt = min((IndexDt+&ittdays.)-1, EpisodeEndDt); %END; %ELSE %DO; *If as-treated episodes are created; *Maximum Episode Duration; if not missing(&MAXEPISDUR.) and EpisodeEndDt-IndexDt + 1 > &MAXEPISDUR. then EpisodeEndDt = IndexDt + &MAXEPISDUR. -1; %END; *Minimum Episode Duration; if EpisodeEndDt-IndexDt + 1 >= &MINEPISDUR. then MinEpiDurMet=1; maxepisdur = &MAXEPISDUR.; minepisdur = &MINEPISDUR.; *Patient must be enrolled throughout the at-risk period when ITT; if minepisdur > 0 then do; if Enr_Start <= indexDt and Indexdt + minepisdur - 1 <= Enr_end then FUEnrolMet=1; end; else FUEnrolMet=1; *remove episodes that do not have at least one day at risk post blackout (at least one day to observe an event); if blackoutper and EpisodeEndDt - (IndexDt + blackoutper) < 0 then delete; *If ADS files are to be created, patients must have at least one post blackout day at risk (at least one day to observe and event) in the look of their index date; if "&Analysis."="ps" or "&Analysis."="ads" or "&Analysis."="ms" then do; if blackoutper and IndexDtLookEndDt - (IndexDt + blackoutper) < 0 then delete; end; if FUEnrolMet and MinEpiDurMet; *Defensive coding; if IndexDt > EpisodeEndDt then delete; keep Group PatID IndexDt EpisodeEndDt ENR_START ENR_END EligEpisode Birth_Date sex Race Hispanic zip: state hhs_reg cb_reg DeathDt EnrEpisodeCensoredAtDeath enr_end_equal_death maxepisdur T2FupWashPer blackoutper T&type.CohortDef IndexLook trunkdate Type EpisodeType EndFollowUpType OrigEpisEndDt; run; *Process Zip Codes to the never exposed group; %IF %STR(&GEOG.) ne %STR() & %LENGTH(&GEOG.) > 0. %then %do; data _PtsMasterlist_nvrexp; set _PtsMasterlist_nvrexp; if missing(zip_date) or indexdt < zip_date then Zip_uncertain = 'Y'; else zip_uncertain = 'N'; run; %END; *CALCULATE AGE and AGE STRATA; %ms_agestrat(infile=_PtsMasterlist_nvrexp, outfile=_PtsMasterlist_nvrexp, startdt=birth_date, enddt=IndexDt, timestrat=&agestrat.); *Find episodes with Events in T2FupWashPer; *If T2FupWashPer=. then patients need to never have had an Event (hence 99999); proc sql noprint; create table _EventsInFupWash_nvrexp(where=(InGap=1)) as select distinct Epi.PatId, Epi.IndexDt, %MS_PeriodsOverlap(period1=Epi.IndexDt-min(Epi.T2FupWashPer,99999) Epi.IndexDt-1, period2=pov5.ADate pov5.ExpireDt) as InGap from _PtsMasterlist_nvrexp as Epi, _FUPEvent(keep=PatId Adate Expiredt) as pov5 where Epi.Patid = pov5.Patid order by Epi.Patid, Epi.indexDt; quit; *Identify episodes with Events in Blackoutwindow; proc sql noprint; create table _EventsInBlackout_nvrexp(where=(InGap=1)) as select distinct Epi.PatId, Epi.IndexDt, %MS_PeriodsOverlap(period1=Epi.IndexDt Epi.IndexDt+Epi.blackoutper-1, period2=pov5.ADate pov5.ExpireDt) as InGap from _PtsMasterlist_nvrexp as Epi, _FUPEvent(keep=PatId Adate Expiredt) as pov5 where Epi.Patid = pov5.Patid order by Epi.Patid, Epi.indexDt; quit; *Identify episodes with IOC in T2FupWashPer at IndexDt; proc sql noprint; create table _WashEventsInFupWash_nvrexp(where=(InGap=1)) as select distinct Epi.PatId, Epi.IndexDt, %MS_PeriodsOverlap(period1=Epi.IndexDt-min(Epi.T2FupWashPer,99999) Epi.IndexDt-1/*+Epi.blackoutper*/, period2=pov6.ADate pov6.ExpireDt) as InGap from _PtsMasterlist_nvrexp as Epi, _FUPWash(keep=PatId Adate Expiredt) as pov6 where Epi.Patid = pov6.Patid order by Epi.Patid, Epi.indexDt; quit; *Keep only valid Episodes, i.e. those without events or IOC in T2FupWashPer; data _PtsMasterlist_nvrexp; merge _PtsMasterlist_nvrexp(in=a) _EventsInFupWash_nvrexp(in=b keep=PatId IndexDt) _WashEventsInFupWash_nvrexp(in=c keep=PatId IndexDt) _EventsInBlackout_nvrexp(in=d keep=patid indexdt); by PatId IndexDt; if a and not b and not c and not d ; run; *Keep only overlapping events; %ms_shaveoutside(reffile=_PtsMasterlist_nvrexp, refstart=indexdt, /*Not possible to have event in blackoutper*/ refend=EpisodeEndDt, KeepPartBf=Y, ToShaveFile=_FUPEvent, ToShaveStart=Adate, ToShaveEnd=ExpireDt, outfile=_FUPEventShaved); *Identify episodes with events; proc sql noprint; create table _UEPisodesWithEvents(where=(InGap=1)) as select distinct index.Patid, index.IndexDt, inc.Adate as EventDt, inc.Code, %MS_PeriodsOverlap(period1=index.indexdt index.EpisodeEndDt, period2=Inc.ADate Inc.ExpireDt) as InGap from _PtsMasterlist_nvrexp as index, _FUPEventShaved as Inc where index.Patid = Inc.Patid; quit; *Summarize Events; proc means data=_UEPisodesWithEvents nway noprint; var InGap EventDt; class Patid IndexDt; output out=_UEPisodesWithEvents(drop=_:) sum(InGap)=NumEvents min(EventDt)=FEventDt/KEEPLEN; run; *Set events and final variables; data _PtsMasterlist_nvrexp; merge _PtsMasterlist_nvrexp(in=a) _UEPisodesWithEvents(in=b); by Patid IndexDt; if a; length NumDisp TotRxSup TotRxAmt Age LastLookFollowed LastLookFollowedDt 8.; Age = (IndexDt-birth_date)/365.25; Year = Year(IndexDt); tte=Min(EpisodeEndDt,Enr_End,FEventDt)-IndexDt -&BLACKOUTPER. +1; /*Enumerate censoring criteria*/ /*initialize censoring variables*/ cens_elig = 0; cens_dth = 0; cens_dpend = 0; cens_qryend = 0; cens_episend = 0; cens_spec = 0; cens_event = 0; /*Episode is not adjusted for events, so need to consider true censoring date*/ EpisodeEndDt_Censor = min(EpisodeEndDt,Enr_End,FEventDt); /*Censored due to event*/ if EpisodeEndDt_Censor = FEventDt then do; Event_flag = 'Y'; cens_event = 1; end; else do; Event_flag = 'N'; cens_event = 0; end; /*Censored due to enrollment*/ If EpisodeEndDt_Censor = Enr_End then cens_elig = 1; else cens_elig = 0; /*Reset if episode truncated due to death and original enrollment did not occur on death date*/ if cens_elig = 1 and EnrEpisodeCensoredAtDeath = 1 and enr_end_equal_death ne 1 then cens_elig = 0; /*Censored due to death*/ If EnrEpisodeCensoredAtDeath = 1 and EpisodeEndDt_Censor = Deathdt then cens_dth = 1; else cens_dth = 0; /*Censored due to query end*/ if EpisodeEndDt_Censor = &enddate. then cens_qryend = 1; else cens_qryend = 0; /*Censored due to DP Max*/ %if &Censor_DPEnd. = 1 %then %do; if EpisodeEndDt_Censor = &dp_maxdate. then cens_dpend = 1; else cens_dpend = 0; %if &DPMax_Adjust. =1 %then %do; Cens_QRYEND = 0; /*Reset Cens_qryend because we adjusted enddate to equal dp_maxdate*/ %end; %end; /*Censored due to requester defined criteria*/ if trunkdate and EpisodeEndDt_Censor = trunkdate then cens_spec = 1; else cens_spec = 0; /*Censored due to episode end date*/ /*No adjustments have been made*/ *if (OrigEpisEndDt = EpisodeEndDt_Censor) then cens_episend = 1; *If ITT episodes are created; %IF %EVAL(&itt.>=1) %THEN %DO; if EpisodeEndDt_Censor = IndexDt+&ittdays.-1 then cens_episend = 1; else cens_episend = 0; %END; %ELSE %DO; *If as-treated episodes are created; if not missing(maxepisdur) and EpisodeEndDt_Censor = IndexDt + maxepisdur -1 then cens_episend = 1; else cens_episend = 0; %END; /*Adjust enr_end for death (for denoms)*/ if EnrEpisodeCensoredAtDeath and DeathDt <= Enr_End then Enr_End=DeathDt; format EpisodeEndDt_Censor date9.; timetocensor = tte; NumDisp = 0; TotRxAmt = 0; TotRxSup = 0; LastLookFollowed = .; LastLookFollowedDt = .; keep Group Type PatId Enr_start Enr_End IndexDt EpisodeEndDt NumEvents FEventDt tte year EpisodeType blackoutper: IndexLook cens_elig cens_dth cens_dpend cens_qryend cens_episend cens_spec cens_event deathdt Event_flag timetocensor NumDisp TotRxAmt TotRxSup Age Birth_Date sex race hispanic AgeGroup MinAgeDate MaxAgeDate zip: state cb_reg hhs_reg LastLook: EndFollowupType ; run; *Remove all labels; proc datasets lib=work memtype=data nolist nowarn; modify _PtsMasterlist_nvrexp; attrib _all_ label=' '; run; /*Set the never exposed and exposed group in same _PtsMasterList*/ data _PtsMasterList; length group $40; set _PtsMasterList _PtsMasterlist_nvrexp; run; %END; *END OF NEVEREXPOSED COHORT; %put NOTE: ******** END OF MACRO: ms_nvrexposed v1.1 ********; %mend ms_nvrexposed;