Base file: K:\TIDE Projects\FDA_Sentinel\07. Projects and Task Orders\01. Modular Programs\QRP\Distributed\QRP_3.3.3\inputfiles\macros\ms_attrition.sas

Compared file: K:\Sentinel\requests\mpl2\cder_mpl2p_wp001\packages\cder_mpl2p_wp001_nsdp_v01\WithRadar\inputfiles\macros\ms_attrition.sas

Generated by CSDiff on 1/24/2017 12:34 PM  

 
****************************************************************************************************
*                                           PROGRAM OVERVIEW
****************************************************************************************************
*
* PROGRAM: ms_attrition.sas  
*
* Created (mm/dd/yyyy): 12/19/2014
* Last modified: 08/15/2016
* Version: 1.8
*
*--------------------------------------------------------------------------------------------------
* PURPOSE:
*   This macro will create the attrition table.                                        
*   
*  Program inputs:                                                                                   
*   -Many datasets created by the ms_cidanum macro
* 
*  Program outputs:                                                                                                                                       
*   - The MSOC.&RUNID._Attrition output table
* 
*  PARAMETERS:                                                                       
*            
*  Programming Notes:                                                                                
*   -Unlike ms_cidanum that reports episode level data, this macro reports member level data                                                                            
*
*
*--------------------------------------------------------------------------------------------------
* CONTACT INFO: 
*  Mini-Sentinel Coordinating Center
*  info@mini-sentinel.org
*
*--------------------------------------------------------------------------------------------------
*  CHANGE LOG: 
*
*   Version   Date       Initials      Comment (reference external documentation when available)
*   -------   --------   --------   ---------------------------------------------------------------
*     1.1     02/02/15   SL(DM)     Corrected when age range span is shorter than the
*                                     StartFollowup and EndDate span.
*
*     1.2     05/07/15    DM        Added members lost to follow-up and still at risk in 
*                                   surveillance mode
*
*     1.3     06/26/15    DM        Fixed days at risk post blackout when Analysis = PS or ADS
*                                   Removed surveillance analysis related code 
*
*     1.4     01/15/16    VC        Remove CovariateWindow and replace with MinCovFrom MaxCovTo
*                                   Removed surveillance analysis related code 
*
*     1.5     04/21/16    DM        Added HDPS window parameters in attrition algorithm
*
*     1.6     08/10/16    AP(SOC)   Reordered AgeGroupMet rows (QCI-180)
*
*     1.7     08/15/16    DM        Re-Added surveillance analysis related code
*
*     1.8     01/11/17    AP        Fixed bug (QRP-296)
*
***************************************************************************************************;


%macro ms_attrition();

%put =====> MACRO CALLED: ms_attrition v1.8;

    /******************/
    /* Utility macros */
    /******************/

    %macro attrition(file=,ToExcl=);

    proc sql noprint;
    select count(distinct PatId) into: NumToExcl
    from &file.
    where not (&ToExcl.);
    quit;
    %put &NumToExcl.;
    data &file.;
    set &file.;
    where not (&ToExcl.);
    run;

    %ISDATA(dataset=_Attrition);
    %IF %EVAL(&NOBS.>=1) %THEN %DO;
        data _Attrition;
        set _Attrition end=eof;
        output;
        if eof then do;
            Level=input("&level.",best.);
            Num=input("&NumToExcl.",best.);
            call symputx("level",&level.+1);
            output;
        end;
        run;
    %END;
    %ELSE %DO;
        data _Attrition;
        format level best. num comma10.;
        Level=input("&level.",best.);
        Num=input("&NumToExcl.",best10.);
        call symputx("level",&level.+1);
        output;
        run;
    %END;
    %put %eval(&level.-1) &NumToExcl.;
    %mend; 

/***************************************************************/
/* Prepare Raw Eligibility-type data for waterfall (all types) */
/***************************************************************/

    *keep last look;
    %if %eval(&type=3) %then %do; 
        data _look;
        format startfollowup enddate date9.;
        startfollowup=&ExpPeriodStartDt.;
        enddate=&ExpPeriodEndDt.;
        call symputx("startfollowup",startfollowup);
        call symputx("enddate",enddate);
        keep startfollowup enddate;
        run;
    %end;
    %else %do;
        data _look;
        set &Monitoringfile. end=eof1;
        if eof1 then output;
        keep startfollowup enddate;
        run;
    %end;

    proc sql noprint;
    create table _enr(where=(InGap=1)) as
    select *,
           %MS_PeriodsOverlap(period1=enr.enr_start enr.enr_end,                                              
                              period2=look.startfollowup look.enddate) as InGap
    from indata.&EnrTable. as enr,
         _look as look
    order by enr.Patid, 
             enr.enr_start;
    quit;

    *Delete data from patients in DPs &PTSTOEXCLUDE. files; 
    %ms_delpatients(datafile=_enr,
                   ptsfile=&PTSTOEXCLUDE.,
                   Outfile=_enr);

    %IF %EVAL(&chartvar.=0) %THEN %DO;
        data _enr;
        set _enr;
        format chart $1.;
        chart="N";
        run;
    %END;

    *non-missing birth date/sex - mutliple demog entries (should be zero - defensive coding);
    data _Demogsmissing;
    set indata.&Demtable.;
    where missing(BIRTH_DATE) or missing(Sex);
    keep PatId;
    run;

    %ms_delpatients(datafile=_Demogsmissing,
                    ptsfile=&PTSTOEXCLUDE.,
                    Outfile=_Demogsmissing);

    proc sort nodupkey data=_Demogsmissing;
    by PatId;
    run;
    proc sort nodupkey data=indata.&Demtable.(keep=patId BIRTH_DATE Sex) out=_demogs 
                                                                         dupout=_MultiDemogs(keep=PatId);
    by PatId;
    run;
    proc sort nodupkey data=_MultiDemogs;
    by PatId;
    run;

    *shaving outside;
    data _enr;
    merge _enr(in=a)
          _demogs(in=b)
          _Demogsmissing(in=c)
          _MultiDemogs(in=d);
    by PatId;
    if a and b and not c and not d;
    if enr_start < &startfollowup. then enr_start = &startfollowup.;
    if enr_end > &enddate. then enr_end = &enddate.;
    if MEDCOV="Y" then meds=1;else  meds=0;
    if DRUGCOV="Y" then drugs=1;else drugs=0;
    if chart="Y" then charts=1;else charts=0;
    Both=0;
    if drugs=1 and meds=1 then both=1;
    run;

    *One record per patient;
    proc means data=_enr nway noprint;
    var meds drugs both charts;
    by Patid;
    id  BIRTH_DATE Sex startfollowup enddate;  *unique at the PatId level;
    output out=_enr(rename=_freq_=NumRec drop=_type_) sum=;
    run;
    
    *get Coverage for this group;
    data _coverage;
    set infolder.&cohortfile.;
    where group="&ITGROUP.";
    keep coverage CHARTRES;
    run;

    data _MedDrugCov(where=(female=1));
    set _enr;
    if _N_=1 then set _coverage;
    all=1;
    MedCovOnly=0;
    DrugCovOnly=0;
    DrugCovMixAlways=0;   
         if drugs=0 and meds=NumRec  then MedCovOnly=1;
    else if meds=0  and drugs=NumRec then DrugCovOnly=1;
    else if both=0 then DrugCovMixAlways=1;

    *trick for attrition table in the case where coverage = D or M;
    if coverage='D' then do;
        DrugCovOnly=0;  *never delete those;
        DrugCovMixAlways=0;  *never delete those;
    end;
    if coverage='M' then do;
        MedCovOnly=0;  *never delete those;
        DrugCovMixAlways=0;  *never delete those;
    end;

    MeetChartReq=0;
    if upcase(CHARTRES)='Y' then do;
        if charts=NumRec then  MeetChartReq=1;  *all eligibility span have charts availability;
    end;
    else MeetChartReq=1;

    if upcase(sex) = 'F' then female = 1;


    run;

    /** CALCULATE AGE and AGE STRATA SPECIFIED BY MSOC **/
    %ms_agestrat(infile=_MedDrugCov,
                 outfile=_keep1,
                 startdt=birth_date,
                 enddt=startfollowup,
                 timestrat=&agestrat.);

    %ms_agestrat(infile=_MedDrugCov,
                 outfile=_keep2,
                 startdt=birth_date,
                 enddt=enddate,
                 timestrat=&agestrat.);

    *Patient eligible age must overlap at least startfollowup - enddate;
    data _combine;
    set _keep1
        _keep2;
    keep PatId;
    if AgeGroup ne '' or %MS_PeriodsOverlap(period1=MinAgeDate MaxAgeDate,
                                            period2=startfollowup enddate);
    keep PatId AgeGroup;
    run;
    proc sort nodupkey data=_combine;
    by PatId;
    run;

    data _keep;
    merge _MedDrugCov(in=a)
          _combine(in=b);
    by PatId;
    if a;
    if b then AgeGroup="Yes";
    else AgeGroup="";
    run;

    *Reset cumulative;
    proc datasets library=work nowarn nolist;
    delete _attrition;
    quit;

    %global level;
    %let level=1;

    %attrition(file=_keep,ToExcl=all=0);
    %attrition(file=_keep,ToExcl=MedCovOnly=1);
    %attrition(file=_keep,ToExcl=DrugCovOnly=1);
    %attrition(file=_keep,ToExcl=DrugCovMixAlways=1);
    %attrition(file=_keep,ToExcl=AgeGroup='');
    %attrition(file=_keep,ToExcl=MeetChartReq=0);

    /*************************************************************************/
    /* Exclusion - Members must have at least one QUERYGROUP                 */
    /* claim within the query period;                                        */
    /*************************************************************************/
    proc sql noprint;
    create table _DistinctPatIds as
    select distinct Patid,
           1 as AtLeastOneClaim
    from _groupindex
    where &startfollowup. <= Adate  <= &enddate.
    order by PatId;
    quit;

    data _keep;
    merge _keep(in=a)
          _DistinctPatIds;
    by Patid;
    if a;
    if _N_=1 then set _Cohortdef;
    run;
    %attrition(file=_keep,ToExcl=AtLeastOneClaim=.);

/***************************************************************/
/* Prepare Episode/Claim-type data for waterfall (all types)   */
/* Copied/Modified from CIDAnum                                */
/***************************************************************/

    %if %eval(&type.=1) %then %do;
          data _EventsInFupWash _WashEventsInFupWash;
          set _POV1(obs=0  keep=PatId Adate  rename=Adate=IndexDt);
          run;
    %end;

    %if %eval(&type.<=2) %then %do;
        *Note that all dummies are created from the "keep" perspective;
        Data _MasterEpisode;
        Merge _POV1(in=a rename=Adate=IndexDt)
              _POV2(in=b rename=Adate=IndexDt drop=InGap)
              _POV3(in=c rename=Adate=IndexDt keep=patid Adate)
              _POV4(in=d rename=EpisodeStartDt=IndexDt)
              _IOT(in=e rename=EpisodeStartDt=IndexDt)
              _EventsInFupWash(in=f keep=PatId IndexDt)
              _WashEventsInFupWash(in=g keep=PatId IndexDt);
        by Patid IndexDt;
        
        if a and d;  *b and d later;

        if a then pov1=1;
        if b then pov2=1;
        if c then pov3=1;
        if d then pov4=1;
        if e then iot=1;

        *indexDate must overlap greater monitoring period;
        if &startfollowup. <= IndexDt <= &enddate.;
        *Trucate episode at OIT;
        if e and TrunkDate 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.) then MinEnrdays=1;
        if  Enr_Start <= IndexDt-max(0,Washper) then MinWashper=1;
        if  Enr_Start <= IndexDt-max(0,T2FupWashPer) then MinT2FupWashPer=1;
        if  Enr_Start <= IndexDt-max(0,&MinCovFrom.) then MinCovariatewindowMet=1;
        if  Enr_Start <= IndexDt-max(0,&PRIORDAYS_bf.,&L1HDPSFROM.) then MinINCLINDEX1=1;
        if  Enr_End   >= IndexDt+max(0,&PRIORDAYS_af.,&MaxCovTo.,&L1HDPSTO.) then MinINCLINDEX2=1;

        if MinINCLINDEX1 and MinINCLINDEX2 then MinINCLINDEX=1;

        *Minimum Episode Duration;
        if EpisodeEndDt-IndexDt + 1 >= minepisdur then MinEpiDurMet=1;  

        *Patient must be enrolled throughout the at-risk period when minepisdur;
        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 and event);
        if blackoutper<=0 then PostBlackoutper=1;
        else if "&Analysis."="ps" or "&Analysis."="ads" then do;             
             if blackoutper and min(EpisodeEndDt,IndexDtLookEndDt) - (IndexDt + blackoutper)  >= 0  then PostBlackoutper=1;
        end;
        else if blackoutper and EpisodeEndDt - (IndexDt + blackoutper) >= 0 then PostBlackoutper=1;

        if not f and not g then NoEventsInFUWAsh=1;

        if not pov2 then NoPov2Claims=1;

        MinDaySupMet=.;
        NoExclClaim=.; 
        SuffExclElig=.;
        HadInclClaim=.;

        label IndexDt="IndexDt";
        run;
    %end;
    %else %if %eval(&type.=3) %then %do;
                *Note that all dummies are created from the "keep" perspective;
        Data _MasterEpisode;
        Merge _POV1(in=a rename=Adate=IndexDt)
              _POV2(in=b rename=Adate=IndexDt drop=InGap)
              _POV3(in=c rename=Adate=IndexDt keep=patid Adate)
              _CtrlEventsInWin(in=d)
              _RiskEventsInWin(in=e)
              _Events(in=f)
              _EventsBothWin(in=e);
        by Patid IndexDt;
        
        if a;  *b later;

        if a then pov1=1;
        if b then pov2=1;
        if c then pov3=1;

        *indexDate must overlap greater monitoring period;
        if &startfollowup. <= IndexDt <= &enddate.;

        *Minimum look back enrollment duration;
        if  Enr_Start <= MinEligtHOIDt then MinLookBackLength=1;
        if  Enr_Start <= IndexDt-max(0,Washper) then MinWashper=1;
        if  Enr_Start <= IndexDt-max(0,&enrdays.) then MinEnrdays=1;

        *Minimum enrollment duration after exposure;
        if MaxEligtEnrAftDt <= Enr_End then MinEnrAft=1;

        if not pov2 then NoPov2Claims=1;

        if d or e then AtLeastOneEvent=1;

        if f then EventInOnlyOneWindow=1;

        NoExclClaim=.; 
        SuffExclElig=.;
        HadInclClaim=.;

        format temp date9.;
        temp=IndexDt-max(0,MinWindowDt - LookBackLength);
        temp2=max(0,MinWindowDt - LookBackLength);;
        label IndexDt="IndexDt";
        run;

        *Events;
        proc freq;tables MinLookBackLength MinWashper MinEnrdays;run;
    %end;

    *Calculate age at index date;
    proc sort nodupkey data=_enr out=_BDates(keep=PatId birth_date);
    by PatId;
    run;

    data _MasterEpisode;
    merge _MasterEpisode (in=a)
          _BDates;
    by PatId;
    if a;
    run;

    %ms_agestrat(infile=_MasterEpisode,
                 outfile=_MasterEpisode,
                 startdt=birth_date,
                 enddt=IndexDt,
                 timestrat=&agestrat.);

    data _MasterEpisode;
    set _MasterEpisode;
    if agegroup ne "" then AgeGroupMet=1;
    ExclCritMet=1;
    HadInclClaim=.;
    run;
    %put &type. &EXCL. &INCL.;

    %macro wrapper;
    %if %eval(&type.=2) %then %do;

        * NOTE: there is a need to recompute the TotRxSup for the mindaysupp criterion 
          before applying all other criterion as it is done in CIDANUM;

        *Summarize utilization that are overlapping selected episodes;
         %ms_shaveoutside(reffile=_MasterEpisode,
                          refstart=IndexDt,
                          refend=EpisodeEndDt,
                          KeepPartBf=Y,
                          ToShaveFile=_GroupIndex,
                          ToShaveStart=Adate,
                          ToShaveEnd=ExpireDt,
                          outfile=_GroupUtilFile);

        *Summarize RxSup during episode (only to apply mindaysupp criterion);
        Proc means data=_GroupUtilFile nway noprint;
        var RxSup RxAmt;
        class PatId IndexDt;
        output out=_GroupUtilFile(keep=PatId IndexDt TotRxSup) sum(RxSup)=TotRxSup;
        run;

        data _MasterEpisode;
        merge _MasterEpisode(in=a)
              _GroupUtilFile;
        by PatId IndexDt;
        if a;
        if TotRxSup >= mindaysupp then MinDaySupMet=1;
        if MinDaySupMet;
        run;

    %end;

    %IF %EVAL(&EXCL.=1) %THEN %DO;       
         data _MasterEpisode;
         merge _MasterEpisode(in=a)
               _POV3Excl1(in=b keep=PatId Adate rename=Adate=IndexDt)  /*had an exclusion claim*/
               _POV3Excl2(in=c keep=PatId Adate rename=Adate=IndexDt); /*had insufficient eligibility*/
         by PatId IndexDt;
             if a;
             NoExclClaim=0;
             SuffExclElig=0;

         if not b then NoExclClaim=1;
         if not c then SuffExclElig=1;

             ExclCritMet=.;
         if NoExclClaim=1 and SuffExclElig=1 then ExclCritMet=1;

         run;


    %end;

   %IF %EVAL(&INCL.=1) %THEN %DO;     
         data _MasterEpisode;
         merge _MasterEpisode(in=a)
               _POV3Incl(in=b keep=PatId Adate rename=Adate=IndexDt);  /*had an inclusion claim*/
         by PatId IndexDt;
         if a;
         if b then HadInclClaim=1;
         run;
    %end;
    %mend wrapper;
    %wrapper;

    %if %eval(&type.<=2) %then %do;
        *One record per patient per indexdt - summarizing the "To Keep dummies (an absence of which will trigger a deletion)";
        proc means data=_MasterEpisode noprint missing nway;
        var MinEnrdays MinWashper MinT2FupWashPer MinCovariatewindowMet MinINCLINDEX MinEpiDurMet FUEnrolMet 
            PostBlackoutper NoPov2Claims AgeGroupMet MinDaySupMet NoEventsInFUWAsh NoExclClaim SuffExclElig HadInclClaim ExclCritMet;
        class PatId IndexDt;
        output out=_MasterPatient(drop=_:) max(MinEnrdays MinWashper MinT2FupWashPer MinCovariatewindowMet MinINCLINDEX MinEpiDurMet FUEnrolMet 
                                               PostBlackoutper NoPov2Claims AgeGroupMet MinDaySupMet NoEventsInFUWAsh HadInclClaim ExclCritMet)=
                                           min(NoExclClaim SuffExclElig)=;
        run;
    %end;
    %else %do;
        *Same day deletion criterion;   
        data _MasterEpisode;
        merge _MasterEpisode(in=a)
              _samedaydeleted(in=b keep=PatID IndexDt);
        by PatID IndexDt;
        if a;
        SameDayDeletedMet=1;
        if b then SameDayDeletedMet=.;  
        run;        

        *One record per patient per indexdt - summarizing the "To Keep dummies (an absence of which will trigger a deletion)";
        proc means data=_MasterEpisode noprint missing nway;
        var NoPov2Claims AgeGroupMet 
            MinLookBackLength MinEnrAft MinWashper MinEnrdays 
            HadInclClaim ExclCritMet
            AtLeastOneEvent EventInOnlyOneWindow SameDayDeletedMet
            NoExclClaim SuffExclElig ;
        class PatId IndexDt;
        output out=_MasterPatient(drop=_:) max(MinEnrdays MinWashper MinLookBackLength MinEnrAft NoPov2Claims AgeGroupMet 
                                               HadInclClaim ExclCritMet AtLeastOneEvent EventInOnlyOneWindow SameDayDeletedMet)=
                                           min(NoExclClaim SuffExclElig)=;
        run;
    %end;

    *add dummies;
    data _keep;
    merge  _keep(in=a) 
           _MasterPatient;
    by PatId;
    if a;   
    run;

    %macro RunClaimToExcl;
        %attrition(file=_keep,ToExcl=AgeGroupMet=.);
        %attrition(file=_keep,ToExcl=NoPov2Claims=.);       

        %if %eval(&type<=2) %then %do; 
            %if %eval(&type=2) %then %attrition(file=_keep,ToExcl=MinDaySupMet=.);
            %if %eval(&type=2 & &itt.=0) %then %attrition(file=_keep,ToExcl=MinEpiDurMet=.);
            %if %eval(&type=2 & &itt.=1) %then %attrition(file=_keep,ToExcl=MinEpiDurMet=-1);  *-1 to delete none, but still increment level;
            %if %eval(&type=2) %then %attrition(file=_keep,ToExcl=PostBlackoutper=.);
            %attrition(file=_keep,ToExcl=MinEnrdays=.);
            %attrition(file=_keep,ToExcl=MinWashper=.);
            %if %eval(&type=2) %then %attrition(file=_keep,ToExcl=MinT2FupWashPer=.);
            %if %eval(&type=2) %then %attrition(file=_keep,ToExcl=NoEventsInFUWAsh=.);
            %put &EXCL. &INCL.;
            %IF %EVAL(&EXCL.=1) %THEN %attrition(file=_keep,ToExcl=ExclCritMet=. and SuffExclElig=0);
            %IF %EVAL(&EXCL.=0) %THEN %attrition(file=_keep,ToExcl=MinWashper=-1);  *MinWashper=-1 to delete none, but still increment level;
            %IF %EVAL(&EXCL.=1) %THEN %attrition(file=_keep,ToExcl=ExclCritMet=. and NoExclClaim=0);
            %IF %EVAL(&EXCL.=0) %THEN %attrition(file=_keep,ToExcl=MinWashper=-1);
            %IF %EVAL(&INCL.=1) %THEN %attrition(file=_keep,ToExcl=HadInclClaim=.);
            %IF %EVAL(&INCL.=0) %THEN %attrition(file=_keep,ToExcl=MinWashper=-1);
        %end;
        %else %do;

            %attrition(file=_keep,ToExcl=MinWashper=.);
            %attrition(file=_keep,ToExcl=MinEnrdays=.);

            %put &EXCL. &INCL.;
            %IF %EVAL(&EXCL.=1) %THEN %attrition(file=_keep,ToExcl=ExclCritMet=. and SuffExclElig=0);
            %IF %EVAL(&EXCL.=0) %THEN %attrition(file=_keep,ToExcl=MinWashper=-1);  *MinWashper=-1 to delete none, but still increment level;
            %IF %EVAL(&EXCL.=1) %THEN %attrition(file=_keep,ToExcl=ExclCritMet=. and NoExclClaim=0);
            %IF %EVAL(&EXCL.=0) %THEN %attrition(file=_keep,ToExcl=MinWashper=-1);
            %IF %EVAL(&INCL.=1) %THEN %attrition(file=_keep,ToExcl=HadInclClaim=.);
            %IF %EVAL(&INCL.=0) %THEN %attrition(file=_keep,ToExcl=MinWashper=-1);

*When cohortDef is 01, consider the first claim only for a member to assess the remaining criteria;
data _keep;
set _keep;
by PatId IndexDt;
if T3CohortDef ='01' then do;
    if first.PatId;
end;
run;

            %attrition(file=_keep,ToExcl=SameDayDeletedMet=.);

*Analytic cohort exclusion criteria;
            %attrition(file=_keep,ToExcl=MinLookBackLength=.);
            %attrition(file=_keep,ToExcl=MinEnrAft=.); 
            %attrition(file=_keep,ToExcl=AtLeastOneEvent=.);
            %attrition(file=_keep,ToExcl=EventInOnlyOneWindow=.);
        %end;
    %mend RunClaimToExcl;
    %RunClaimToExcl;

    %macro InformationCrit;
    %if %sysfunc(fileexist(_itdrugsexcl))=0 %then %do;

            *Create dummies for Stockpiled excluded records;
            data _StockInformation;
            merge _keep(in=a)
                  _itdrugsexcl;
            By PatId;
            if a;
            exclGroup=0;
            exclEvent=0;
            exclCond=0;            
            if upcase(T2_INDEX)="DEF" then exclGroup=1;
            if upcase(T2_FUP)="DEF" then exclEvent=1;
            if upcase(T2_INDEX) in('INC','EXC') then exclCond=1;
            run;

            *One record per patent;
            proc means data=_StockInformation noprint;
            var exclGroup exclEvent exclCond;
            by PatId;
            output out=_StockInformation(drop=_:) max=;
            run;
            
            data _keep_;
            set _StockInformation;
            run;
            %attrition(file=_keep_,ToExcl=exclGroup=0);
            data _keep_;
            set _StockInformation;
            run;
            %attrition(file=_keep_,ToExcl=exclEvent=0);
            data _keep_;
            set _StockInformation;
            run;
            %attrition(file=_keep_,ToExcl=exclCond=0);
    %end;
    %else %do;
            data _Attrition;
            set _Attrition end=eof;
            output;
            if eof then do;
                Num=.;
                do i=1 to 3;
                    Level=input("&level.",best.);output;
                    call symputx("level",&level.+1);
                end;
            end;
            drop i;
            run;
     %end;
    %mend;
    %InformationCrit;

/*********************************************************/
/* Calculate Exclusions variables and ADD extra variable */
/*********************************************************/

    %put &type.;

    data _Attrition;
    retain group level descr num Excluded;
    format group $30. descr $500.;
    set _Attrition;
    group="&ITGROUP.";

    format ;
    LastRemain=lag(num);

    if (&type.=1 and 1 <=_N_<=14) or (&type.=2 and 1 <=_N_<= 19) or (&type.=3 and 1 <=_N_<= 19) then do;
        Excluded=LastRemain-num;
    end;
    else if (&type.=1 and 15 <=_N_<=17) or (&type.=2 and 20 <=_N_<=22) or (&type.=3 and 20 <=_N_<=22) then do;
        Excluded=num;
        Num =.;
    end;

    if &type.=1 then do;
        if level=1 then descr="Initial Member Count - Members with a non-missing birth date/sex at any enrollment episode overlapping the query period";
        if level=2 then descr="Exclusion – Members must be excluded if they only have episodes with DrugCov=N and MedCov=Y during the query period";
        if level=3 then descr="Exclusion – Members must be excluded if they only have episodes with DrugCov=Y and MedCov=N during the query period";
        if level=4 then descr="Exclusion – Members must be excluded if they only have episodes with DrugCov=Y and MedCov=N and DrugCov=N and MedCov=Y during the query period";
        if level=5 then descr="Exclusion - Members must satisfy the age range condition within the query period";
        if level=6 then descr="Exclusion - Members must meet chart availability criterion within the query period";
        if level=7 then descr="Exclusion - Members must have at least one GROUP claim within the query period";
        if level=8 then descr="Exclusion - Members must have at least one GROUP episode beginning within the age range condition";
        if level=9 then descr="Exclusion - Members must have at least one GROUP episode that meets Query incidence criterion [no GROUP claim in the prior Query 'WashPer' days]";       
        if level=10 then descr="Exclusion - Members must have at least one GROUP episode satisfying the enrollment criterion specified by the 'EnrDays' parameter";
        if level=11 then descr="Exclusion - Members must have at least one GROUP episode satisfying the enrollment criterion specified by the Query 'WashPer' parameter";
        if level=12 then descr="Exclusion - Members must have at least one GROUP episode satisfying the Exclusion enrollment requirement";
        if level=13 then descr="Exclusion - Members must have at least one GROUP episode satisfying the Exclusion conditions";
        if level=14 then descr="Exclusion - Members must have at least one GROUP episode satisfying the Inclusion conditions";
        if level=15 then descr="Information - Members with at least one GROUP claim with supply and/or amount outside specified ranges";
        if level=16 then descr="Information - Members with at least one EVENT claim with supply and/or amount outside specified ranges";
        if level=17 then descr="Information - Members with at least one INCL/EXCL claim with supply and/or amount outside specified ranges";
    end;
    if &type.=2 then do;
        if level=1 then descr="Initial Member Count - Members with a non-missing birth date/sex at any enrollment episode overlapping the query period";
        if level=2 then descr="Exclusion – Members must be excluded if they only have episodes with DrugCov=N and MedCov=Y during the query period";
        if level=3 then descr="Exclusion – Members must be excluded if they only have episodes with DrugCov=Y and MedCov=N during the query period";
        if level=4 then descr="Exclusion – Members must be excluded if they only have episodes with DrugCov=Y and MedCov=N and DrugCov=N and MedCov=Y during the query period";
        if level=5 then descr="Exclusion - Members must satisfy the age range condition within the query period";
        if level=6 then descr="Exclusion - Members must meet chart availability criterion within the query period";
        if level=7 then descr="Exclusion - Members must have at least one GROUP claim within the query period";
        if level=8 then descr="Exclusion - Members must have at least one GROUP episode beginning within the age range condition";
        if level=9 then descr="Exclusion - Members must have at least one GROUP episode that meets Query incidence criterion [no GROUP claim in the prior Query 'WashPer' days]";       
        if level=10 then descr="Exclusion - members must have at least one GROUP episode with at least minimum days supplied (based on MinDaysSupp criterion)";
        if level=11 then descr="Exclusion - members must have at least one GROUP episode with at least minimum days duration (based on MinEpisDur criterion)";
        if level=12 then descr="Exclusion - Members must have at least one GROUP episode with more than blackout days duration";
        if level=13 then descr="Exclusion - Members must have at least one GROUP episode satisfying the enrollment criterion specified by the 'EnrDays' parameter";
        if level=14 then descr="Exclusion - Members must have at least one GROUP episode satisfying the enrollment criterion specified by the Query 'WashPer' parameter";
        if level=15 then descr="Exclusion - Members must have at least one GROUP episode satisfying the enrollment criterion specified by the Event 'WashPer' parameter";
        if level=16 then descr="Exclusion - Members must have at least one GROUP episode that meets Event incidence criterion [no QUERYEVENTGROUP claim in the prior Event 'WashPer' days]";
        if level=17 then descr="Exclusion - Members must have at least one GROUP episode satisfying the Exclusion enrollment requirement";
        if level=18 then descr="Exclusion - Members must have at least one GROUP episode satisfying the Exclusion conditions";
        if level=19 then descr="Exclusion - Members must have at least one GROUP episode satisfying the Inclusion conditions";
        if level=20 then descr="Information - Members with at least one GROUP claim with supply and/or amount outside specified ranges";
        if level=21 then descr="Information - Members with at least one EVENT claim with supply and/or amount outside specified ranges";
        if level=22 then descr="Information - Members with at least one INCL/EXCL claim with supply and/or amount outside specified ranges";
    end;
    if &type.=3 then do;
        if level=1 then descr="Initial Member Count - Members with a non-missing birth date/sex at any enrollment episode overlapping the query period";
        if level=2 then descr="Exclusion – Members must be excluded if they only have episodes with DrugCov=N and MedCov=Y during the query period";
        if level=3 then descr="Exclusion – Members must be excluded if they only have episodes with DrugCov=Y and MedCov=N during the query period";
        if level=4 then descr="Exclusion – Members must be excluded if they only have episodes with DrugCov=Y and MedCov=N and DrugCov=N and MedCov=Y during the query period";
        if level=5 then descr="Exclusion - Members must satisfy the age range condition within the query period";
        if level=6 then descr="Exclusion - Members must meet chart availability criterion within the query period";
        if level=7 then descr="Exclusion - Members must have at least one GROUP claim within the query period";
        if level=8 then descr="Exclusion - Members must have at least one GROUP episode beginning within the age range condition";
        if level=9 then descr="Exclusion - Members must have at least one GROUP episode that meets Query incidence criterion [no GROUP claim in the prior Query 'WashPer' days]";       
        if level=10 then descr="Exclusion - Members must have at least one GROUP episode satisfying the enrollment criterion specified by the Query 'WashPer' parameter";
        if level=11 then descr="Exclusion - Members must have at least one GROUP episode satisfying the enrollment criterion specified by the 'EnrDays' parameter";
        if level=12 then descr="Exclusion - Members must have at least one GROUP episode satisfying the Exclusion enrollment requirement";
        if level=12 then descr=" ";
        if level=13 then descr="Exclusion - Members must have at least one GROUP episode satisfying the Exclusion conditions";
        if level=14 then descr="Exclusion - Members must have at least one GROUP episode satisfying the Inclusion conditions";
        if level=15 then descr="Exclusion - Members must have only one exposure code on day 0";  
        if level=16 then descr="Exclusion - Members must have at least one GROUP episode satisfying the enrollment criterion specified by the (calculated) HOI assessment window";        
        if level=17 then descr="Exclusion - Members must have at least one GROUP episode satisfying the enrollment criterion specified by the 'EnrDaysAft' parameter";        
        if level=18 then descr="Exclusion - Members must have at least one Event in one of the two HOI assessment windows";
        if level=19 then descr="Exclusion - Members must have an event satisfying the incidence criterion";
        if level=20 then descr="Information - Members with at least one GROUP claim with supply and/or amount outside specified ranges";
        if level=21 then descr="Information - Members with at least one EVENT claim with supply and/or amount outside specified ranges";
        if level=22 then descr="Information - Members with at least one INCL/EXCL claim with supply and/or amount outside specified ranges";
    end;

    rename num=remaining;
    drop LastRemain;

    run;

    *Surveillance mode: report members lost to follow-up and still at risk;
    %if &type.=2 %then %do;
        %let MEMBERS_LOSTTOFOLLOWUP=.;
        %let MEMBERS_STILLATRISK=.;

        %VAREXIST(_ptsmasterlist,LastLookFollowed);
        %if %eval(&VAREXIST.=1) %then %do;
        
            proc means data=_ptsmasterlist;
            class PatId;
            var LastLookFollowed;
            output out=_LastLookFollowed (drop=_:) max=;
            run;
    
            proc sql noprint;
            select count(distinct PatId) into : MEMBERS_LOSTTOFOLLOWUP
            from _ptsmasterlist
            where 0 < LastLookFollowed <= &PERIODIDSTART.;

            proc sql noprint;
            select count(distinct PatId) into : MEMBERS_STILLATRISK
            from _LastLookFollowed
            where LastLookFollowed =0 and not missing(PatId);
            quit;
        %end;

        %put &MEMBERS_LOSTTOFOLLOWUP. &MEMBERS_STILLATRISK.;

        data _Attrition;
        set _Attrition end=eof;
        output;
        if eof then do;
            group="&ITGROUP.";

            descr="Information - Members lost to follow-up up to this period.";
            Excluded=&MEMBERS_LOSTTOFOLLOWUP.;
            Remaining=.; 
            level= 23;
            output;

            descr="Information - Members still at risk at the end of this period.";
            Excluded=.;
            Remaining=&MEMBERS_STILLATRISK.; 
            level= 24;
            output;
        end;
        run;
    %end;

    *Store patient episodes to MSOC;
    %IF %EVAL(&group.=1) %THEN %DO;
        data MSOC.&RUNID._Attrition;
        retain group level descr remaining Excluded;
        set _Attrition;
        run;
    %END;
    %ELSE %DO;
        proc append base=MSOC.&RUNID._Attrition 
                    data=_Attrition force;
        run;
    %END;

%put NOTE: ******** END OF MACRO: ms_attrition v1.8 ********;

%mend ms_attrition;