Base file: K:\TIDE Projects\FDA_Sentinel\07. Projects and Task Orders\01. Modular Programs\QRP\Distributed\QRP_3.3.3\inputfiles\macros\ms_cidanum.sas
Compared file: A:\git\radar\inputfiles\macros\ms_cidanum.sas
***************************************************************************************************
* PROGRAM OVERVIEW
****************************************************************************************************
*
* PROGRAM: ms_cidanum.sas
*
* Created (mm/dd/yyyy): 12/19/2014
* Last modified: 11/30/2016
* Version: 1.21
*
*--------------------------------------------------------------------------------------------------
* PURPOSE:
* This macro will compute the numerators and call many other macros creating the denominators
* and the output tables.
*
* Program inputs:
* -infolder.&COHORTFILE.
* -infolder.&TYPE1FILE.
* -infolder.&TYPE2FILE.
* -infolder.&TYPE3FILE.
* -infolder.&TYPE4FILE.
* -infolder.&comorbfile.
* -infolder.&T3METADATA.
* -infolder.&STOCKPILINGFILE.
* -infolder.&INCLUSIONCODES.
* -infolder.&PREGCODES.
* -infolder.&MOICODES.
* -infolder.&COVARIATECODES.
*
* Program outputs:
* -DPLocal.&RUNID._mstr
*
* PARAMETERS:
* -RUNID = Run identifier to denote each execution of the program
* -MSPROJID = MSOC-defined value
* -MSWPTYPE = MSOC-defined value
* -MSWPID = MSOC-defined value
* -MSDPID = MSOC-defined value
* -MSVERID = MSOC-defined value
* -AGESTRAT = Age groups stratification. E.G.: 20-44 45-64 65+
* -PERIODIDSTART = Starting PERIODID from the MONITORINGFILE
* -PERIODIDEND = Ending PERIODID from the MONITORINGFILE
* -ANALYSIS = Type of Analysis to perform in ms_cidacov
* -MONITORNGFILE = Name of the SAS dataset that defines the relevant query periods for execution
* -COHORTFILE = Name of the SAS dataset that defines how the cohort should be created
(gaps, coverage,...)
* -TYPE1FILE = Name of the SAS dataset that defines type 1 cohort parameters
(washouts, cohortdef, ...)
* -TYPE2FILE = Name of the SAS dataset that defines type 2 cohort parameters
(washouts, cohortdef, ...)
* -TYPE3FILE = Name of the SAS dataset that defines type 3 cohort parameters
(washouts, cohortdef, ...)
* -TYPE4FILE = Name of the SAS dataset that defines type 4 cohort parameters
(washouts, cohortdef, ...)
* -T3METADATA = Name of the SAS dataset that defines the metadata generated for each time
period request
* -COHORTCODES = Name of the SAS dataset that includes the codes defining the cohort
* -INCLUSIONCODES = Name of the SAS dataset that includes the codes defining inclusions/exclusions
* -COVARIATECODES = Name of the SAS dataset that includes the codes associated with covariates
used to determine the propensity score
* -STOCKPILINGFILE= Name of the SAS dataset that defines how the stockpiling algorithm should be
performed
* -UTILFILE = Name of the SAS dataset that defines how ms_util should be performed
* -COMBOFILE = Name of the SAS dataset that will be used to create combination items. When this
parameter is specified, "CODES" and "STOCK" input files are also required.
* -COMORBFILE = Name of the SAS dataset that defines how ms_cci_elix should be performed
* -DRUGCLASSFILE = Name of the SAS dataset that lists NDCs by generic and class names and allows
the program to count the number of distinct generic names and class names for
input into the PS match model
* -FREEZEDATA = Indicates if MSDD patient data shoul be copied to DPLocal
* -LABSCODEMAP = Name of the SAS dataset that contains laboratory matching information
* -SURVEILLANCEMODE = Indicates if the module should be executed in suveilance mode and wich type
* -PREGCODES = Name of the SAS dataset that contains pre/post term delivery codes for type 4 analysis
* -MOICODES = Name of the SAS dataset that contains medication/drug use codes for type 4 analysis
* -ZIPFILE = Name of the SAS dataset that contains list of valid Zip/Zip State values
* Programming Notes:
*
*
*
*--------------------------------------------------------------------------------------------------
* CONTACT INFO:
* Mini-Sentinel Coordinating Center
* info@mini-sentinel.org
*
*--------------------------------------------------------------------------------------------------
* CHANGE LOG:
*
* Version Date Initials Comment (reference external documentation when available)
* ------- -------- -------- ---------------------------------------------------------------
* 1.4 01/22/14 DM Minor bug fix. Added de-duped sort to _agegroups dataset
*
* 1.5 02/06/15 SL(EM) Removed MSREQID definition line
*
* 1.6 02/23/15 DM Removed Enr_Start and Enr_end when shaving ITDrugsComb to
* avoid a warning in the log and incorrect results in outputs
* Modified how the required follow-up time after exposure is
* calculated
*
* 1.7 03/11_15 DM Renamed Episodes_analysis to Pt_Analysis_Episode_Num
* Renamed Episodes_exposure to Pt_Exposure_Episode_Num
* Process _ITDrugs, _ITMeds and _ITLabs with ms_shaveoutside
* even when they are empty
*
* 1.8 03/26/15 DM Changed the method to verify MSDD file existence before calling
* the FREEZEDATA macro
*
* 1.9 04/13/15 DM Changed the stockpiling input file parameters processing
*
* 1.10 06/26/15 DM Verify that patients have at least one day at risk in the look
* of their index date (analysis = PS or ADS).
* Removed Surveillance Analysis related code.
*
* 1.11 09/14/15 DM Added code for Type 4 analysis.
*
* 1.12 03/10/16 VC Enhance Inclusions and Covariates to allow more complex criteria.
*
* 1.13 04/21/16 DM Carried forward changes made in 2.2.1 version (incidence at day 0)
*
* 1.14 05/26/16 VC JIRA 173
*
* 1.15 06/14/16 DM Fix for RxAmt and RxAmt in Type 1 plus hybrid AT/ITT
*
* 1.16 07/05/16 DM Added time to censor data plus fix for JIRA #DEVI-14
*
* 1.17 08/10/16 AP (SOC) Modified to remove true duplicates in SDD (QCI-195)
* DM Fixed same day claims issue in type 1 analysis (QCI-208)
*
* 1.18 08/15/16 DM Added Surveillance Analysis related code removed in 1.10.
*
* 1.19 10/17/16 DM Fixed an issue regarding Eventcount (QCI-219) and replaced an
* append statement with a set one (QCI-210)
*
* 1.20 11/14/16 AP Added ability to stratify T1/T2 output by geographic location
* Added ability to turn off envelope macro
*
* 1.21 11/30/16 AP Added ability to truncate TTE at Query period end date and also to
* enumerate reasons for censoring
*
***************************************************************************************************;
%macro ms_cidanum(RUNID,
MSPROJID,
MSWPTYPE,
MSWPID,
MSDPID,
MSVERID,
AGESTRAT,
PERIODIDSTART,
PERIODIDEND,
ANALYSIS,
MONITORINGFILE,
COHORTFILE,
TYPE1FILE,
TYPE2FILE,
TYPE3FILE,
TYPE4FILE,
T3METADATA,
COHORTCODES,
INCLUSIONCODES,
COVARIATECODES,
STOCKPILINGFILE,
UTILFILE,
COMBOFILE,
COMORBFILE,
DRUGCLASSFILE,
FREEZEDATA,
LABSCODEMAP,
SURVEILLANCEMODE,
PREGCODES,
MOICODES,
ZIPFILE
);
%PUT "ms_cidanum";
%put =====> MACRO CALLED: ms_cidanum v1.21;
%ms_starttimer(START=MAINSTART);
/*---------------------------------------------------*/
/* 01 -- Set up variables needed for the run */
/*---------------------------------------------------*/
%LET RUNID=%LOWCASE(&RUNID.);
%LET MSPROJID=%LOWCASE(&MSPROJID.);
%LET MSWPTYPE=%LOWCASE(&MSWPTYPE.);
%LET MSWPID=%LOWCASE(&MSWPID.);
%LET MSDPID=%LOWCASE(&MSDPID.);
%LET MSVERID=%LOWCASE(&MSVERID.);
%LET AGESTRAT=%LOWCASE(&AGESTRAT.);
%LET PERIODIDSTART=%LOWCASE(&PERIODIDSTART.);
%LET PERIODIDEND=%LOWCASE(&PERIODIDEND.);
%LET ANALYSIS=%LOWCASE(&ANALYSIS.);
%LET SURVEILLANCEMODE=%LOWCASE(&SURVEILLANCEMODE.);
%LET MONITORINGFILE=%TRIM(%SYSFUNC(TranWrd(%LOWCASE(&MONITORINGFILE.), .sas7bdat, %STR())));
%LET COHORTFILE=%TRIM(%SYSFUNC(TranWrd(%LOWCASE(&COHORTFILE.), .sas7bdat, %STR())));
%LET TYPE1FILE=%TRIM(%SYSFUNC(TranWrd(%LOWCASE(&TYPE1FILE.), .sas7bdat, %STR())));
%LET TYPE2FILE=%TRIM(%SYSFUNC(TranWrd(%LOWCASE(&TYPE2FILE.), .sas7bdat, %STR())));
%LET TYPE3FILE=%TRIM(%SYSFUNC(TranWrd(%LOWCASE(&TYPE3FILE.), .sas7bdat, %STR())));
%LET TYPE4FILE=%TRIM(%SYSFUNC(TranWrd(%LOWCASE(&TYPE4FILE.), .sas7bdat, %STR())));
%LET COHORTCODES=%TRIM(%SYSFUNC(TranWrd(%LOWCASE(&COHORTCODES.), .sas7bdat, %STR())));
%LET INCLUSIONCODES=%TRIM(%SYSFUNC(TranWrd(%LOWCASE(&INCLUSIONCODES.), .sas7bdat, %STR())));
%LET COVARIATECODES=%TRIM(%SYSFUNC(TranWrd(%LOWCASE(&COVARIATECODES.), .sas7bdat, %STR())));
%LET STOCKPILINGFILE=%TRIM(%SYSFUNC(TranWrd(%LOWCASE(&STOCKPILINGFILE.), .sas7bdat, %STR())));
%LET UTILFILE=%TRIM(%SYSFUNC(TranWrd(%LOWCASE(&UTILFILE.), .sas7bdat, %STR())));
%LET COMORBFILE=%TRIM(%SYSFUNC(TranWrd(%LOWCASE(&COMORBFILE.), .sas7bdat, %STR())));
%LET DRUGCLASSFILE=%TRIM(%SYSFUNC(TranWrd(%LOWCASE(&DRUGCLASSFILE.), .sas7bdat, %STR())));
%LET COMBOFILE=%TRIM(%SYSFUNC(TranWrd(%LOWCASE(&COMBOFILE.), .sas7bdat, %STR())));
%LET FREEZEDATA=%TRIM(%SYSFUNC(TranWrd(%LOWCASE(&FREEZEDATA.), .sas7bdat, %STR())));
%LET PREGCODES=%TRIM(%SYSFUNC(TranWrd(%LOWCASE(&PREGCODES.), .sas7bdat, %STR())));
%LET MOICODES=%TRIM(%SYSFUNC(TranWrd(%LOWCASE(&MOICODES.), .sas7bdat, %STR())));
%LET ZIPFILE=%TRIM(%SYSFUNC(TranWrd(%LOWCASE(&ZIPFILE.), .sas7bdat, %STR())));
/*---------------------------------------------------*/
/* 02 -- Import input files in SAS format */
/*---------------------------------------------------*/
%IMPORTFILES(var=&MONITORINGFILE.);
%IMPORTFILES(var=&COHORTFILE.);
%IMPORTFILES(var=&TYPE1FILE.);
%IMPORTFILES(var=&TYPE2FILE.);
%IMPORTFILES(var=&TYPE3FILE.);
%IMPORTFILES(var=&TYPE4FILE.);
%IMPORTFILES(var=&COHORTCODES.);
%IMPORTFILES(var=&INCLUSIONCODES.);
%IMPORTFILES(var=&COVARIATECODES.);
%IMPORTFILES(var=&STOCKPILINGFILE.);
%IMPORTFILES(var=&UTILFILE.);
%IMPORTFILES(var=&COMORBFILE.);
%IMPORTFILES(var=&DRUGCLASSFILE.);
%IMPORTFILES(var=&COMBOFILE.);
%IMPORTFILES(var=&DRUGCLASSFILE.);
%IMPORTFILES(var=&LABSCODEMAP.);
%IMPORTFILES(var=&PTSTOEXCLUDE.);
%IMPORTFILES(var=&PREGCODES.);
%IMPORTFILES(var=&MOICODES.);
%IMPORTFILES(var=&ZIPFILE.);
/*---------------------------------------------------*/
/* 03 -- Process input file parameters */
/*---------------------------------------------------*/
%MS_ProcessInputFiles();
/*---------------------------------------------------*/
/* 04 -- Process Ranges "-" and Wilcards "*" */
/* in lookup codes */
/*---------------------------------------------------*/
%ISDATA(dataset=_diag);
%IF %EVAL(&NOBS.>=1) %THEN %DO;
%MS_ProcessWildcards(InFile=_diag,
CodeVar=code,
OutFile=_diag);
%END;
/*---------------------------------------------------*/
/* 05 -- Extract Raw Data and run envellope or */
/* Create combo records (if applicable) */
/*---------------------------------------------------*/
%if %str(&COMBOFILE.) = %str() or %length(&COMBOFILE.) = 0. %then %do;
/*Programming note: We are not using caresettingprincipal in the pre-extraction phase
because typical sensitivity analysis around caresettingprincipal
could lead to massive claim duplication;*/
%ms_extractmeds(datafile=indata.&proctable.,
datacode=PX ,
datacodetype=PX_codetype ,
lookfile=_proc,
outfile=_procextract(drop=EncounterID Provider));
proc sort data=_procextract nodupkey;
by _all_;
run;
*Delete data from patients in DPs &PTSTOEXCLUDE. files;
%ms_delpatients(datafile=_procextract,
ptsfile=&PTSTOEXCLUDE.,
Outfile=_procextract);
%ms_extractmeds(datafile=indata.&diatable.,
datacode=DX,
datacodetype=DX_codetype,
lookfile=_diag,
outfile=_diagextract(drop=EncounterID Provider));
proc sort data=_diagextract nodupkey;
by _all_;
run;
*Delete data from patients in DPs &PTSTOEXCLUDE. files;
%ms_delpatients(datafile=_diagextract,
ptsfile=&PTSTOEXCLUDE.,
Outfile=_diagextract);
*Codes must be unique to avoid claim duplication.;
proc sort nodupkey data=_ndc;
by code;
run;
%ms_extractdrugs(datafile=indata.&distable.,
datavar=NDC ,
lookfile=_ndc(drop=codecat CodeType),
lookvar=code ,
outfile=Drugs);
proc sort data=Drugs nodupkey;
by _all_;
run;
*Delete data from patients in DPs &PTSTOEXCLUDE. files;
%ms_delpatients(datafile=Drugs,
ptsfile=&PTSTOEXCLUDE.,
Outfile=Drugs);
%ms_extractlabs(datafile=indata.&labtable.,
lookfile=_lab,
outfile=labextract);
proc sort data=labextract nodupkey;
by _all_;
run;
*Delete data for patients in DPs &PTSTOEXCLUDE. files;
%ms_delpatients(datafile=labextract,
ptsfile=&PTSTOEXCLUDE.,
Outfile=labextract);
%end;
%else %do;
*Combo is called using a macro wrapper to preserve the scoping of
macro call combo variables (specifically STOCKPILINGFILE);
%MACRO RUNCOMBO;
%global COMBORUN;
%let COMBORUN=1;
%if %EVAL(&RUN_ENVELOPE. = 2) %then %do;
%let DXENVEL = 0;
%let PXENVEL = 0;
%end;
%else %do;
%let DXENVEL = 1;
%let PXENVEL = 1;
%end;
%combo(COMBFILE=&COMBOFILE.,
CODESFILE=&COMBOFILE.Codes,
DXENVEL=1&DXENVEL.,
PXENVEL=1&PXENVEL.,
OUTNAME=&RUNID._Combo,
STOCKPILING=1,
STOCKPILINGFILE=&COMBOFILE.Stock,
ENROLGAP=0,
LABSCODEMAP=&LABSCODEMAP.,
SAVETODPLOCAL=N,
PTSTOEXCLUDE=&PTSTOEXCLUDE.);
*Combo creates _Drugs, but we need Drugs to continue;
proc datasets library=work nowarn nolist;
change _drugs=drugs;
quit;
%MEND RUNCOMBO;
%RUNCOMBO;
%end;
* Deaths are not processed by combo tool. Therefore they must be extracted no matter if combo is run;
%ms_extractdeaths(deathfile=indata.&DEATHTABLE.,
encounterfile=indata.&enctable.,
outfile=deathextract);
* Delete data for patients in DPs &PTSTOEXCLUDE. files;
%ms_delpatients(datafile=deathextract,
ptsfile=&PTSTOEXCLUDE.,
Outfile=deathextract);
*Combine _diags and _procs for envelope later;
proc sql noprint;
create table meds as
select proc.PatId,
proc.Adate,
compress(proc.PX,' .')as Code,
" " as Pdx,
proc.EncType,
PX_Codetype as Codetype,
"PX" as CodeCat,
1 as RxSup,
1 as RxAmt
from _procextract as proc
union corr all
select diag.PatId,
diag.Adate,
compress(diag.DX,' .') as Code,
diag.EncType,
diag.Pdx,
diag.DX_Codetype as Codetype,
"DX" as CodeCat,
1 as RxSup,
1 as RxAmt
from _diagextract as diag;
quit;
*To avoid w a r n i n g s down the line;
data meds;
set meds;
length Adate 4.;
run;
*If meds records not out of combo, run envellope;
%ISDATA(dataset=meds);
%if (%str(&COMBOFILE.) = %str() or %length(&COMBOFILE.) = 0.) & %EVAL(&NOBS.>=1) & %EVAL(&RUN_ENVELOPE. ne 2) %then %do;
%MS_ENVELOPE(INFILE=meds, /*Name of SAS dataset with diagnosis and/or procedure claims data*/
ENCOUNTERINFILE=indata.&ENCTABLE., /*Name of SAS dataset with encounter data*/
OUTFILE=meds); /*Name of output file with reclassified claims*/
%end;
*Separate raw un-stockpiled RX claims from
combo claims set to behave like Rx;
data Drugs
DrugsComb;
set Drugs;
if RxSup>0 and RxAMt>0;
if comb then output DrugsComb;
else output Drugs;
run;
*FOR DEBUG OPTION;
%let GroupNum=0;
%CreateCopyDir(tmp_libref=&RUNID.&GroupNum.);
%CopyAllFiles(prefix=_,suffix=,tmp_libref=&RUNID.&GroupNum.);
proc datasets library=work nowarn nolist;
delete _:;
quit;
/*---------------------------------------------------*/
/* 06 -- For each Comp and for each Group */
/*---------------------------------------------------*/
*Adjust type macro variable scope outside of forloop;
%let Type=;
%let Type3=0;
%let Type4=0;
%global MinExpPeriodStartDt MaxExpPeriodEndDt censor_output;
%let censor_output =.;
%let MinExpPeriodStartDt=.;
%let MaxExpPeriodEndDt=.;
%macro forloop;
%do Group=1 %to &Ngroup.;
*FOR DEBUG OPTION;
%let GroupNum=%eval(&Group.);
%CreateCopyDir(tmp_libref=&RUNID.&GroupNum.);
/*---------------------------------------------------*/
/* 06.a -- Loop pre-processing */
/*---------------------------------------------------*/
*Get current Group Name and load macro parameters;
*Check if Type3 and EnrDaysAfter variables are in the COHORTFILE input file;
data _null_;
dset=open("infolder.&COHORTFILE.");
call symput ('Type3',put(varnum(dset,'type3'),best.));
call symput ('EnrDaysAfter',put(varnum(dset,'EnrDaysAfter'),best.));
call symput ('Type4',put(varnum(dset,'type4'),best.));
/*Check if Geog exists*/
call symput ('Geogvar',put(varnum(dset,'geog'),best.));
run;
%put &Type3. &EnrDaysAfter. &Type4.;
%global GEOG;
data _null_;
set infolder.&COHORTFILE.;
if _n_ = &Group. then do;
call symputx('ITGROUP',strip(Group));
*Type of analysis;
if upcase(type1)=:"Y" then call symputx('Type',1);
if upcase(type2)=:"Y" then call symputx('Type',2);
%if %eval(&Type3. >= 1) %then %do;
if upcase(type3)=:"Y" then do;
call symputx('Type',3);
call symput("T3VARS","T3_INDEX T3_FUP");
end;
else call symput("T3VARS","");
%end;
%else %do;
call symput("T3VARS","");
%end;
%if %eval(&Type4. >= 1) %then %do;
if upcase(type4)=:"Y" then do;
call symputx('Type',4);
call symput("T4VARS","T4_INDEX T4_FUP CATEGORY1 CATEGORY2 CATEGORY3 CATEGORY4");
if missing(Sex) then do;
call symputx("DemogSex","'F' 'M' 'U' 'A'");
end;
else do;
call symputx('DemogSex',upcase(Sex));
end;
if missing(Race) then do;
call symputx("DemogRace","'0' '1' '2' '3' '4' '5'");
end;
else do;
call symputx("DemogRace",upcase(Race));
end;
if missing(Hispanic) then do;
call symputx("DemogHispanic","'Y' 'N' 'U'");
end;
else do;
call symputx("DemogHispanic",upcase(Hispanic));
end;
*Minimum enrollment days of exposure required;
call symputx("EnrDaysFloor",put(EnrDaysFloor,best.));
end;
else call symput("T4VARS","");
%end;
%else %do;
call symput("T4VARS","");
%end;
/*Assign Geographic location parameter*/
%if %eval(&Geogvar. >= 1) %then %do;
if upcase(type1)=:"Y" or upcase(type2)=:"Y" then do;
call symputx("GEOG",strip(upcase(Geog)));
end;
else do;
call symput("GEOG","");
end;
%end;
%else %do;
call symput("GEOG","");
%end;
*Enrollment paramters;
call symputx("COVERAGE",strip(upcase(coverage)));
*Minimum Enrollment days before exposure;
call symputx("enrdays",put(enrdays,best.));
*Minimum Enrollment days after exposure;
if &EnrDaysAfter.=0 then EnrDaysAfter =0;
call symputx("EnrDaysAft",put(EnrDaysAfter,best.));
*Enrollment gap;
if enrolgap ne . then call symputx("ENROLGAP",enrolgap);
else call symputx("ENROLGAP","0"); /*Use enrollment file as-is*/
*Compound name for suffix of elig file;
if missing(CHARTRES) then CHARTRES='N';
call symputx("CHARTRES",CHARTRES);
call symputx("RECONTYP",strip(upcase(coverage))||compress(CHARTRES,"_"));
end;
run;
%PUT &ITGROUP. &Type. &COVERAGE. &CHARTRES. &ENROLGAP. &RECONTYP. &enrdays. &enrdaysaft. &T3VARS. &T4VARS. &GEOG.;
%PUT INFO: NOW LOOPING ON &ITGROUP.;
%global L1HDPSFROM;
%global L1HDPSTO;
%let L1HDPSFROM=0;
%let L1HDPSTO=0;
%ISDATA(dataset=HDPS);
%IF %EVAL(&NOBS.>0) %THEN %DO;
data _null_;
set HDPS;
where group="&ITGROUP";
if .<HDPSWinFROM <0 then call symputx("L1HDPSFROM",put(abs(HDPSWinFROM),best.));
if 0<HDPSWinTO then call symputx("L1HDPSTO",put(HDPSWinTO,best.));
run;
%put &L1HDPSFROM. &L1HDPSTO.;
%END;
%if %eval(&Type. = 2) %then %do;
*Get Type2 cohort definition;
data _CohortDef;
set infolder.&TYPE2FILE.;
where group="&ITGROUP.";
if ittdays>0 then do;
call symputx("ITT",1);
*defensive coding;
Episodegap=.;
expextper=.;
minepisdur=.;
maxepisdur=.;
end;
else call symputx("ITT",0);
call symputx("eventcount",eventcount);
if upcase(censor_dth) eq "Y" then call symputx("censor_dth",1);
else call symputx("censor_dth",0);
if upcase(censor_dpend) eq "Y" then call symputx("censor_dpend",1);
else call symputx("censor_dpend",0);
if upcase(censor_output) eq "Y" then call symputx("censor_output",1);
else call symputx("censor_output",0);
if upcase(censor_qryend) eq "Y" then call symputx("censor_qryend",1);
else call symputx("censor_qryend",0);
call symputx("PRIORDAYS_bf",.);
call symputx("PRIORDAYS_af",.);
*needed by ms_cidadenom;
call symputx("COHORTDEF",T2CohortDef);
call symputx("WASHPER",T2WashPer);
call symputx("FUPWASHPER",T2FupWashPer);
call symputx("ITTDAYS",ittdays);
call symputx("BLACKOUTPER",BlackoutPer);
call symputx("MINEPISDUR",MinEpisDur);
call symputx("MAXEPISDUR",MaxEpisDur);
call symputx("MINDAYSUPP",MinDaySupp);
rename T2WashPer=WashPer;
drop group;
run;
%put &eventcount. &ITT.;
*Get max covariatewindow from covariatecodes - even if looped at group level it will be the same since Covariate window same for both groups;
*Note: before absolute value in type2file, now its negative;
%let MinCovFrom=.;
%let MaxCovTo=.;
*Since input file is optional, make sure it exists;
%IF %SYSFUNC(exist(infolder.&COVARIATECODES.))=1 %THEN %DO;
*Select covariates codes (not combination of covariates);
data _&COVARIATECODES.;
set infolder.&COVARIATECODES.;
where codecat not in ("CC");
run;
proc sql;
select max(-1*CovFrom) into: MinCovFrom
from _&COVARIATECODES.(where=(. < CovFrom < 0));
quit;
proc sql;
select max(CovTo) into: MaxCovTo
from _&COVARIATECODES.;
quit;
*Add w.a.r.n.i.n.g. if there is a covariatecodes inputfile, but not PS or ADS;
%if (%UPCASE("&ANALYSIS.") ne ("PS")) & (%UPCASE("&ANALYSIS.") ne ("ADS")) %then %do ;
%put WARNING: Table 1 not specified but &COVARIATECODES. exist MinCovFrom MaxCovTo will be set to missing;
%let MinCovFrom=.;
%let MaxCovTo=.;
%END;
%END;
%put &MinCovFrom. &MaxCovTo.;
%ISDATA(dataset=infolder.&comorbfile.);
%IF %EVAL(&NOBS.>0) %THEN %DO;
data _Null_;
set infolder.&comorbfile.;
where group="&ITGROUP.";
if ComorbFROM <0 then call symputx("PRIORDAYS_bf",/*PRIORDAYS*/ComorbFROM);
if ComorbTo>=0 then call symputx("PRIORDAYS_af",/*INCLINDEX*/ComorbTo);
run;
%put &PRIORDAYS_bf. &PRIORDAYS_af.;
%END;
%end;
%if %eval(&Type. = 1 ) %then %do;
data _CohortDef;
set infolder.&TYPE1FILE.;
where group="&ITGROUP.";
call symputx("ITT",0);
call symputx("eventcount",.);
call symputx("Censor_dth",0);
call symputx("covariatewindow",.);
call symputx("PRIORDAYS_bf",.);
call symputx("PRIORDAYS_af",.);
*needed by ms_cidadenom;
call symputx("COHORTDEF",T1CohortDef);
call symputx("WASHPER",T1WashPer);
call symputx("FUPWASHPER",.);
call symputx("ITTDAYS",.);
call symputx("BLACKOUTPER",.);
call symputx("MINEPISDUR",.);
call symputx("MAXEPISDUR",.);
call symputx("MINDAYSUPP",.);
call symputx("censor_output",.);
call symputx("censor_qryend",.);
call symputx("censor_dpend",.);
rename T1WashPer=WashPer;
run;
*Since PtsMasterlist step for type 1 or 2, need to set missing values;
%let MinCovFrom=.;
%let MaxCovTo=.;
%end;
%if %eval(&Type. = 3 ) %then %do;
%global ExpPeriodStartDt ExpPeriodEndDt;
data _CohortDef;
set infolder.&TYPE3FILE.;
where group="&ITGROUP.";
call symputx("ITT",0);
call symputx("eventcount",.);
if upcase(T3censor_dth) eq "Y" then call symputx("Censor_dth",1);
else call symputx("Censor_dth",0);
call symputx("covariatewindow",.);
call symputx("PRIORDAYS_bf",.);
call symputx("PRIORDAYS_af",.);
*type3 specific;
call symputx("T3CtrlFrom",T3CtrlFrom);
call symputx("T3CtrlTo",T3CtrlTo);
call symputx("T3RiskFrom",T3RiskFrom);
call symputx("T3RiskTo",T3RiskTo);
call symputx("T3ExclOnSameDay",T3ExclOnSameDay);
call symputx("T3SURVSTARTDATE",T3SURVSTARTDATE);
if T3ENDOFUPDATE=. then T3ENDOFUPDATE=99999;
call symputx("T3ENDOFUPDATE",T3ENDOFUPDATE);
call symputx("ExpPeriodStartDt",T3SURVSTARTDATE);*Default when &PERIODIDSTART. = 1;
rename T3WashPer=WashPer;
run;
%if &PERIODIDSTART. > 1 %then %do;
data _null_;
set infolder.&T3METADATA.;
where group="&ITGROUP.";
call symputx("ExpPeriodStartDt",INTNX('Month', ExpPeriodEndDt+1, 0, 'begin'));
run;
%put ExpPeriodStartDt: &ExpPeriodStartDt.;
%end;
data _null_;
call symputx("MinExpPeriodStartDt",Min(&MinExpPeriodStartDt., &ExpPeriodStartDt.));
run;
*Check if risk and control windows overlap;
%if (&T3CtrlFrom. <= &T3RiskTo. & &T3CtrlTo. >= &T3RiskFrom.) |
(&T3RiskFrom. <= &T3CtrlTo. & &T3RiskTo. >= &T3CtrlFrom.) %then %do;
%put WARNING: The control window overlaps with the risk window.;
%end;
%end;
%if %eval(&Type. = 4) %then %do;
data _CohortDef;
set infolder.&TYPE4FILE.;
where group="&ITGROUP.";
expextper=0; * needed for POV4 creation to avoid w.a.r.n.i.n.g.s.;
expextper2=0;
*Primary Cohort parameters;
* Forced ITT to 0 for primary cohort;
call symputx("ITT",0);
call symputx("Censor_dth",0);
/*call symputx("eventcount",.);
call symputx("covariatewindow",.);
call symputx("PRIORDAYS_bf",.);
call symputx("PRIORDAYS_af",.);
*needed by ms_cidadenom;*/
call symputx("COHORTDEF",T4CohortDef);
call symputx("WASHPER",T4WashPer);
call symputx("FUPWASHPER",T4FupWashper);
call symputx("ITTDAYS",ittdays);
/*call symputx("BLACKOUTPER",.);
call symputx("MINEPISDUR",.);
call symputx("MAXEPISDUR",.);
call symputx("MINDAYSUPP",.);*/
rename T4WashPer=WashPer;
*Secondary Cohort parameters;
if ittdays2>0 then do;
call symputx("ITT2",1);
end;
else call symputx("ITT2",0);
/*call symputx("Censor2",0);
call symputx("eventcount2",.);
call symputx("covariatewindow2",.);
call symputx("PRIORDAYS_bf2",.);
call symputx("PRIORDAYS_af2",.);
*needed by ms_cidadenom;
call symputx("COHORTDEF2",T4CohortDef2);*/
call symputx("WASHPER2",T4WashPer2);
call symputx("ITTDAYS2",ittdays2);
/*call symputx("FUPWASHPER2",T4FupWashper2);
call symputx("BLACKOUTPER2",.);
call symputx("MINEPISDUR2",.);
call symputx("MINDAYSUPP2",.);*/
rename T4WashPer2=WashPer2;
run;
*Verify that enrdays is sufficient to cover at least the whole episode duration;
%macro wrapper;
%if %eval(&EnrDays. < &EnrDaysFloor.) %then %do;
%put WARNING: The value of EnrDays is not high enough to ensure enrollment during episodes.A value of at least 294 days is required.;
%end;
%mend; %wrapper;
%end;
*Get specific codes for this loop
and separate diags from others to process wildcards;
data _diag
_others;
set &COHORTCODES.;
where group="&ITGROUP.";
if CodeCat in("DX") then output _diag ;
else output _others;
run;
%ISDATA(dataset=_diag);
%IF %EVAL(&NOBS.>=1) %THEN %DO;
%MS_ProcessWildcards(InFile=_diag,
CodeVar=code,
OutFile=_diag);
%END;
*Create Loop-specific parameter files;
data _ITGroupParamNDC
_ITGroupParamMeds
_ITGroupParamLabs(rename=Group=RawGroup rename=CareSettingprincipal=RawCaresetting);
set _diag
_others;
if Condinclusion=. then Condinclusion=1;
if CodeCat="RX" then output _ITGroupParamNDC;
if CodeCat in("DX","PX") then output _ITGroupParamMeds;
if CodeCat in("LB") then output _ITGroupParamLabs;
run;
*Break out CaresettingPrincipal into Caresetting and Principal;
%ms_caresettingprincipal(InFile=_ITGroupParamMeds,
Var=CareSettingPrincipal,
OutFile=_ITGroupParamMeds);
/*---------------------------------------------------*/
/* 06.b -- Extract loop-specific records */
/*---------------------------------------------------*/
*Extract Records necessary for creating this group only
(record duplic OK + add loop parameters back to recrods);
%ms_loopmeds(datafile=meds,
lookfile=_ITGroupParamMeds,
outfile=_ITMeds);
%ms_extractdrugs(datafile=Drugs,
datavar=NDC ,
lookfile=_ITGroupParamNDC,
lookvar=code ,
outfile=_ITDrugs);
*Need to define Supply End Date to make sure days of supply are constrained within enrollment;
data _ITDrugs;
set _ITDrugs;
format RxSupEndDt date9.;
RxSupEndDt=Rxdate+RxSup-1;
run;
%ms_extractdrugs(datafile=DrugsComb,
datavar=NDC ,
lookfile=_ITGroupParamNDC,
lookvar=code ,
outfile=_ITDrugsComb);
*Need to define Supply End Date to make sure days of supply are constrained within enrollment;
data _ITDrugsComb;
set _ITDrugsComb;
format RxSupEndDt date9.;
RxSupEndDt=Rxdate+RxSup-1;
run;
%ms_extractlabs(datafile=labextract,
lookfile=_ITGroupParamLabs,
outfile=_ITlabs);
/*---------------------------------------------------*/
/* 06.c -- Create enrollment dataset for required */
/* coverage/charts (if not already created) */
/*---------------------------------------------------*/
%IF %SYSFUNC(exist(ENR_&RECONTYP.&ENROLGAP.))=0 %THEN %DO;
%MS_EPISODEREC2(INFILE=indata.&ENRTABLE., /*SAS dataset containing the raw enrollment data*/
OUTFILE=ENR_&RECONTYP.&ENROLGAP., /*SAS output dataset containing the conciliated enrollment periods*/
COVERAGE=&COVERAGE., /*Drug/Medical coverage indicator*/
CHARTRES=&CHARTRES., /*Chart availability indicator*/
ENRSTART=Enr_Start, /*Name of enrollment episode start date variable in INFILE*/
ENREND=Enr_End, /*Name of enrollment episode end date variable in INFILE*/
ENROLGAP=&ENROLGAP., /*Maximum allowable enrollment episode gap*/
REMOVEDUP=Yes); /*Indicates the removal of duplicates from the output file*/
*Restrict to desired population according to demographic criteria in COHORTFILE;
%IF %EVAL(&TYPE.=4 | 2) %THEN %DO;
data _null_;
call symputx("DemogSex","'F'");
call symputx("DemogRace","'0' '1' '2' '3' '4' '5'");
call symputx("DemogHispanic","'Y' 'N' 'U'");
run;
proc sql noprint undo_policy=none;
create table ENR_&RECONTYP.&ENROLGAP. as
select Enr.*,
Dem.BIRTH_DATE
from ENR_&RECONTYP.&ENROLGAP. as Enr,
indata.&DEMTABLE. as Dem
where Enr.patid=Dem.patid and upcase(Dem.sex) in (&DemogSex.) and
upcase(Dem.Race) in (&DemogRace.) and upcase(Dem.Hispanic) in (&DemogHispanic.)
order by PatID, Enr_Start, Enr_End;
quit;
%END;
* Delete data for patients in DPs &PTSTOEXCLUDE. files;
%ms_delpatients(datafile=ENR_&RECONTYP.&ENROLGAP.,
ptsfile=&PTSTOEXCLUDE.,
Outfile=ENR_&RECONTYP.&ENROLGAP.);
%END;
*If censoring is used, truncate eligibility at death date;
data ENR_&GROUP.;
merge ENR_&RECONTYP.&ENROLGAP.(in=a)
DeathExtract(in=b); *Already sorted by PatID;
by PatId;
if a;
if &censor_dth.=1 then do;
if b and DeathDt <= Enr_End then do;
%if %eval(&Type. = 3) %then %do;
Enr_End=DeathDt; /*Adjust for Type 3, keep for Type 2 and adjust later*/
%end;
EnrEpisodeCensoredAtDeath=1;
end;
if Enr_end < Enr_Start then delete;
end;
%if %eval(&Type. = 3) %then %do;
if &T3ENDOFUPDATE. < Enr_End then Enr_End=&T3ENDOFUPDATE.;
if Enr_end < Enr_Start then delete;
%end;
run;
*Make sure all claims overlap enrollment span and only
keep eligible exposures (overlaping Enr_Start Enr_End);
*FOR DEBUG OPTION;
%CopyFile(FileName=_ITDrugs,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
%ms_shaveoutside(reffile=ENR_&GROUP.,
refstart=Enr_Start,
refend=Enr_End,
KeepPartBf=N,
ToShaveFile=_ITDrugs,
ToShaveStart=RxDate,
ToShaveEnd=RxSupEndDt,
outfile=_ITDrugs);
*FOR DEBUG OPTION;
%CopyFile(FileName=_ITDrugsComb,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
%ISDATA(dataset=_ITDrugsComb);
%IF %EVAL(&NOBS.>=1) %THEN %DO;
%ms_shaveoutside(reffile=ENR_&GROUP.,
refstart=Enr_Start,
refend=Enr_End,
KeepPartBf=N,
ToShaveFile=_ITDrugsComb,
ToShaveStart=RxDate,
ToShaveEnd=RxSupEndDt,
outfile=_ITDrugsComb(drop=Enr_Start Enr_end));
%END;
*FOR DEBUG OPTION;
%CopyFile(FileName=_ITMeds,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
%ms_shaveoutside(reffile=ENR_&GROUP.,
refstart=Enr_Start,
refend=Enr_End,
KeepPartBf=N,
ToShaveFile=_ITMeds,
ToShaveStart=ADate,
ToShaveEnd=ADate,
outfile=_ITMeds);
*FOR DEBUG OPTION;
%CopyFile(FileName=_ITLabs,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
%ms_shaveoutside(reffile=ENR_&GROUP.,
refstart=Enr_Start,
refend=Enr_End,
KeepPartBf=N,
ToShaveFile=_ITLabs,
ToShaveStart=ADate,
ToShaveEnd=ADate,
outfile=_ITLabs);
%IF %EVAL(&TYPE.=3) %THEN %DO;
%if %str(&T3ExclOnSameDay.)=%str(Y) %then %do;
*for type3 use - identifying SameDayDispensings for different NDCs but from the same group;
proc sort nodupkey data=_ITDrugs out=_SameDayNDCs(keep=Patid rxdate ndc);
by Patid rxdate ndc;
WHERE T3_INDEX="DEF";
run;
proc means data=_SameDayNDCs nway noprint;
class Patid rxdate;
output out=_SameDayNDCs(where=(_freq_>1));
run;
%end;
%END;
/*---------------------------------------------------*/
/* 06.d -- Apply StockPiling */
/*---------------------------------------------------*/
*Default stockpiling parameters;
%let SAMEDAY=aa;
%let SUPRANGE=0<-HIGH;
%let AMTRANGE=0<-HIGH;
%let PERCENTDAYS=;
%ISDATA(dataset=infolder.&STOCKPILINGFILE.);
%IF %EVAL(&NOBS.>0) %THEN %DO;
data &STOCKPILINGFILE.;
set infolder.&STOCKPILINGFILE.;
where group="&ITGROUP.";
call symputx("SAMEDAY",strip(SameDay));
call symputx("SUPRANGE",strip(SupRange));
call symputx("AMTRANGE",strip(AmtRange));
call symputx("PERCENTDAYS",put(PercentDays,best.));
run;
%end;
/*DO NOT RUN MS_STOCKPILING to attempt matching module 1*/
%let InclExclVars=;
%IF %SYSFUNC(exist(infolder.&INCLUSIONCODES.))=1 %THEN %DO;
%let InclExclVars=CondInclusion CondFrom CondTo Cond Subcond SubCondInclusion;
%END;
%put &InclExclVars.;
*FOR DEBUG OPTION;
%CopyFile(FileName=_ITDrugs,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
%MS_STOCKPILING(INFILE=_ITDrugs,
CLMDATE=RxDate,
CLMSUP=RxSup,
CLMAMT=RxAmt,
PROCFLAG=,
PERCENTDAYS=&PERCENTDAYS.,
GROUPING=Group Subgroup T1_INDEX T2_INDEX T2_FUP &T3VARS. &T4VARS. &InclExclVars.,
SAMEDAY=&SAMEDAY.,
SUPRANGE=&SUPRANGE.,
AMTRANGE=&AMTRANGE.,
ID=CODECAT,
OUTFILE=_ITDrugs,
OUTFILEEXCL=_ITDrugsExcl
);
*FOR DEBUG OPTION;
%CopyFile(FileName=_ITDrugs,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
data _ITDrugs;
set _ITDrugs
_ITDrugsComb(in=a);
*ExpireDt does not exist in _ITDrugsComb;
if a then ExpireDt=Rxdate+RxSup-1;
run;
*Need to reshave because Stockpiling may have pushed claims outside enrollment period;
%ISDATA(dataset=_ITDrugs);
%IF %EVAL(&NOBS.>=1) %THEN %DO;
*FOR DEBUG OPTION;
%CopyFile(FileName=_ITDrugs,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
%ms_shaveoutside(reffile=ENR_&GROUP.,
refstart=Enr_Start,
refend=Enr_End,
KeepPartBf=N,
ToShaveFile=_ITDrugs,
ToShaveStart=RxDate,
ToShaveEnd=ExpireDt,
outfile=_ITDrugs);
%END;
*FOR DEBUG OPTION;
%CopyFile(FileName=_ITGroupParamNDC,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
%CopyFile(FileName=_ITGroupParamMeds,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
%CopyFile(FileName=_ITGroupParamLabs,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
%CopyFile(FileName=_diag,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
%CopyFile(FileName=_others,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
proc datasets library=work nowarn nolist;
delete _ITGroupParam: _diag _others;
quit;
* Process same day diag, proc and labs for type 1 analysis;
%if %eval(&Type. eq 1) %then %do;
%ISDATA(dataset=_ITMeds);
%IF %EVAL(&NOBS.>=1) %THEN %DO;
proc means data=_ITMeds nway noprint;
class PatId ADate T1_Index;
output out=_ITMedsFreq(rename=_freq_=NumDispensing drop=_Type_) N=;
run;
proc sort data=_ITMeds;
by PatId ADate T1_Index;
run;
data _ITMeds;
merge _ITMeds(drop=RxSup RxAmt)
_ITMedsFreq(keep=PatId ADate T1_Index NumDispensing);
by PatId ADate T1_Index;
RxSup=1;
RxAmt=1;
Expiredt=Adate;
run;
proc sort nodupkey data=_ITMeds;
by PatId ADate T1_Index;
run;
%END;
%ISDATA(dataset=_ITLabs);
%IF %EVAL(&NOBS.>=1) %THEN %DO;
proc means data=_ITLabs nway noprint;
class PatId ADate T1_Index;
output out=_ITLabsFreq(rename=_freq_=NumDispensing drop=_Type_) N=;
run;
proc sort data=_ITLabs;
by PatId ADate T1_Index;
run;
data _ITLabs;
merge _ITLabs(drop=RxSup RxAmt)
_ITLabsFreq(keep=PatId ADate T1_Index NumDispensing);
by PatId ADate T1_Index;
RxSup=1;
RxAmt=1;
Expiredt=Adate;
run;
proc sort nodupkey data=_ITLabs;
by PatId ADate T1_Index;
run;
%END;
%end;
/*---------------------------------------------------*/
/* 06.e -- Create raw POVs datasets */
/*---------------------------------------------------*/
data _GroupIndex(keep=PatId Adate ExpireDt RXsup RXAmt NumDispensing Enr_Start Enr_end) /* POV1: Primary cohort Group Index Date (GroupIndex) */
%if %eval(&Type. eq 4) %then %do;
_GroupIndex2(keep=PatId Adate ExpireDt RXsup RXAmt Enr_Start Enr_end CodeSupply Category:) /* POV1: Secondary cohort Group Index Date (GroupIndex2) */
_GroupWash2(keep=PatId Adate ExpireDt Enr_Start) /* POV2: Secondary Cohort Group Washout (GroupWash2) */
_GroupPreg(keep=PatId Adate ExpireDt Enr_Start CodeCat Codetype Code PreTerm PostTerm Priority Duration)
%end;
_GroupWash(keep=PatId Adate ExpireDt Enr_Start T2_INDEX) /* POV2: Primary Cohort Group Washout (GroupWash) */
_GroupWashForTrunk(keep=PatId Adate Enr_Start T2_INDEX)
_InclExcl /* POV3: Inclusion/Exclusion (InclExcl) */
_FUPEvent(keep=PatId Adate ExpireDt Enr_Start codecat codetype code) /* POV5: Risk Follow-Up Event (FUPEvent) */
_FUPWash(keep=PatId Adate ExpireDt Enr_Start); /* POV6: Risk Follow-Up Washout (FUPWash) */
set _ITDrugs(in=a rename=RxDate=ADate)
_ITMeds(in=b)
_ITLabs(in=c);
%if %eval(&Type. ne 1) %then %do;
if b or c then do;
Expiredt=Adate;
RXsup=1;
RXAmt=1;
end;
%end;
if &type.=1 then do;
/*POV1*/ if T1_INDEX in('DEF') then output _GroupIndex;
/*POV2*/ if T1_INDEX in('IOC') then output _GroupWash;
/*POV3*/ if T1_INDEX in('INC','EXC') then output _InclExcl;
end;
if &type.=2 then do;
/*POV1*/ if T2_INDEX in('DEF') then output _GroupIndex;
/*POV2*/ if T2_INDEX in('IOT','IOD') then output _GroupWash; *for the IO* part;
if T2_INDEX in('IOT','FUT') then output _GroupWashForTrunk; *for the **T part;
/*POV3*/ if T2_INDEX in('INC','EXC') then output _InclExcl;
/*POV5*/ if T2_FUP in('DEF') then output _FUPEvent;
/*POV6*/ if T2_FUP in('IOC') then output _FUPWash;
end;
if &type.=3 then do;
/*POV1*/ if T3_INDEX in('DEF') then output _GroupIndex;
/*POV2*/ if T3_INDEX in('IOC') then output _GroupWash; *for the IO* part;
/*POV3*/ if T3_INDEX in('INC','EXC') then output _InclExcl;
/*POV5*/ if T3_FUP in('DEF') then output _FUPEvent;
/*POV6*/ if T3_FUP in('IOC') then output _FUPWash;
end;
%if %eval(&Type. eq 4) %then %do;
if not a and not missing(CodeSupply) then do;
RxSup=CodeSupply;
Expiredt=ADate+RxSup-1;
end;
/*POV1*/ if T4_INDEX in('DEF') then output _GroupIndex;
/*POV1*/ if T4_INDEX in('DEF2') then output _GroupIndex2;
/*POV2*/ if T4_INDEX in('IOC') then output _GroupWash; *for the IO* part;
/*POV2*/ if T4_INDEX in('IOC2') then output _GroupWash2; *for the IO* part;
/*POV3*/ if T4_INDEX in('INC','EXC') then output _InclExcl;
/*Preg*/ if T4_INDEX in('PCY') then output _GroupPreg;
/*POV5*/ if T4_FUP in('DEF') then output _FUPEvent;
/*POV6*/ if T4_FUP in('IOC') then output _FUPWash;
%end;
run;
*FOR DEBUG OPTION;
%CopyFile(FileName=_ITDrugs,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
%CopyFile(FileName=_ITMeds,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
%CopyFile(FileName=_ITLabs,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
proc datasets library= work nowarn nolist;
delete _ITDrugs _ITMeds _ITLabs;
quit;
/*---------------------------------------------------*/
/* 06.f -- POV1: Find All potential index dates */
/*---------------------------------------------------*/
%macro CreatePOV1(CohortId=);
*Add Cohort defining parameters to potential index dates;
data _GroupIndex&CohortId.;
set _GroupIndex&CohortId.;
if _N_=1 then set _CohortDef;
run;
%if %eval(&&itt&CohortId.=0) %then %do; *type 1, 2, 3 and 4;
*Find list of all potential index dates meeting washout wrt own group;
%IF %EVAL(&TYPE.=4 & &CohortId. eq 2) %THEN %DO;
*Create secondary episode cohort by different categories;
%macro FindGapByCategory();
%do Cat=1 %to 4;
*Adjust supply for DX and PX codes if necessary;
proc means data=_GroupIndex&CohortId. nway noprint missing;
class &&Category&Cat.;
var CodeSupply;
output out=_AdjustedSupply_&Cat. (drop=_:) &PXDXSUPPLYFUNC.=AdjustedSupply;
proc sort data=_GroupIndex&CohortId.;
by &&Category&Cat.;
run;
data _GroupIndex&CohortId._&Cat.;
merge _GroupIndex&CohortId.
_AdjustedSupply_&Cat.;
by &&Category&Cat.;
if not missing(CodeSupply) and not missing(AdjustedSupply) then do;
RxSup=AdjustedSupply;
Expiredt=ADate+RxSup-1;
end;
run;
%ms_findgap(InFile=_GroupIndex&CohortId._&Cat.,
OutFile=_POV1&CohortId._&Cat.,
sort=PatId &&Category&Cat. Adate ExpireDt,
NoDup=Y,
gapby=PatId &&Category&Cat.,
gap=WashPer&CohortId.);
%end;
data _POV1&CohortId.;
set _pov12_1(in=a)
_pov12_2(in=b)
_pov12_3(in=c)
_pov12_4(in=d);
if a then do;
Category="Category1";
Category2="";
Category3="";
Category4="";
end;
if b then do;
Category="Category2";
Category3="";
Category4="";
end;
if c then do;
Category="Category3";
Category4="";
end;
if d then Category="Category4";
run;
%mend FindGapByCategory;
%FindGapByCategory();
%END;
%ELSE %DO;
%let NoDuplicate=Y;
%if %eval(&type. eq 1) %then %do;
%let NoDuplicate=N;
%end;
%ms_findgap(InFile=_GroupIndex&CohortId.,
OutFile=_POV1&CohortId.,
sort=PatId Adate ExpireDt,
NoDup=&NoDuplicate.,
gapby=PatId,
gap=WashPer&CohortId.);
%END;
%end;
%else %do;
*In this section we make sure that any ADate of the study group that falls in an ITT period is ignored;
/*
Parameters to adjust same day dispensing
a: adds all (amount supplied or days supplied) values for dispensings in the same GROUP/SUBGROUP on the same day
n: uses minimum (amount supplied or days supplied) value for dispensings in the same GROUP/SUBGROUP on the same day
x: uses maximum (amount supplied or days supplied) value for dispensings in the same GROUP/SUBGROUP on the same day
m: uses mean (amount supplied or days supplied) value for dispensings in the same GROUP/SUBGROUP on the same day
*/
*Dedup same day dispensing using the same rule from stockpiling;
data _null_;
*daysupp;
if substr(UPCASE(strip("&SAMEDAY.")),1,1) = "A" then call symputx("fcn1","sum");
if substr(UPCASE(strip("&SAMEDAY.")),1,1) = "N" then call symputx("fcn1","min");
if substr(UPCASE(strip("&SAMEDAY.")),1,1) = "X" then call symputx("fcn1","max");
if substr(UPCASE(strip("&SAMEDAY.")),1,1) = "M" then call symputx("fcn1","mean");
if substr(UPCASE(strip("&SAMEDAY.")),2,1) = "A" then call symputx("fcn2","sum");
if substr(UPCASE(strip("&SAMEDAY.")),2,1) = "N" then call symputx("fcn2","min");
if substr(UPCASE(strip("&SAMEDAY.")),2,1) = "X" then call symputx("fcn2","max");
if substr(UPCASE(strip("&SAMEDAY.")),2,1) = "M" then call symputx("fcn2","mean");
run;
%put &SAMEDAY. translates to &fcn1. &fcn2.;
data _GroupIndexITT&CohortId.;
set _GroupIndex&CohortId.;
*Consider exposure for washout criterion;
ExpireDt=ADate+RxSup-1;
run;
*Apply Washout considering exposure;
%IF %EVAL(&TYPE.=4 & &CohortId. eq 2) %THEN %DO;
*Create secondary episode cohort by different categories;
%macro FindGapByCategory();
%do Cat=1 %to 4;
%ms_findgap(InFile=_GroupIndexITT&CohortId.,
OutFile=_POV1&CohortId._&Cat.,
sort=PatId &&Category&Cat. Adate ExpireDt,
NoDup=Y,
gapby=PatId &&Category&Cat.,
gap=WashPer&CohortId.);
*When ITT days is greater than exposure plus washper we must make sure that two consecutive
claims are separated by at least ITT days for the second one to be a valid index date;
data _POV1&CohortId._&Cat.;
set _POV1&CohortId._&Cat.;
ExpireDt=ADate;
run;
%ms_findgap(InFile=_POV1&CohortId._&Cat.,
OutFile=_POV1&CohortId._&Cat.,
sort=PatId &&Category&Cat. Adate ExpireDt,
NoDup=Y,
gapby=PatId &&Category&Cat.,
gap=ittdays&CohortId.);
proc sql noprint;
create table _GroupIndex_&CohortId._&Cat. as
select distinct tbl.Patid,
&&CategorySQL&Cat.,
tbl.Adate,
&fcn1.(RXSUP) as RXSUP,
&fcn2.(RXAMT) as RXAMT
from _GroupIndex&CohortId. as tbl
group by Patid, &&CategorySQL&Cat. , Adate;
run;
*assing RXSUP and RXAMT after correcting for same day dipsening at the index date;
data _GroupIndex&CohortId._&Cat.;
merge _POV1&CohortId._&Cat.(in=a drop=RxSup RxAmt)
_GroupIndex_&CohortId._&Cat.;
by PatId &&Category&Cat. Adate;
if a;
* Reset ExpireDt to first claim ADate plus supply;
ExpireDt=ADate+RxSup-1;
run;
data _POV1&CohortId._&Cat.;
merge _POV1&CohortId._&Cat.(in=a drop=ExpireDt)
_GroupIndex&CohortId._&Cat.(keep=PatId &&Category&Cat. ADate ExpireDt RxSup RxAmt);
by PatId &&Category&Cat. Adate;
if a;
run;
%end;
data _POV1&CohortId.;
set _pov12_1(in=a)
_pov12_2(in=b)
_pov12_3(in=c)
_pov12_4(in=d);
if a then do;
Category="Category1";
Category2="";
Category3="";
Category4="";
end;
if b then do;
Category="Category2";
Category3="";
Category4="";
end;
if c then do;
Category="Category3";
Category4="";
end;
if d then Category="Category4";
*Defensive coding;
AdjustedSupply=.;
run;
%mend FindGapByCategory;
%FindGapByCategory();
%END;
%ELSE %DO;
%ms_findgap(InFile=_GroupIndexITT&CohortId.,
OutFile=_POV1&CohortId.,
sort=PatId Adate ExpireDt,
NoDup=Y,
gapby=PatId,
gap=WashPer&CohortId.);
*When ITT days is greater than exposure plus washper we must make sure that two consecutive claims are separated
by at least ITT days for the second one to be a valid index date;
data _POV1&CohortId.;
set _POV1&CohortId.;
ExpireDt=ADate;
run;
%ms_findgap(InFile=_POV1&CohortId.,
OutFile=_POV1&CohortId.,
sort=PatId Adate ExpireDt,
NoDup=Y,
gapby=PatId,
gap=ittdays&CohortId.);
proc sql noprint;
create table _GroupIndex_&CohortId. as
select distinct tbl.Patid,
tbl.Adate,
&fcn1.(RXSUP) as RXSUP,
&fcn2.(RXAMT) as RXAMT
from _GroupIndex&CohortId. as tbl
group by Patid, Adate;
run;
*assing RXSUP and RXAMT after correcting for same day dipsening at the index date;
data _GroupIndex&CohortId.;
merge _POV1&CohortId.(in=a drop=RxSup RxAmt)
_GroupIndex_&CohortId.;
by PatId Adate;
if a;
* Reset ExpireDt to first claim ADate plus supply;
ExpireDt=ADate+RxSup-1;
run;
data _POV1&CohortId.;
merge _POV1&CohortId.(in=a drop=ExpireDt)
_GroupIndex&CohortId.(keep=PatId ADate ExpireDt RxSup RxAmt);
by PatId Adate;
if a;
run;
%END;
%end;*end itt>0;
/**********/
/* Demogs */
/**********/
proc sort nodupkey data=_POV1&CohortId. out=_UniquePts&CohortId.(Keep=PatId);
by PatId;
run;
*Add an observation Id to make sure all Type 1 observations will be kept (even true duplicates);
%if %eval(&Type. eq 1) %then %do;
data _POV1&CohortId.;
set _POV1&CohortId.;
ObsId=_N_;
%end;
proc sql noprint;
/*Age and demogs*/
create table _DemoCount&CohortId. as
select Dem.PatId,
Count(Dem.PatId) as numdemo
from indata.&demtable as DEM
inner join
_UniquePts&CohortId. as pts
on DEM.patid = pts.patid
group by Dem.PatId;
create table _Demo&CohortId. as
select distinct pts.*,
BIRTH_DATE,
SEX,
RACE,
(ADate-birth_date)/365.25 as Age ,
zip,
zip_date
from indata.&demtable as DEM
inner join
_POV1&CohortId. as pts
on DEM.patid = pts.patid
inner join _DemoCount&CohortId. (where=(numdemo=1)) as cnt
on cnt.patid = pts.patid;
create table _POV1&CohortId.
as select *
from _demo&CohortId.;
quit;
/** Process Zip Codes **/
%ISDATA(dataset=infolder.&ZIPFILE.);
%IF %EVAL(&NOBS.>0) %THEN %DO;
proc sql noprint;
create table _zip&CohortId. as
select pts.*,
lkup.statecode
from _POV1&CohortId. as pts
left join infolder.&zipfile. as lkup
on pts.zip = lkup.zip
order by pts.patid, adate;
quit;
data _POV1&CohortId.(drop=statecode);
length zip3 state $7;
set _zip&CohortId.;
if missing(zip) then do;
zip3 = 'Missing';
state = 'Missing';
end;
else if missing(statecode) then do;
zip3 = 'Invalid';
state = 'Invalid';
end;
else do;
zip3 = substr(strip(zip),1,3);
state = statecode;
end;
if missing(zip_date) or adate < zip_date then Zip_uncertain = 'Y';
else zip_uncertain = 'N';
run;
%END;
/** CALCULATE AGE and AGE STRATA SPECIFIED BY MSOC **/
*FOR DEBUG OPTION;
%CopyFile(FileName=_POV1&CohortId.,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
%ms_agestrat(infile=_POV1&CohortId.,
outfile=_POV1&CohortId.,
startdt=birth_date,
enddt=ADate,
timestrat=&agestrat.);
data _POV1&CohortId.(drop=Age AgeGroup MinAgeDate MaxAgeDate)
_AgeGroups&CohortId.(keep=PatId Adate Age AgeGroup MinAgeDate MaxAgeDate BIRTH_DATE sex Race zip: state rename=Adate=IndexDt);
set _POV1&CohortId.;
%IF %EVAL(&CohortId. ne 2) %THEN %DO;
if strip(AgeGroup) ne "";
%END;
%IF %STR(&GEOG.) = %STR() or %LENGTH(&GEOG.) = 0. %then %do;
state = '';
%end;
run;
proc sort nodupkey data=_agegroups&CohortId.;
by PatId IndexDt;
run;
%IF %EVAL(&TYPE.=4 & &CohortId. ne 2) %THEN %DO;
*Because _POV1 index dates actually contain delivery dates, they must be calculated
to contain pregnancy start dates and this must be done before _POV2 and _POV3 calculation;
*Create all pregnancy episodes, incident or not (for exclusion in control group);
proc sort nodupkey data=_GroupIndex out=_GroupEpis;
by PatId ADate;
run;
%ms_createclaimepi(infile=_GroupEpis,
gap=EpisodeGap,
outfile=_GroupEpis);
*Get Pre/Post term codes and calculate pregnancy durations and start dates;
proc sql noprint undo_policy=none;
create table _GroupEpis as
select epis.*,
pregstart.PreTerm,
pregstart.PostTerm,
pregstart.Priority,
pregstart.Duration,
pregstart.Code,
pregstart.ADate as PSAdate
from _GroupEpis as epis left join
_GroupPreg as pregstart
on epis.PatId = pregstart.PatId;
quit;
data _GroupEpis;
set _GroupEpis;
*Make sure pre/post term codes are close enough from delivery codes;
if abs(ADate - PSADate) >episodegap then
call missing(Code,PreTerm,PostTerm,Priority,Duration,PSADate);
if PreTerm = 1 then PrePostPriority = 1;
else if PostTerm = 1 then PrePostPriority = 2;
else PrePostPriority = 3;
run;
proc sort data = _GroupEpis;
by PatID Episode ADate PrePostPriority Priority;
run;
data _GroupEpis;
set _GroupEpis;
length EpisodeStartDt EpisodeEndDt 4.;
format EpisodeStartDt EpisodeEndDt mmddyy10.;
by PatId Episode;
EpisodeEndDt=Adate;
if Duration ne . then do;
EpisodeStartDt = EpisodeEndDt - Duration;
end;
else do;
EpisodeStartDt = EpisodeEndDt - &ittdays.;
end;
run;
data _EpisDates;
set _GroupEpis;
by PatId Episode;
if first.episode;
keep PatId ADate;
run;
data _PrePostCodes;
merge _GroupEpis
_EpisDates (in=b);
by PatId ADate;
if b;
run;
*Check if women had preterm and/or postterm codes;
proc means data=_PrePostCodes nway noprint;
var PreTerm PostTerm;
class PatId Episode;
output out=_PrePostCodes(drop=_:) max(PreTerm PostTerm)=HadPreTerm HadPostTerm;
run;
data _PrePostCodes;
set _PrePostCodes;
HadNone=0;
if HadPreTerm ne 1 and HadPostTerm ne 1 then HadNone=1;
if HadNone=1 then do;
HadPreTerm=0;
HadPostTerm=0;
end;
*If both PreTerm and PostTerm codes were found, do not consider PostTerm;
if HadPreTerm eq 1 and HadPostTerm eq 1 then HadPostTerm=0;
run;
data _GroupEpis
_GroupStartCodes;
merge _GroupEpis(in=a)
_PrePostCodes;
by PatId Episode;
if a;
*Identify pregnancies that really have 3 trimesters;
Duration=EpisodeEndDt - EpisodeStartDt;
Has3Trim=0;
if Duration>=181 then Has3Trim=1;
if first.Episode then output _GroupEpis; *Unique pre/post start code for episode uniqueness;
output _GroupStartCodes; *All pre/post start codes for statistics;
run;
proc sort data=_GroupStartCodes;
by PatId Adate;
run;
data _GroupStartCodes;
set _GroupStartCodes;
format IndexDt mmddyy10.;
by PatId Adate;
if first.ADate then TempDt=EpisodeStartDt;
IndexDt = TempDt;
retain TempDt;
drop TempDt;
run;
proc sort nodupkey data=_GroupStartCodes;
by PatId IndexDt Code;
run;
proc sort data =_GroupEpis;
by PatID ADate;
run;
data _POV1(rename=EpisodeStartDt2=ADate);
merge _POV1(in=a)
_GroupEpis(keep=PatId ADate EpisodeStartDt EpisodeEndDt);
by PatID ADate;
format EpisodeStartDt2 mmddyy10.;
if a;
ExpireDt=EpisodeStartDt;
EpisodeStartDt2=EpisodeStartDt;
drop ADate;
run;
proc sort data=_pov1;
by PatId ADate;
run;
%END;
%mend CreatePOV1;
*Leave CohortId blank for primary cohort;
%CreatePOV1(CohortId=);
%IF %EVAL(&TYPE.=4) %THEN %DO;
%CreatePOV1(CohortId=2);
%END;
/*---------------------------------------------------*/
/* 06.g -- POV2: identify all index dates that are */
/* free of POV2 records in washper */
/*---------------------------------------------------*/
%macro CreatePOV2(CohortId=);
%IF %EVAL(&TYPE.=4 & &CohortId. eq 2) %THEN %DO;
*Create secondary episode cohort by different categories;
%macro Pov2ByCategory();
%do Cat=1 %to 4;
proc sql noprint;
create table _POV2&CohortId._&Cat.(where=(InGap=1)) as
select distinct index.Patid,
index.Adate,
index.Category1,
index.Category2,
index.Category3,
index.Category4,
index.Category,
%MS_PeriodsOverlap(period1=index.Adate-min(Washper&CohortId.,99999) index.Adate-1,
period2=Inc.ADate Inc.ExpireDt) as InGap
from _POV1&CohortId.(where=(Category="Category&Cat.")) as index,
_GroupWash&CohortId. as Inc
where index.Patid = Inc.Patid
order by index.Patid,
index.Adate;
quit;
%end;
data _POV2&CohortId.;
set _pov22_1(in=a)
_pov22_2(in=b)
_pov22_3(in=c)
_pov22_4(in=d);
run;
%mend Pov2ByCategory;
%Pov2ByCategory();
%END;
%ELSE %DO;
proc sql noprint;
create table _POV2&CohortId.(where=(InGap=1)) as
select distinct index.Patid,
index.Adate,
%MS_PeriodsOverlap(period1=index.Adate-min(Washper&CohortId.,99999) index.Adate-1,
period2=Inc.ADate Inc.ExpireDt) as InGap
from _POV1&CohortId. as index,
_GroupWash&CohortId. as Inc
where index.Patid = Inc.Patid
order by index.Patid,
index.Adate;
quit;
%END;
%mend CreatePOV2;
%CreatePOV2(CohortId=);
%IF %EVAL(&TYPE.=4) %THEN %DO;
%CreatePOV2(CohortId=2);
%END;
/*---------------------------------------------------*/
/* 06.h -- POV3: identify all index dates meeting */
/* inclusion Exclusion */
/*---------------------------------------------------*/
*initialize for the case where No inclusion/exclusion criteria => keeping all patients;
data _POV3;
set _POV1;
keep Patid Adate;
run;
%ISDATA(dataset=&INCLUSIONCODES.);
%let INCL=0;
%let EXCL=0;
%IF %EVAL(&NOBS.>=1) %THEN %DO;
*Are there any incl/excl conds to verify for this iteration?;
data _IT&INCLUSIONCODES.;
set &INCLUSIONCODES.;
where group="&ITGROUP.";
run;
%ISDATA(dataset=_IT&INCLUSIONCODES.);
%IF %EVAL(&NOBS.>=1) %THEN %DO;
*Initialize list with all potential index dates;
data _POV3;
set _POV1;
MustKickOutHard=0; *cumulative;
MustKickOutElig=0; *cumulative;
keep PatId Adate Enr_Start Enr_end MustKickOut:;
run;
*Uniqueness of _InclExcl;
Proc Sort nodupkey data=_InclExcl out=_InclExcl;
by Patid Adate Expiredt CondInclusion cond subcond SubCondInclusion CondFrom CondTo;
run;
*Get all in INCL/EXCL period claims (both incl and Excl)
relative to each potential index dates;
proc sql noprint;
create table _POV3Claims(where=(InPeriod=1)) as
select index.Patid,
index.Adate,
Inc.CondInclusion,
Inc.cond,
Inc.Subcond,
Inc.SubCondinclusion,
%MS_PeriodsOverlap(period1=COALESCE(index.Adate+CondFrom,-999999) COALESCE(index.Adate+CondTo,999999),
period2=Inc.ADate Inc.Expiredt) as InPeriod
from _POV1 as index,
_InclExcl as Inc
where index.Patid = Inc.Patid
order by index.Patid,
index.Adate;
quit;
data _null_;
set _IT&INCLUSIONCODES.;
if CondInclusion=0 then call symputx("EXCL",1);
if CondInclusion=1 then call symputx("INCL",1);
run;
%put EXCL = &EXCL.;
%put INCL = &INCL.;
*Determine how many conditions;
%let MAXCOND=;
proc sql;
select max(cond) into: maxcond
from _IT&INCLUSIONCODES.;
quit;
data _NULL_;
call symputx("MAXCOND",&MAXCOND.);*For sum issue;
run;
%put &MAXCOND.;
%DO COND=1 %TO &MAXCOND.;
%let MAXSUBCOND=0;
*Determine how many levels of subconditions needed;
proc sql;
select max(subcond) into: maxsubcond
from _IT&INCLUSIONCODES. (where=(cond=&COND.));
quit;
data _NULL_;
call symputx("MAXSUBCOND",&MAXSUBCOND.);*For sum issue;
run;
%put &MAXSUBCOND.;
*Determine whether this COND is an exclusion or inclusion;
proc sql;
select max(CondInclusion) into: inclusion
from _IT&INCLUSIONCODES. (where=(cond=&COND.));
select min(CondFrom) into: MinimumCondFrom
from _IT&INCLUSIONCODES. (where=(cond=&COND.));
select max(CondTo) into: MaximumCondTo
from _IT&INCLUSIONCODES. (where=(cond=&COND.));
quit;
%put &INCLUSION. &MinimumCondFrom. &MaximumCondTo.;
*Set data for this cond;
data _POV3_&cond.;
set _POV3(keep=Patid ADate ENR_START ENR_END);
ExclCond&COND.=0;
run;
%DO SUBCOND=1 %TO &MAXSUBCOND.;
%let subinclusion=0;
*Determine if SUBCOND is an exclusion or inclusion;
proc sql;
select max(SubCondInclusion) into: SUBCONDINCLUSION
from _IT&INCLUSIONCODES. (where=(cond=&COND. and subcond=&SUBCOND.));
select min(CondFrom) into: CondFrom
from _IT&INCLUSIONCODES. (where=(cond=&COND. and subcond=&SUBCOND.));
select max(CondTo) into: CondTo
from _IT&INCLUSIONCODES. (where=(cond=&COND. and subcond=&SUBCOND.));
quit;
%put &SUBCONDINCLUSION. &CondFrom. &CondTo.;
proc sort nodupkey data=_POV3Claims out=_POV3Incl_tmp(keep=PatId Adate);
by PatId Adate; *Here Adate is of the episode, not the INCL;
where cond=&COND. and subcond=&SUBCOND.;
run;
*Square;
data _POV3_&cond.;
merge _POV3_&cond.(in=a)
_POV3Incl_tmp(in=b);
by PatId Adate;
if a and b then SubCond_&COND._&SUBCOND.=1;
else SubCond_&COND._&SUBCOND.=0;
if &SUBCONDINCLUSION.=1 then do;
if a and b then SubCond_&COND._&SUBCOND.=1;
else SubCond_&COND._&SUBCOND.=0;
end;
*If the subcondition is met but it is a subexclusion, then means that condition not satisfied;
else do;
if a and b then SubCond_&COND._&SUBCOND.=0;
else SubCond_&COND._&SUBCOND.=1;
end;
run;
*Check elig for the subconditions exclusions;
*If the condition is a CondExclusion, then it will be checked again later with different assumptions;
%if &SUBCONDINCLUSION.=0 and &inclusion.=1 %then %do;
data _POV3_&cond.;
set _POV3_&cond.;
*check that the patient meets also meets elig for this subcondition;
if not(Enr_Start <= Adate + COALESCE(&CondFrom.,0) and
Enr_End >= Adate + COALESCE(&CondTo.,0)) then do;*Open ended means -infty/infty;
SubCond_&COND._&SUBCOND.=0;
ExclCond&COND.=1;
end;
run;
%end;
%END; *loop subcond;
*If all subconditions are satisfied, then condition is satisfied (at this point);
data _POV3_&cond.;
set _POV3_&cond.;
Cond&cond.=sum(of SubCond_&COND._1-SubCond_&COND._&MAXSUBCOND.)=&MAXSUBCOND.;
run;
%let condFrom2=.;
%let condto2=.;
data _null_;
set _IT&INCLUSIONCODES. (where=(cond=&COND.));
if CondInclusion=0 then do;
call symputx('CondFrom2',"&MinimumCondFrom.");
call symputx('CondTo2',"&MaximumCondTo.");
end;
run;
%put &CondFrom. &CondTo. &CondFrom2. &CondTo2.;
*Manage if an exclusion - if you have one hard exclusion
or not enough elig, you are out;
data _POV3_&cond.;
set _POV3_&cond.;
if &INCLUSION.=0 then do;
*if the patient had cond&cond.=1 then can never be included;
if Cond&cond.=1 then MustKickOutHard_&cond.=1;
else MustKickOutHard_&cond.=0;
*flip the dummy;
Cond&cond.=Cond&cond.=0;
*Because we can have CondInclusion=0 and SubInclusion=1, we still need to consider if enough elig in this case;
MustKickOutElig_&cond.=0;
if not(Enr_Start <= Adate + COALESCE(&CondFrom2.,0) and
Enr_End >= Adate + COALESCE(&CondTo2.,0)) then do;
MustKickOutElig_&cond.=1;
ExclCond&COND.=1; *Open ended means -infty/infty;
Cond&cond.=0;
end;
end;
*Create a dummy to remember if this condition was an inclusion or and exlusion;
InclCond&COND.=&INCLUSION.;
run;
*Put all together;
data _POV3;
merge _POV3(in=a)
_POV3_&cond.(in=b keep=PatId Adate Cond: SubCond: Excl: InclCond: MustKickOut:);
by PatId Adate;
if MustKickOutHard_&cond.=1 then MustKickOutHard=1;
if MustKickOutElig_&cond.=1 then MustKickOutElig=1;
if a;
run;
%END;*loop cond;
*Clean up;
data _POV3;
set _POV3;
array _cond(*) Cond1-cond&maxcond.;
array _InclCond(*) InclCond1-InclCond&maxcond.;
*at least one inclusion criterion;
%if &maxcond. >1 %then %do;
inclusion=max(of InclCond1-InclCond&maxcond.);
%end;
%else %do;
inclusion=InclCond1;
%end;
*initialize AnyCond;
if inclusion ne 1 then AnyCond=1;
else do;
AnyCond=0;
*Find those with at least on inclusion met;
do i=1 to &maxcond.;
if _cond(i)=1 and _InclCond(i)=1 then AnyCond=1;
end;
end;
*kick out any index not meeting the elig criteria for at least one exlcusion
or that has at least one hard exclusion (claim).;
if MustKickOutElig or MustKickOuthard then AnyCond=0;
drop inclusion;
run;
data _POV3Excl1;*for attrition;
set _POV3;
if MustKickOutHard then output _POV3Excl1;
run;
data _POV3Excl2;*for attrition;
set _POV3;
if MustKickOutElig then output _POV3Excl2;
run;
data _POV3Incl;*for attrition;
set _POV3;
If AnyCond then output _POV3Incl;
run;
data _POV3;
set _POV3Incl;
run;
%END; *_ITINCLUSIONCODES;
%END; *INCLUSIONCODES;
/*---------------------------------------------------*/
/* 06.i -- POV4: Create At-Risk Episodes */
/*---------------------------------------------------*/
%macro CreatePOV4(CohortId=);
%IF %EVAL((&TYPE.=2 | &TYPE.=4) & &&ITT&CohortId.>=1) %THEN %DO;
*Create itt episodes;
%IF %EVAL(&CohortId. ne 2) %THEN %DO;
data _POV4&CohortId.;
set _POV1&CohortId.;
by PatId ADate;
if first.PatId then Episode=0;
Episode=Episode+1;
Expiredt=min(Adate+&&ittdays&CohortId.-1, Enr_end);
retain Episode;
run;
%END;
%ELSE %IF %EVAL(&TYPE.=4) %THEN %DO;
*Create secondary episode cohort by different categories;
%macro EpisByCategory();
%do Cat=1 %to 4;
proc sort data=_POV1&CohortId._&Cat. out=_POV4&CohortId._&Cat.;
by Patid &&Category&Cat. Adate;
run;
data _POV4&CohortId._&Cat.;
set _POV4&CohortId._&Cat.;
by PatId &&Category&Cat. ADate;
if first.Category&Cat. then Episode=0;
Episode=Episode+1;
Expiredt=min(Adate+&&ittdays&CohortId.-1, Enr_end);
retain Episode;
run;
%end;
%mend EpisByCategory;
%EpisByCategory();
%END;
%END;
%ELSE %IF %EVAL(&TYPE.=2 | &TYPE.=4) %THEN %DO;
%IF %EVAL(&CohortId. ne 2) %THEN %DO;
*Create claim based episodes (as-treated);
%ms_createclaimepi(infile=_GroupIndex&CohortId.,
gap=episodegap&CohortId.,
outfile=_POV4&CohortId.);
%END;
%ELSE %IF %EVAL(&TYPE.=4) %THEN %DO;
*Create secondary episode cohort by different categories;
%macro EpisByCategory();
%do Cat=1 %to 4;
*Create claim based episodes (as-treated);
%ms_createclaimepibycat(infile=_GroupIndex&CohortId._&Cat.,
category=&&Category&Cat.,
lastCat=Category&Cat.,
gap=episodegap&CohortId.,
outfile=_POV4&CohortId._&Cat.);
%end;
%mend EpisByCategory;
%EpisByCategory();
%END;
%END;
%ELSE %IF %EVAL(&TYPE.=1) %THEN %DO;
*Create itt episodes;
data _POV4;
set _POV1;
by Patid;
if first.PatId then Episode=0;
Episode=Episode+1;
*trick Adate and ExpireDt to mimic an episode;
length EpisodeStartDt EpisodeEndDt 4.;
format EpisodeStartDt EpisodeEndDt date9.;
EpisodeStartDt=Adate; *placeholder;
EpisodeEndDt=min(ExpireDt, Enr_end);
retain Episode;
run;
*Creating empty IOT file along with empty type2 variable;
data _IOT;
set _POV4(obs=0);
length TrunkDate 4.;
TrunkDate=.;
T2FupWashPer=.;
minepisdur=.;
maxepisdur=.;
mindaysupp=.;
T2CohortDef=" ";
keep PatId EpisodeStartDt TrunkDate T2FupWashPer minepisdur maxepisdur mindaysupp T2CohortDef;
run;
%END;
%IF %EVAL(&TYPE.=2 | &TYPE.=4) %THEN %DO;
%IF %EVAL(&CohortId. ne 2) %THEN %DO;
*Summarize episode level details;
Proc means data=_POV4&CohortId. nway noprint missing;
var Adate ExpireDt;
class PatId Episode;
output out=_POV4&CohortId.(drop=_:) min(Adate)=EpisodeStartDt
max(ExpireDt)=EpisodeEndDt/KEEPLEN;
run;
*Adjust EpisodeEndDt for episode Extension that overlap the next episode;
*will not impact itt;
data _NextEpisodeStart&CohortId.;
set _POV4&CohortId.;
Episode=Episode-1;
format NextEpisodeStartDt date9.;
NextEpisodeStartDt=EpisodeStartDt;
keep PatId Episode NextEpisodeStartDt;
run;
data _POV4&CohortId.;
merge _POV4&CohortId.(in=a)
_NextEpisodeStart&CohortId.;
by PatId Episode;
if _N_=1 then set _CohortDef(keep=expextper&CohortId.);
if a;
EpisodeEndDt=sum(EpisodeEndDt,expextper&CohortId.);
if &&itt&CohortId.=0 and NextEpisodeStartDt and EpisodeEndDt >= NextEpisodeStartDt then EpisodeEndDt = NextEpisodeStartDt - 1;
if EpisodeEndDt and EpisodeEndDt<EpisodeStartDt then delete;
drop NextEpisodeStartDt expextper&CohortId.;
run;
%IF %EVAL(&TYPE.=2) %THEN %DO;
*Truncate (potentially extended using Episode Extension) episodes with IOT/FUT;
Proc Sort nodupkey data=_GroupWashForTrunk out=_GroupWashForTrunk;
by Patid Adate;* Expiredt;
run;
proc sql noprint;
create table _IOT as
select distinct Epi.PatId,
Epi.EpisodeStartDt,
min(Trunk.Adate) as TrunkDate format date9.,
" " as T1CohortDef format $2.
from _POV4 as Epi,
_GroupWashForTrunk as trunk
where Epi.Patid = trunk.Patid and %MS_PeriodsOverlap(period1=Epi.EpisodeStartDt Epi.EpisodeEndDt,
period2=trunk.ADate)
group by Epi.Patid,
Epi.EpisodeStartDt
order by Epi.Patid,
Epi.EpisodeStartDt;
quit;
%END;
%END;
%ELSE %IF %EVAL(&TYPE.=4) %THEN %DO;
%macro Pov4ByCategory();
%do Cat=1 %to 4;
*Summarize episode level details;
Proc means data=_POV4&CohortId._&Cat. nway noprint missing;
var Adate ExpireDt;
class PatId &&Category&Cat. Episode;
output out=_POV4&CohortId._&Cat.(drop=_:) min(Adate)=EpisodeStartDt
max(ExpireDt)=EpisodeEndDt/KEEPLEN;
run;
*Adjust EpisodeEndDt for episode Extension that overlap the next episode;
*will not impact itt;
data _NextEpisodeStart&CohortId._&Cat.;
set _POV4&CohortId._&Cat.;
Episode=Episode-1;
format NextEpisodeStartDt date9.;
NextEpisodeStartDt=EpisodeStartDt;
keep PatId &&Category&Cat. Episode NextEpisodeStartDt;
run;
data _POV4&CohortId._&Cat.;
merge _POV4&CohortId._&Cat.(in=a)
_NextEpisodeStart&CohortId._&Cat.;
by PatId &&Category&Cat. Episode;
if _N_=1 then set _CohortDef(keep=expextper&CohortId.);
if a;
EpisodeEndDt=sum(EpisodeEndDt,expextper&CohortId.);
if &&itt&CohortId.=0 and NextEpisodeStartDt and EpisodeEndDt >= NextEpisodeStartDt then EpisodeEndDt = NextEpisodeStartDt - 1;
if EpisodeEndDt and EpisodeEndDt<EpisodeStartDt then delete;
drop NextEpisodeStartDt expextper&CohortId.;
run;
%end;
data _POV4&CohortId.;
set _pov42_1(in=a)
_pov42_2(in=b)
_pov42_3(in=c)
_pov42_4(in=d);
if a then Category="Category1";
if b then Category="Category2";
if c then Category="Category3";
if d then Category="Category4";
run;
%mend Pov4ByCategory;
%Pov4ByCategory();
%END;
*FOR DEBUG OPTION;
%CopyFile(FileName=_Nextepisodestart&CohortId.,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
%CopyFile(FileName=_GroupWashForTrunk,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
proc datasets library=work nowarn nolist;
delete _Nextepisodestart _GroupWashForTrunk;
quit;
%END; *TYPE=2 or 4;
%IF %EVAL(&TYPE.=4 & &CohortId. ne 2) %THEN %DO;
proc sort data=_pov1 (drop=ConcWashPer RXSUP RXAMT DaysUntreated episodegap episodegap2 expextper expextper2
ittdays ittdays2 t4cohortdef t4CohortDef2 t4fupwashper2 WashPer2);
by PatId EpisodeStartDt;
run;
proc sort data=_GroupEpis;
by PatId EpisodeStartDt;
run;
data _POV4;
merge _POV1 (in=a)
_GroupEpis;
by PatId EpisodeStartDt;
if a;
run;
%END; *TYPE=4;
%mend CreatePOV4;
%CreatePOV4(CohortId=);
%IF %EVAL(&TYPE.=4) %THEN %DO;
%CreatePOV4(CohortId=2);
%END;
/*---------------------------------------------------*/
/* 06.j -- Create Final Record wrt index date */
/*---------------------------------------------------*/
%IF %EVAL(&TYPE.=2 | &TYPE.=3) %THEN %DO;
*add whether the enrollment was trucated due to death;
proc sql noprint undo_policy=none;
create table _POV1 as
select b.*,
e.EnrEpisodeCensoredAtDeath,
e.DeathDt
from _POV1 as b left join
ENR_&GROUP. as e
on b.PatId = e.PatId and
b.Enr_Start = e.Enr_Start and
b.Enr_End = e.Enr_End
order by b.PatId,
b.ADate;
quit;
%if &TYPE.=2 %then %do; /*already adjusted for type 3*/
/*Truncate enrollment at death date (for denominators*/
data ENR_&GROUP.;
set ENR_&GROUP.;
if &censor_dth.=1 then do;
if EnrEpisodeCensoredAtDeath = 1 and DeathDt <= Enr_End then do;
Enr_End=DeathDt;
end;
if Enr_end < Enr_Start then delete;
end;
run;
%end;
*FOR DEBUG OPTION;
%CopyFile(FileName=_POV1,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
%end;
%IF %EVAL(&TYPE.=3) %THEN %DO;
*FOR DEBUG OPTION;
%CopyFile(FileName=_POV1,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
data _null_;
call symputx("ExpPeriodEndDt",INTNX('Month', &T3ENDOFUPDATE.-max(&T3RiskTo.,&T3CtrlTo.,0),0, 'begin')-1); *for inclusion in metadatafile;
run;
data _null_;
call symputx("MaxExpPeriodEndDt",Max(&MaxExpPeriodEndDt., &ExpPeriodEndDt.));
run;
*Manage All Dates;
data _POV1;
set _POV1;
by PatId;
*Calulate windows start and end dates for reuse;
format T3CtrlFromDt T3CtrlToDt T3RiskFromDt T3RiskToDt date9.;
T3CtrlFromDt=Adate+T3CtrlFrom;
T3CtrlToDt=Adate+T3CtrlTo;
T3RiskFromDt=Adate+T3RiskFrom;
T3RiskToDt=Adate+T3RiskTo;
*Calculate length of lookback period for HOI INCIDENCE ASSESSMENT;
format MinWindowDt MaxWindowDt date9.;
MinWindowDt=min(ADate, T3CtrlFromDt,T3RiskFromDt);
MaxWindowDt=max(ADate, T3CtrlToDt,T3RiskToDt);
LookBackLength=MaxWindowDt-MinWindowDt+1;
RequiredFUPPer=max(T3RiskTo,T3CtrlTo,0);
call symputx("ExpPeriodEndDt",INTNX('Month', &T3ENDOFUPDATE.-RequiredFUPPer,0, 'begin')-1); *for inclusion in metadatafile;
*Calculate min/max elig requirement from Adate perspective and using LookBackLength;
*Note: Exclusion elig requirement is already accounted for in POV3;
format MinEligtDt MaxEligtDt MinEligtHOIDt MaxEligtEnrAftDt date9.;
*NOTE: if CCI is to be added in a future version, then PRIORDAYS_bf and PRIORDAYS_af must
be considered and attrition will need to be revised;
MinEligtDt=Adate-max(0, &enrdays., Washper/*, -1*&PRIORDAYS_bf.*/);
* MinEligtDt=min(MinEligtDt, MinWindowDt - LookBackLength);
MinEligtHOIDt=min(MinEligtDt, MinWindowDt - LookBackLength);
MaxEligtDt=max(0,MaxWindowDt,/*sum(*/Adate/*,&PRIORDAYS_af.)*/);
MaxEligtEnrAftDt=.;
if &enrdaysaft. > 0 then MaxEligtEnrAftDt=Max(MaxEligtDt, Adate + &enrdaysaft.);
if T3FupWashPer = . then LookBackLength=999999;
LookBackLength=max(LookBackLength,T3FupWashPer);
run;
*IndexDt Reference Cohort Criteria (Exposure Event Criteria);
Data _PtsMasterList;
Merge _POV1(in=pov1 rename=Adate=IndexDt)
_POV2(in=pov2 rename=Adate=IndexDt drop=InGap)
_POV3(in=pov3 rename=Adate=IndexDt keep=patid Adate)
_AgeGroups(keep=PatId IndexDt age BIRTH_DATE SEX RACE AgeGroup);
by Patid IndexDt; * Adate respresents potential Index Date;
if pov1 and not pov2 and pov3;
*Minimum look back enrollment duration;
if Enr_Start <= MinEligtDt then MinEnrolMet=1;
if Enr_Start <= MinEligtHOIDt then MinEnrolHOIMet=1;
if Enr_End >= MaxEligtDt then MinEnrolMet2=1;
if MinEnrolMet;
*create EpisodeStartDt variable;
format IndexDt ENR_START ENR_END date9.;
label IndexDt="IndexDt";
keep PatID IndexDt
ENR_START ENR_END EnrEpisodeCensoredAtDeath MinEnrolHOIMet MinEnrolMet2
MaxEligtEnrAftDt MinEligtDt MaxEligtDt MinWindowDt MaxWindowDt LookBackLength
T3FupWashPer T3CohortDef T3CtrlFromDt T3CtrlToDt T3RiskFromDt T3RiskToDt
age BIRTH_DATE SEX RACE AgeGroup MinEligtHOIDt;
run;
%END;
%ELSE %DO;*else if type ne 3;
%IF %EVAL(&TYPE. ne 4) %THEN %DO;
*Add looks-related variables to POV1 for this group;
proc sql noprint undo_policy=none;
create table _POV1 as
select dat.*,
look.LookEnd as IndexDtLookEndDt,
Look.Look as IndexLook
from _POV1 as dat,
looks as look
where look.LookStart<=dat.Adate<=look.LookEnd
order by Patid,Adate;
quit;
%put *******************;
%put &L1HDPSFROM. &L1HDPSTo.;
*IndexDt Reference Cohort Criteria;
Data _PtsMasterList;
Merge _POV1(in=pov1 rename=Adate=IndexDt)
_POV2(in=pov2 rename=Adate=IndexDt drop=InGap)
_POV3(in=pov3 rename=Adate=IndexDt keep=patid Adate)
_POV4(in=pov4 rename=EpisodeStartDt=IndexDt)
_IOT(in=iot rename=EpisodeStartDt=IndexDt);
by Patid IndexDt; * Adate represents potential Index Date;
if pov1 and not pov2 and pov3 and pov4;
*indexDate must overlap greater monitoring period;
if &startfollowup. <= IndexDt <= &enddate.;
OrigEpisEndDt = EpisodeEndDt;
*Adjust EpisodeEndDt if EpisodeEndDt > enddate;
%if &censor_qryend = 1 %then %do;
if EpisodeEndDt > &enddate. then EpisodeEndDt = &enddate.;
%end;
%if %eval(&type. = 2) %then %do;
/*Adjust EpisodeEndDt if EpisodeEndDt > DeathDt*/
if EnrEpisodeCensoredAtDeath = 1 and EpisodeEndDt > DeathDt then EpisodeEnddt = deathdt;
%end;
*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;
*ExpExtPer may have made the episode to go beyond elig.;
if EpisodeEndDt > enr_end then EpisodeEndDt = enr_end;
if blackoutper =. then blackoutper=0;
*Minimum look back enrollment duration;
if Enr_Start <= IndexDt-max(0,&enrdays.,Washper,T2FupWashPer,-1*&PRIORDAYS_bf.,&MinCovFrom.,&L1HDPSFROM.) then MinEnrolMet=1;
if Enr_End >= IndexDt+max(0,&PRIORDAYS_af.,&MaxCovTo.,&L1HDPSTO.) then MinEnrolMet2=1;
*Minimum Episode Duration;
if EpisodeEndDt-IndexDt + 1 >= minepisdur then MinEpiDurMet=1;
*Maximum Episode Duration;
if not missing(maxepisdur) and EpisodeEndDt-IndexDt + 1 > maxepisdur then EpisodeEndDt = IndexDt + maxepisdur -1;
*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;
*patients must 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" then do;
if blackoutper and IndexDtLookEndDt - (IndexDt + blackoutper) < 0 then delete;
end;
if MinEnrolMet and MinEnrolMet2 and FUEnrolMet and MinEpiDurMet;
*Defensive coding;
if IndexDt > EpisodeEndDt then delete;
*create EpisodeStartDt variable;
format IndexDt EpisodeEndDt ENR_START ENR_END date9.;
label IndexDt="IndexDt";
keep PatID IndexDt EpisodeEndDt ENR_START ENR_END
mindaysupp T2FupWashPer blackoutper T2CohortDef T1CohortDef IndexLook washper RxSup RxAmt NumDispensing
%if %eval(&type.=2) %then %do;
OrigEpisEndDt trunkdate EnrEpisodeCensoredAtDeath maxepisdur Deathdt
%end;
;
run;
*Add w.a.r.n.i.n.g. if MinCovFrom is bigger in absolute terms than priordays;
%isdata(dataset=_PtsMasterList);
%IF %EVAL(&NOBS.>=1) %THEN %DO;
data _null_;
set _PtsMasterList;
MINFROM=max(0,&enrdays.,Washper,T2FupWashPer,-1*&PRIORDAYS_bf.);
MAXTO=max(0,&PRIORDAYS_af.);
call symputx("MINFROM",MINFROM);
call symputx("MAXTO",MAXTO);
run;
%IF %EVAL(&MinCovFrom. > &MINFROM.) %THEN %DO;
%put WARNING: many elig patients may be lost due to lookback days used for covariates (MinCovFrom);
%END;
%IF %EVAL(&MaxCovTo. > &MAXTO.) %THEN %DO;
%put WARNING: many elig patients may be lost due to lookforward days used for covariates (MaxCovTo);
%END;
%put &MinCovFrom. &MINFROM. &MAXTO.;
%END;*if dataset is populated;
data _PtsMasterList;
set _PtsMasterList (drop=washper);
run;
%END;
%ELSE %DO;
*IndexDt Reference Cohort Criteria;
*Output data for enroll table;
data _PtsMasterList
_Deliveries(drop=adate birth_date had: has: post: pre: priority race sex code: indexdt expiredt episode
washper t4fupwashper ConcWashPer MinEnrolMet);
merge _POV1(in=pov1 rename=Adate=IndexDt)
_POV2(in=pov2 rename=Adate=IndexDt drop=InGap)
_POV3(in=pov3 rename=Adate=IndexDt keep=patid Adate)
_POV4(in=pov4 rename=EpisodeStartDt=IndexDt);
by Patid IndexDt; * Adate represents potential Index Date;
if first.PatId then DelNum=1;
else DelNum=DelNum+1;
retain DelNum;
if pov1 and not pov2 and pov3 and pov4;
* Apply the period dates criterion to episode end date (unlike type 1, 2 and 3 it is not applied to IndexDt);
if &startfollowup. <= EpisodeEndDt <= &enddate.;
*Minimum look back enrollment duration;
if Enr_Start <= EpisodeEndDt -max(0,&enrdays.,Washper,/*Washper,*/T4FupWashPer) then MinEnrolMet=1;
*Defensive coding;
if IndexDt > EpisodeEndDt then delete;
*create EpisodeStartDt variable;
format IndexDt EpisodeEndDt ENR_START ENR_END date9.;
label IndexDt="IndexDt";
*Create eligdays for enrolment tables;
eligdays=adate-enr_start;
drop Duration episodegap episodegap2 expextper expextper2 gap ittdays ittdays2
LEnrStart LRunOutDate RXAMT RXSUP WashPer2 t4CohortDef2 t4cohortdef t4fupwashper2;
if MinEnrolMet then output _PtsMasterList;
output _Deliveries;
run;
proc sql noprint undo_policy=none;
create table _PtsMasterList as
select mstr.*,
age.AgeGroup
from _PtsMasterList as mstr,
_AgeGroups as age
where mstr.PatId=age.PatId and mstr.EpisodeEndDt=age.IndexDt
order by Patid,IndexDt;
quit;
proc sql noprint undo_policy=none;
create table _Deliveries as
select del.*,
age.AgeGroup
from _Deliveries as del,
_AgeGroups as age
where del.PatId=age.PatId and del.EpisodeEndDt=age.IndexDt
order by Patid,IndexDt;
quit;
%END;
%IF %EVAL(&TYPE.=4) %THEN %DO;
proc sql noprint;
create table _PtsMasterList2 as
select pov1.*,
pov4.Episode,
pov4.EpisodeStartDt as IndexDt,
pov4.EpisodeEndDt
from _POV12 as pov1,
_POV42 as pov4
where pov1.PatId=pov4.PatId and
pov1.ADate=pov4.EpisodeStartDt and
pov1.Category1=pov4.Category1 and
pov1.Category2=pov4.Category2 and
pov1.Category3=pov4.Category3 and
pov1.Category4=pov4.Category4 and
pov1.Category=pov4.Category
order by PatId, ADate, Category1, Category2, Category3, Category4, Category;
quit;
proc sort data=_POV22;
by PatId ADate Category1 Category2 Category3 Category4 Category;
run;
data _PtsMasterList2;
merge _PtsMasterList2 (in=a)
_POV22(in=pov2 drop=InGap);
by PatId ADate Category1 Category2 Category3 Category4 Category; * Adate represents potential Index Date;
if a and not pov2;
drop ConcWashPer DaysUntreated episodegap: expextper: ExpireDt ittdays ittdays2 t4cohortdef t4CohortDef2
t4fupwashper t4fupwashper2 WashPer WashPer2 ADate RxSup RxAmt CodeSupply AdjustedSupply;
run;
%END;
%END;
/**********/
/* EVENTS */
/**********/
%if %eval(&type=2) %then %do;
*Summarize utilization that are overlapping selected episodes;
%ms_shaveoutside(reffile=_PtsMasterList,
refstart=IndexDt,
refend=EpisodeEndDt,
KeepPartBf=Y,
ToShaveFile=_GroupIndex,
ToShaveStart=Adate,
ToShaveEnd=ExpireDt,
outfile=_GroupUtilFile);
*Summarize RxSup during episode (only to apply mindaysupp criterion);
*The final summarization will be done later when episode selection is finalized;
Proc means data=_GroupUtilFile nway noprint;
var RxSup RxAmt;
class PatId IndexDt;
output out=_GroupUtilFile(keep=PatId IndexDt TotRxSup) sum(RxSup)=TotRxSup;
run;
*FOR DEBUG OPTION;
%CopyFile(FileName=_PtsMasterList,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
data _PtsMasterList;
merge _PtsMasterList(in=a)
_GroupUtilFile;
by PatId IndexDt;
if a;
if TotRxSup >= mindaysupp then MinDaySupMet=1;
if MinDaySupMet;
drop TotRxSup;
run;
*Find patients with Events in T2FupWashPer;
*if T2FupWashPer=. then patient needs to never have had an Event (hence 99999);
proc sql noprint;
create table _EventsInFupWash(where=(InGap=1)) as
select distinct Epi.PatId,
Epi.IndexDt,
%MS_PeriodsOverlap(period1=Epi.IndexDt-min(Epi.T2FupWashPer,99999) Epi.IndexDt+Epi.blackoutper-1,
period2=pov5.ADate pov5.ExpireDt) as InGap
from _PtsMasterList 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(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 as Epi,
_FUPWash(keep=PatId Adate Expiredt) as pov6
where Epi.Patid = pov6.Patid
order by Epi.Patid,
Epi.indexDt;
quit;
*FOR DEBUG OPTION;
%CopyFile(FileName=_PtsMasterList,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
*Keep only ValidEpisodes;
data _PtsMasterList;
merge _PtsMasterList(in=a)
_EventsInFupWash(in=b keep=PatId IndexDt)
_WashEventsInFupWash(in=c keep=PatId IndexDt);
by PatId IndexDt;
if a and not b and not c;
run;
%IF %EVAL(&eventcount.=1) %THEN %DO;
proc sort nodupkey data=_FUPEvent;
by PatId Adate codecat codetype code;
run;
%END;
%IF %EVAL(&eventcount.=2) %THEN %DO;
proc sort nodupkey data=_FUPEvent;
by PatId Adate;
run;
%END;
*Keeping only overlapping events;
%ms_shaveoutside(reffile=_PtsMasterList,
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 _EPisodesWithEvents(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 as index,
_FUPEventShaved as Inc
where index.Patid = Inc.Patid;
quit;
*Summarize Events;
proc means data=_EPisodesWithEvents nway noprint;
var InGap EventDt;
class Patid IndexDt;
output out=_EPisodesWithEvents(drop=_:) sum(InGap)=NumEvents min(EventDt)=FEventDt/KEEPLEN;
run;
*First event in any episode (for T2_CohortDef ='03');
proc means data=_EPisodesWithEvents nway noprint;
var FEventDt;
class PatId;
output out=_FirstEventDtInEpisode(drop=_:) min(FEventDt)=FEventDt_Any/KEEPLEN;
run;
*FOR DEBUG OPTION;
%CopyFile(FileName=_PtsMasterList,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
*Add to _EPisodesWithEvents by Patid;
data _PtsMasterList;
merge _PtsMasterList
_FirstEventDtInEpisode;
by PatId;
run;
*FOR DEBUG OPTION;
%CopyFile(FileName=_PtsMasterList,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
data _PtsMasterList;
merge _PtsMasterList(in=a)
_EPisodesWithEvents(in=b);
by Patid IndexDt;
if a;
tte=Min(EpisodeEndDt,Enr_End,FEventDt,deathdt)-IndexDt -&BLACKOUTPER. +1;
if T2CohortDef ='01' then do;
if first.PatId;
end;
if T2CohortDef ='03' then do;
if FEventDt_Any and IndexDt > FEventDt_Any then delete;
end;
format group $30.;
Group="&ITGROUP.";
Type=input("&type.",best.);
year=year(Indexdt);
if &itt.=1 then EpisodeType="ITT";
else EpisodeType="EPI";
/*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, deathdt);
/*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;
/*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;
else if not missing(maxepisdur) and EpisodeEndDt_Censor = IndexDt + maxepisdur -1 then cens_episend = 1;
else cens_episend = 0;
/*Adjust enr_end for death (for denoms)*/
if EnrEpisodeCensoredAtDeath and DeathDt <= Enr_End then Enr_End=DeathDt;
format EpisodeEndDt_Censor OrigEpisEndDt date9.;
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;
run;
%end; *type=2;
%if %eval(&type=1) %then %do;
data _PtsMasterList;
set _PtsMasterList;
by Patid IndexDt;
if T1CohortDef ='01' then do;
if first.PatId;
end;
format group $30.;
Group="&ITGROUP.";
year=year(Indexdt);
Type=input("&type.",best.);
NumEvents=.;
FEventDt=.;
tte=.;
blackoutper=.;
EpisodeType=" ";
keep Group Type PatId Enr_start Enr_End IndexDt EpisodeEndDt
NumEvents FEventDt tte year EpisodeType blackoutper indexlook RxAmt RxSup NumDispensing;
run;
%end; *type=1;
%if %eval(&type=3) %then %do;
*find all events in Ctrl window;
proc sql noprint;
create table _CtrlEventsInWin as
select distinct Epi.PatId,
Epi.IndexDt,
Epi.LookBackLength,
1 as CtrlEventInWin,
min(max(T3CtrlFromDt,Ctrl.ADate)) as CtrlEventDt format=date9. /*Adate is either before or in the interval*/
from _PtsMasterList as Epi,
_FUPEvent(keep=PatId Adate Expiredt) as Ctrl
where Epi.Patid = Ctrl.Patid and
%MS_PeriodsOverlap(period1=Epi.T3CtrlFromDt Epi.T3CtrlToDt,
period2=Ctrl.ADate Ctrl.ExpireDt)
group by Epi.PatId,
Epi.IndexDt
order by Epi.Patid,
Epi.indexDt;
quit;
*find all events with prior events in LookBackLengthWin;
proc sql noprint;
create table _CtrlEventsInLookBackWin as
select distinct Epi.PatId,
Epi.IndexDt,
1 as CtrlEventInLookBackWin
from _CtrlEventsInWin as Epi,
_FUPEvent(keep=PatId Adate Expiredt) as Ctrl
where Epi.Patid = Ctrl.Patid and
%MS_PeriodsOverlap(period1=Epi.CtrlEventDt-LookBackLength Epi.CtrlEventDt-1,
period2=Ctrl.ADate Ctrl.ExpireDt)
order by Epi.Patid,
Epi.indexDt;
quit;
*find all events with prior WASH events in LookBackLengthWin;
proc sql noprint;
create table _CtrlWashEventsInLBWin as
select distinct Epi.PatId,
Epi.IndexDt,
1 as CtrlWashEventInLookBackWin
from _CtrlEventsInWin as Epi,
_FUPWash(keep=PatId Adate Expiredt) as Ctrl
where Epi.Patid = Ctrl.Patid and
%MS_PeriodsOverlap(period1=Epi.CtrlEventDt-LookBackLength Epi.CtrlEventDt-1,
period2=Ctrl.ADate Ctrl.ExpireDt)
order by Epi.Patid,
Epi.indexDt;
quit;
*find all events in Risk window;
proc sql noprint;
create table _RiskEventsInWin as
select distinct Epi.PatId,
Epi.IndexDt,
Epi.LookBackLength,
1 as RiskEventInWin,
min(max(T3RiskFromDt,Risk.ADate)) as RiskEventDt format=date9. /*Adate is either before or in the interval*/
from _PtsMasterList as Epi,
_FUPEvent(keep=PatId Adate Expiredt) as Risk
where Epi.Patid = Risk.Patid and
%MS_PeriodsOverlap(period1=Epi.T3RiskFromDt Epi.T3RiskToDt,
period2=Risk.ADate Risk.ExpireDt)
group by Epi.PatId,
Epi.IndexDt
order by Epi.Patid,
Epi.indexDt;
quit;
*find all events with prior events in LookBackLengthWin;
proc sql noprint;
create table _RiskEventsInLookBackWin as
select distinct Epi.PatId,
Epi.IndexDt,
1 as RiskEventInLookBackWin
from _RiskEventsInWin as Epi,
_FUPEvent(keep=PatId Adate Expiredt) as Risk
where Epi.Patid = Risk.Patid and
%MS_PeriodsOverlap(period1=Epi.RiskEventDt-LookBackLength Epi.RiskEventDt-1,
period2=Risk.ADate Risk.ExpireDt)
order by Epi.Patid,
Epi.indexDt;
quit;
*find all events with prior WASH events in LookBackLengthWin;
proc sql noprint;
create table _RiskWashEventsInLBWin as
select distinct Epi.PatId,
Epi.IndexDt,
1 as RiskWashEventInLookBackWin
from _RiskEventsInWin as Epi,
_FUPWash(keep=PatId Adate Expiredt) as Risk
where Epi.Patid = Risk.Patid and
%MS_PeriodsOverlap(period1=Epi.RiskEventDt-LookBackLength Epi.RiskEventDt-1,
period2=Risk.ADate Risk.ExpireDt)
order by Epi.Patid,
Epi.indexDt;
quit;
*keeping only valid events (event in only one window);
data _Events
_EventsBothWin;
merge _CtrlEventsInWin
_CtrlEventsInLookBackWin
_CtrlWashEventsInLBWin
_RiskEventsInWin
_RiskEventsInLookBackWin
_RiskWashEventsInLBWin;
by PatId IndexDt;
*Patients need to have at least one event in one of the windows;
*FALSE condition added on purpose in case we want to reactivate in the future;
if CtrlEventInWin and RiskEventInWin and 0 then do;
WinOfEvent="BOTH";
output _EventsBothWin;
end;
else do;
format FEventDt date9.;
FEventDt=min(RiskEventDt,CtrlEventDt);
if (CtrlEventInWin and not CtrlEventInLookBackWin and not CtrlWashEventInLookBackWin) then do;
WinOfEvent="CTRL";
output _Events;
end;
else if (RiskEventInWin and not RiskEventInLookBackWin and not RiskWashEventInLookBackWin) then do;
WinOfEvent="RISK";
output _Events;
end;
end;
keep PatId IndexDt FEventDt WinOfEvent;
run;
*remove true duplicates;
proc sort nodupkey data=_Events;
by PatId IndexDt FEventDt WinOfEvent;
run;
proc sort nodupkey data=_eventsBothWin;
by PatId IndexDt FEventDt WinOfEvent;
run;
*FOR DEBUG OPTION;
%CopyFile(FileName=_PtsMasterList,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
data _PtsMasterList_Censor;
set _PtsMasterList;
run;
*Keep only Valid IndexDt in Master;
data _PtsMasterList;
merge _PtsMasterList(in=a)
_Events(in=b);
by PatId IndexDt;
if a;
if first.patid then do;
Pt_Analysis_Episode_Num=0;
Pt_Exposure_Episode_Num=0;
end;
Pt_Exposure_Episode_Num=Pt_Exposure_Episode_Num+1;
format group $30.;
Group="&ITGROUP.";
Type=input("&type.",best.);
year=year(Indexdt);
if &ExpPeriodStartDt. <= indexdt <= &ExpPeriodEndDt.;
*01: Cohort includes only the first exposure during the query period;
if T3CohortDef ='01' then do;
if first.PatId;
end;
CensorDeath=0;
CensorEnrol=0;
if MaxEligtDt > ENR_END then do;
if EnrEpisodeCensoredAtDeath then CensorDeath=1;
else CensorEnrol=1;
end;
*Analysis cohort should be turned off if censoring applies;
*computing days at risk;
AnalysisCohort=0;
if FEventDt ne . /* and CensorDeath=0 and CensorEnrol=0 */ and MinEnrolHOIMet=1
and MaxEligtEnrAftDt <= ENR_END then do;
DAYS_FROM_EXPOS_TO_EVENT=FEventDt-IndexDt;
if WinOfEvent="CTRL" then DAYS_FROM_EXPOS_TO_CTRL_EVENT=DAYS_FROM_EXPOS_TO_EVENT;
if WinOfEvent="RISK" then DAYS_FROM_EXPOS_TO_RISK_EVENT=DAYS_FROM_EXPOS_TO_EVENT;
AnalysisCohort=1;
Pt_Analysis_Episode_Num=Pt_Analysis_Episode_Num+1;
end;
if AnalysisCohort=0 then do;
FEventDt=.;
WinOfEvent="";
end;
POSTENR_EXPOSURE=Enr_End-IndexDt;
retain Pt_Analysis_Episode_Num Pt_Exposure_Episode_Num;
keep Group Type PatId Enr_start Enr_End IndexDt FEventDt DAYS_FROM: year WinOfEvent MinEnrolHOIMet
age BIRTH_DATE SEX RACE AgeGroup Censor: AnalysisCohort Pt_: POSTENR_EXPOSURE;
run;
*Create a master file for Censor data;
data _PtsMasterList_Censor;
merge _PtsMasterList_Censor(in=a)
_Events(in=b);
by PatId IndexDt;
if a;
if first.patid then do;
Pt_Analysis_Episode_Num=0;
Pt_Exposure_Episode_Num=0;
end;
Pt_Exposure_Episode_Num=Pt_Exposure_Episode_Num+1;
format group $30.;
Group="&ITGROUP.";
Type=input("&type.",best.);
year=year(Indexdt);
if &ExpPeriodStartDt. <= indexdt <= &ExpPeriodEndDt.;
*01: Cohort includes only the first exposure during the query period;
if T3CohortDef ='01' then do;
if first.PatId;
end;
TTC=.;
CensorDeath=0;
CensorEnrol=0;
if MaxEligtDt > ENR_END then do;
if EnrEpisodeCensoredAtDeath then CensorDeath=1;
else CensorEnrol=1;
TTC=ENR_END-Indexdt;
end;
*Analysis cohort should be turned off if censoring applies;
*computing days at risk;
AnalysisCohort=0;
if FEventDt ne . and MinEnrolHOIMet=1
and MaxEligtEnrAftDt <= ENR_END then do;
DAYS_FROM_EXPOS_TO_EVENT=FEventDt-IndexDt;
if WinOfEvent="CTRL" then DAYS_FROM_EXPOS_TO_CTRL_EVENT=DAYS_FROM_EXPOS_TO_EVENT;
if WinOfEvent="RISK" then DAYS_FROM_EXPOS_TO_RISK_EVENT=DAYS_FROM_EXPOS_TO_EVENT;
AnalysisCohort=1;
Pt_Analysis_Episode_Num=Pt_Analysis_Episode_Num+1;
end;
if AnalysisCohort=0 then do;
FEventDt=.;
WinOfEvent="";
end;
POSTENR_EXPOSURE=Enr_End-IndexDt;
retain Pt_Analysis_Episode_Num Pt_Exposure_Episode_Num;
keep Group Type PatId Enr_start Enr_End IndexDt FEventDt DAYS_FROM: year WinOfEvent MinEnrolHOIMet
TTC age BIRTH_DATE SEX RACE AgeGroup Censor: AnalysisCohort Pt_: POSTENR_EXPOSURE;
run;
*Delete patients with multiple NDC on the incident exposure date;
%if %str(&T3ExclOnSameDay)=%str(Y) %then %do;
*FOR DEBUG OPTION;
%CopyFile(FileName=_PtsMasterList,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
data _PtsMasterList
_SameDayDeleted;
merge _PtsMasterList(in=a)
_SameDayNDCs(in=b keep=PatId Rxdate rename=Rxdate=indexDt);
by PatId IndexDt;
if a;
if b then output _SameDayDeleted;
else output _PtsMasterList;
run;
data _PtsMasterList_Censor;
merge _PtsMasterList_Censor(in=a)
_SameDayNDCs(in=b keep=PatId Rxdate rename=Rxdate=indexDt);
by PatId IndexDt;
if a and not b then output _PtsMasterList;
run;
%end;
%else %do;
data _SameDayDeleted;
set _PtsMasterList(keep=PatId IndexDt);
if _N_=0 then output;
run;
%end;*end type=3;
%end;
%if %eval(&type=1 | &type=2) %then %do;
*FOR DEBUG OPTION;
%CopyFile(FileName=_GroupUtilFile,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
proc datasets library=work nolist nowarn;
delete _GroupUtilFile;
quit;
* Because Type 1 analysis does not create episodes, the same index date can exist within a group
if dispensings with different subgroups have the same RXDate. Because episodes are not created, overlapping
can also occurs between claims that belong to different subgroups. In this case the ms_shaveoutside macro
can create duplicates and is not reliable. In Type 1, because each observation in _PtsMasterList represents
a single claim, we can adjust RxSup and RxAmt using this dataset instead of using _GroupIndex;
%if %eval (&type. eq 1) %then %do;
data _GroupUtilFile;
set _PtsMasterList;
TotRxSup=EpisodeEndDt-IndexDt+1;
TotRxAmt=RxAmt*TotRxSup/RxSup;
NumDisp=NumDispensing;
keep PatId IndexDt NumDisp TotRxSup TotRxAmt;
run;
%end;
%else %do;
*Summarize utilization overlapping only FINAL episodes;
%ms_shaveoutside(reffile=_PtsMasterList,
refstart=IndexDt,
refend=EpisodeEndDt,
KeepPartBf=Y,
ToShaveFile=_GroupIndex,
ToShaveStart=Adate,
ToShaveEnd=ExpireDt,
outfile=_GroupUtilFile);
Proc means data=_GroupUtilFile nway noprint;
var RxSup RxAmt;
class PatId IndexDt;
output out=_GroupUtilFile(rename=_freq_=NumDisp drop=_Type_) sum(RxSup RxAmt)=TotRxSup TotRxAmt;
run;
%end;
proc sort nodupkey data=_agegroups;
by PatId IndexDt;
run;
*FOR DEBUG OPTION;
%CopyFile(FileName=_PtsMasterList,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
data _PtsMasterList;
merge _PtsMasterList (in=a)
_GroupUtilFile (in=b)
_AgeGroups;
by PatId IndexDt;
if a or b;*defensive;
run;
* This will be applied later for Surveillance analysis;
data _PtsMasterList;
set _PtsMasterList;
LastLookFollowed=0;
format LastLookFollowedDt date9.;
LastLookFollowedDt=.;
format EndFollowUpType $20.;
EndFollowUpType="";
run;
*Remove all labels;
proc datasets lib=work memtype=data nolist nowarn;
modify _PtsMasterList;
attrib _all_ label=' ';
run;
%end; *types 1 and 2;
*TODO: reuse some code above to manage HOI in future development;
%if %eval(&type=4) %then %do;
data _PtsMasterList (drop=lEpisodeEndDt eligdays episode expiredt minenrolmet postterm PrePostPriority preterm priority);
set _PtsMasterList;
by Patid IndexDt;
* If there are overlapping episodes, truncate the episode and set the start date
at the day following the end date of the preceding episode;
lEpisodeEndDt = lag(EpisodeEndDt);
if first.PatId then lEpisodeEndDt = .;
if EpisodeStartDt <= lEpisodeEndDt then do;
EpisodeStartDt = lEpisodeEndDt + 1;
IndexDt=EpisodeStartDt;
end;
DelYear=Year(EpisodeEndDt);
/* This functionality is deferred to a future version of type 4 analysis
if T4CohortDef ='01' then do;
if first.PatId;
end;
*/
format group $30.;
Group="&ITGROUP.";
run;
proc sort data=_Deliveries;
by Patid EpisodeStartDt;
run;
* If there are overlapping episodes, truncate the episode and set the start date
at the day following the end date of the preceding episode;
data _Deliveries;
set _Deliveries;
by Patid EpisodeStartDt;
lEpisodeEndDt = lag(EpisodeEndDt);
if first.PatId then lEpisodeEndDt = .;
if EpisodeStartDt <= lEpisodeEndDt then EpisodeStartDt = lEpisodeEndDt + 1;
drop lEpisodeEndDt;
run;
* Make sure the it is the correct episode start date that is in _PtsMasterList;
proc sort data=_PtsMasterList;
by PatId DelNum;
run;
proc sort data=_Deliveries;
by PatId DelNum;
run;
data _PtsMasterList;
merge _PtsMasterList(in=a drop=EpisodeStartDt)
_Deliveries (in=b keep=PatId DelNum EpisodeStartDt);
by Patid DelNum;
if a;
IndexDt=EpisodeStartDt;
Duration=EpisodeEndDt - EpisodeStartDt;
Has3Trim=0;
if Duration>=181 then Has3Trim=1;
run;
proc sort data=_PtsMasterList;
by patid ADate;
run;
proc sort data=_GroupStartCodes;
by patid ADate;
run;
data _PrePostCodes;
merge _PtsMasterList(in=a keep=patid adate delnum)
_GroupStartCodes;
by PatId ADate;
if a;
if not missing(PreTerm) and not missing(PostTerm);
*Delete PostTerm codes that occured in an episode where PreTerm codes were also found;
if HadPostTerm=0 and PostTerm=1 then delete;
keep PatId Group PreTerm PostTerm Code DelNum;
run;
* Create the control group (match treated patients);
%ms_CreateControlGroup();
data _ControlGroup;
set _ControlGroup;
format group $30.;
Group="&ITGROUP.";
DelNum=_N_; * To make sure patients used more than once in the matching process are considered distinct deliveries;
run;
* Keep only the secondary cohort episodes of members who are in the primary cohort (to avoid unnecessary big file);
proc sql noprint;
create table _UniquePtsList as
select distinct(PatId) from _PtsMasterList
union
select distinct(PatId) from _ControlGroup;
quit;
data _PtsMasterList2;
merge _UniquePtsList(in=a)
_PtsMasterList2 (in=b);
by Patid;
if a and b;
run;
proc sort nodupkey data=_PtsMasterList2;
by _ALL_;
run;
%end; *type 4;
%if ("&SURVEILLANCEMODE."="f" or "&SURVEILLANCEMODE."="p") and %eval(&Type. = 2) %then %do;
%if ("&SURVEILLANCEMODE."="f") %then %do;
data _PtsMasterList;
set _PtsMasterList;
format EndFollowUpType $20.;
if LastLookFollowed=0 then do; *apply only to eligible patients;
if min(Feventdt,EpisodeEndDt) < &enddate. then do;
LastLookFollowedDt = &enddate.;
LastLookFollowed = &PERIODIDSTART.;
EndFollowUpType="EpisodeEndBFEnddate";
end;
else if Feventdt = &enddate. then do;
LastLookFollowedDt = &enddate.;
LastLookFollowed = &PERIODIDSTART.;
EndFollowUpType="EventOnEnddate";
end;
end;
run;
%end;
* Prior data existence for Surveillance;
%ISDATA(dataset=DPLPRIOR.&RUNID._mstr);
%IF %EVAL(&NOBS.>0) and %EVAL(&PERIODIDSTART. > 1) %THEN %DO;
%ms_surveillance();
%END;
%end;
*Store patient episodes to DPLocal;
%IF %EVAL(&group.=1) %THEN %DO;
data DPLocal.&RUNID._mstr;
set _PtsMasterList;
run;
%if %eval(&type eq 3) %then %do;
data DPLocal.&RUNID._mstr_Censor;
set _PtsMasterList_Censor;
run;
%end;
%if %eval(&type eq 4) %then %do;
data DPLocal.&RUNID._sec;
set _PtsMasterList2;
run;
data DPLocal.&RUNID._ctrl;
set _ControlGroup;
run;
data DPLocal.&RUNID._prePostCodes;
set _prePostCodes;
run;
data DPLocal.&RUNID._allDeliveries;
set _Deliveries;
run;
%end;
%END;
%ELSE %DO;
*because of surveillance mode and the carried forward variables from look <&PERIODIDSTART. that have not been
calculated yet for the (&PERIODIDSTART.<=look<=&PERIODIDEND.)) - we will use a set method rather than append;
data DPLocal.&RUNID._mstr;
set DPLocal.&RUNID._mstr
_PtsMasterList;
run;
%if %eval(&type eq 3) %then %do;
proc append base=DPLocal.&RUNID._mstr_Censor
data=_PtsMasterList_Censor force;
run;
%end;
%if %eval(&type eq 4) %then %do;
proc append base=DPLocal.&RUNID._sec
data=_PtsMasterList2 force;
run;
proc append base=DPLocal.&RUNID._ctrl
data=_ControlGroup force;
run;
proc append base=DPLocal.&RUNID._prePostCodes
data=_prePostCodes force;
run;
proc append base=DPLocal.&RUNID._allDeliveries
data=_Deliveries force;
run;
%end;
%END;
%if %eval(&type < 4) %then %do;
*Compute Attrition for this group;
%ms_attrition();
%if %eval(&type<3) %then %do;
*Compute Denominators;
%ms_cidadenom();
%end;
%end;
*FOR DEBUG OPTION;
%CopyAllFiles(prefix=_,suffix=_&Group.,tmp_libref=&RUNID.&GroupNum.);
proc datasets library=work nowarn nolist;
delete _:;
quit;
* Reset loop macro variables;
%let EXCL=;
%let INCL=;
%let CHARTRES=;
%put LAST GROUP RAN WAS &ITGROUP. &group.;
%end; /*group loop*/
%mend forloop;
%forloop;
proc sort data=DPLocal.&RUNID._mstr;
by Group PatId IndexDt;
run;
/************************/
/* Freeze Patients Data */
/************************/
%macro Wrapper;
%global DataLib;
%let DataLib=indata.;
%IF %UPCASE(%str("&FREEZEDATA."))=%str("Y") %THEN %DO;
%if %eval(&type < 4) %then %do;
proc sort nodupkey data=DPLocal.&RUNID._mstr out=_PtsList(keep=Patid);
by Patid;
run;
%end;
%else %do;
data _PtsList;
set DPLocal.&RUNID._allDeliveries (keep=Patid)
DPLocal.&RUNID._ctrl (keep=Patid);
run;
proc sort nodupkey data=_PtsList;
by Patid;
run;
%end;
%isdata(dataset=_PtsList);
%if %eval(&nobs.>0) %then %do;
%IF %STR(&ENRTABLE.) ne %STR() or %LENGTH(&ENRTABLE.) > 0. %THEN %DO;%MS_FREEZEDATA(INFILE=indata.&ENRTABLE, PATLISTINFILE=_PtsList,OUTFILE=DPLocal.&RUNID._&ENRTABLE.);%END;
%IF %STR(&DEMTABLE.) ne %STR() or %LENGTH(&DEMTABLE.) > 0. %THEN %DO;%MS_FREEZEDATA(INFILE=indata.&DEMTABLE, PATLISTINFILE=_PtsList,OUTFILE=DPLocal.&RUNID._&DEMTABLE.);%END;
%IF %STR(&DISTABLE.) ne %STR() or %LENGTH(&DISTABLE.) > 0. %THEN %DO;%MS_FREEZEDATA(INFILE=indata.&DISTABLE, PATLISTINFILE=_PtsList,OUTFILE=DPLocal.&RUNID._&DISTABLE.);%END;
%IF %STR(&DIATABLE.) ne %STR() or %LENGTH(&DIATABLE.) > 0. %THEN %DO;%MS_FREEZEDATA(INFILE=indata.&DIATABLE, PATLISTINFILE=_PtsList,OUTFILE=DPLocal.&RUNID._&DIATABLE.);%END;
%IF %STR(&PROCTABLE.) ne %STR() or %LENGTH(&PROCTABLE.) > 0. %THEN %DO;%MS_FREEZEDATA(INFILE=indata.&PROCTABLE,PATLISTINFILE=_PtsList,OUTFILE=DPLocal.&RUNID._&PROCTABLE.);%END;
%IF %STR(&ENCTABLE.) ne %STR() or %LENGTH(&ENCTABLE.) > 0. %THEN %DO;%MS_FREEZEDATA(INFILE=indata.&ENCTABLE, PATLISTINFILE=_PtsList,OUTFILE=DPLocal.&RUNID._&ENCTABLE.);%END;
%IF %STR(&LABTABLE.) ne %STR() or %LENGTH(&LABTABLE.) > 0. %THEN %DO;%MS_FREEZEDATA(INFILE=indata.&LABTABLE, PATLISTINFILE=_PtsList,OUTFILE=DPLocal.&RUNID._&LABTABLE.);%END;
%IF %STR(&VITTABLE.) ne %STR() or %LENGTH(&VITTABLE.) > 0. %THEN %DO;%MS_FREEZEDATA(INFILE=indata.&VITTABLE, PATLISTINFILE=_PtsList,OUTFILE=DPLocal.&RUNID._&VITTABLE.);%END;
%IF %STR(&DEATHTABLE.) ne %STR() or %LENGTH(&DEATHTABLE.) > 0. %THEN %DO;%MS_FREEZEDATA(INFILE=indata.&DEATHTABLE, PATLISTINFILE=_PtsList,OUTFILE=DPLocal.&RUNID._&DEATHTABLE.);%END;
*This datalib will point to the subset of PatId data gererated by Freeze data;
%let DataLib=DPlocal.&RUNID._;
%end;
%END;
%mend;
%wrapper;
%if %eval(&type. < 3) %then %do;
*Execute Covariates macro;
%ms_CIDAcov();
*Create Analysis Tables to output to MSOC;
%ms_cidatables();
*delete undocumented vars from MSTR;
data dplocal.&RUNID._mstr;
set dplocal.&RUNID._mstr;
drop EndFollowUpType blackoutper birth_date;
run;
/*output censoring vars*/
%if &censor_output. = 1 %then %do;
%ISDATA(dataset=dplocal.&runid._mstr);
%IF %EVAL(&NOBS.>=1) %THEN %DO;
proc sql noprint;
create table msoc.&runid._censor_cida as
select distinct group
, tte as censdays_value
, event_flag
, count(*) as episodes
, sum(cens_elig) as cens_elig
, sum(cens_dth) as cens_dth
, sum(cens_dpend) as cens_dpend
, sum(cens_qryend) as cens_qryend
, sum(cens_episend) as cens_episend
, sum(cens_spec) as cens_spec
, sum(cens_event) as cens_event
from dplocal.&runid._mstr
group by group, censdays_value, event_flag
order by group, censdays_value, event_flag;
quit;
%end;
%end;
%end;
%else %do;
%if %eval(&type. eq 3) %then %do;
%ms_cidatablest3();
%end;
%else %do;
%ms_cidatablest4();
%end;
%end;
%if "&SURVEILLANCEMODE."="f" or "&SURVEILLANCEMODE."="p" and %eval(&Type. = 2) %then %do;
data MSOC.&RUNID._t2_cida;
set MSOC.&RUNID._t2_cida;
DenNumPts=.;
DenNumMemDays=.;
run;
%end;
*FOR DEBUG OPTION;
%let GroupNum=0;
%CopyAllFiles(prefix=,suffix=,tmp_libref=&RUNID.&GroupNum.);
proc datasets library=work nowarn nolist;
delete &COHORTCODES. DRUGS Drugscomb ENR_MD30 LABEXTRACT DeathExtract MEDS &MONITORINGFILE. &Inclusioncodes. Enr_:;
quit;
%ms_stoptimer(START=&MAINSTART.);
* Reset global macro variables;
%let COMBORUN=;
*Creating the number of groups to create cohorts (into NOBS);
%isdata(dataset=infolder.&COHORTFILE.);
data _null_;
if "&PTSTOEXCLUDE." ne "" then call symputx("PatidExcl","Yes");
else call symputx("PatidExcl","No");
call symputx("seconds2",put(int(&seconds.-&hours.*3600-&minutes.*60),best.));
run;
*Signature;
data signature;
DPID="&DPID.";
SITEID="&SITEID.";
MSReqID="&MSREQID.";
MSProjID="&MSPROJID.";
MSWPType="&MSWPTYPE.";
MSWPID="&MSWPID.";
MSDPID="&MSDPID.";
MSVerID="&MSVERID.";
RunID="&RUNID.";
MPNum="QRP";
MPVer="&Ver.";
format StartTime StopTime datetime21.2;
StartTime=input("&MAINSTART.",best.);
StopTime=input("&STOP.",best.);
format Seconds $20.;
Seconds=put(int(&stop.-&MAINSTART.),best.)||" s";
ExecutionTime="&hours. h &minutes. m &seconds. s";
ScenarioCnt=strip("&NOBS.");
AGESTRAT="&AGESTRAT.";
PERIODIDSTART="&PERIODIDSTART.";
PERIODIDEND="&PERIODIDEND.";
ANALYSIS="&ANALYSIS.";
MONITORINGFILE="&MONITORINGFILE.";
T3METADATA="&T3METADATA.";
COHORTFILE="&COHORTFILE.";
TYPE1FILE="&TYPE1FILE.";
TYPE2FILE="&TYPE2FILE.";
TYPE3FILE="&TYPE3FILE.";
TYPE4FILE="&TYPE4FILE.";
COHORTCODES="&COHORTCODES.";
INCLUSIONCODES="&INCLUSIONCODES.";
COVARIATECODES="&COVARIATECODES.";
STOCKPILINGFILE="&STOCKPILINGFILE.";
UTILFILE="&UTILFILE.";
COMBOFILE="&COMBOFILE.";
COMORBFILE="&COMORBFILE.";
DRUGCLASSFILE="&DRUGCLASSFILE.";
FREEZEDATA="&FREEZEDATA.";
LABSCODEMAP="&LABSCODEMAP.";
PREGCODES="&PREGCODES.";
MOICODES="&MOICODES.";
ZIPFILE="&ZIPFILE.";
PATIDEXCL="&PATIDEXCL.";
ETLNUMBER="&ETL.";
format DPMINDATE DPMAXDATE date9.;
DPMINDATE=&DP_MinDate.;
DPMAXDATE=&DP_MaxDate.;
output;
run;
proc transpose data=signature out=msoc.&RUNID._signature(rename=_NAME_=Var rename=COL1=VALUE);
var _ALL_;
run;
%put NOTE: ********END OF MACRO: ms_cidanum v1.21********;
%mend ms_cidanum;