/*

/ Program      : npcttab.sas
/ Version      : 8.1
/ Author       : Roland Rashleigh-Berry
/ Date         : 09-May-2008
/ Purpose      : Clinical reporting macro to produce tables showing "n", the
/                percentage and optionally the number of events.
/ SubMacros    : %quotelst %words %varlen %zerogrid %npctpvals %attrn %dequote
/                %varfmt %sysfmtlist %verifyb %commas %sortedby %match %nodup
/                %attrc %removew (assumes %popfmt already run)
/ Notes        : This macro is NO LONGER SUPPORTED on a free basis. Maintenance
/                on it is time-consuming and the author does not have the free
/                time to do this. You can use it for free but suggestions for 
/                enhancements from free users will be ignored and bugs reported
/                by free users may take a long time to get resolved or perhaps
/                never will be. Bugs normally only get fixed when the author
/                encounters them while using this macro. A support contract is
/                possible and is especially important if you intend to run 
/                this or any of the other clinical reporting macros in a
/                production environment. Some major changes might be made to
/                this macro in the future and only those with a support
/                contract with me will be given consideration on how the change
/                might effect their use of this macro in the workplace.
/
/                Observations for the total of all treatment arms will be
/                generated inside this macro so do not set this up in the
/                input dataset.
/
/                If you do not specify ordering variables then the levels will
/                be displayed in descending subject frequency count for the
/                total column (even if not displayed).
/
/                Paging is difficult for this macro so the mid level term might
/                be shown right at the bottom of the page and the corresponding
/                low level terms on the following page. This is difficult to
/                solve programmatically using "proc report" because even if the
/                number of titles and footnotes is known and whether a "by line"
/                is being used and how many lines are required for the column
/                headings then you have to know if any low level terms will
/                "flow" and so take up extra lines. No attempt is made to solve
/                this problem inside this macro but a pagevar= and pgbrkpos=
/                parameter can be set for a paging variable and its break
/                position for if you have set up a paging variable in the input
/                data. Of course, this does not apply to non-paginated ODS
/                output such as ODS HTML.
/
/ Usage        : See tutorial with demonstrations on this web site
/
/===============================================================================
/ PARAMETERS:
/-------name------- -------------------------description------------------------
/ dsin              Name of input dataset
/ dsall             Optional dataset for displaying all levels present. The 
/                   level variables in this dataset must be identical to those
/                   in the dsin= dataset.
/ style=3           This is the default layout style to use.
/                     Style=1 is for indented terms and is most suitable for
/                             ascii output. It is the original style and the 
/                             most reliable. It should handle three-level
/                             reports well.
/                     Style=2 is for each level term to appear in its own column
/                             but because these terms can be long it can cause
/                             data values to be "pushed down" where these terms
/                             flow. This option is only good for ODS output and
/                             should only be used for sas v9.2 or later with
/                             the parameter spanrows=yes set for this macro.
/                             This style is NOT IMPLEMENTED YET.
/                     Style=3 is for combining all the levels in the same column
/                             and the levels will be indented with non-breaking 
/                             spaces as per the indent= parameter setting. You
/                             should use this for ODS output only because 
/                             the proportional text used will hopefully avoid
/                             the higher levels terms "flowing". If text flows
/                             then indentation is not preserved and the report 
/                             can look messy. For this style, the anylowlvl= 
/                             term will get overwritten by the mid level term.
/                             This style has only recently been implemented and
/                             ONLY WORKS FOR TWO-LEVEL REPORTS.
/ dspop=_popfmt     Name of population dataset used for calculating percentages
/ uniqueid          List of variables that uniquely identify a subject
/                   (defaults to &_uniqueid_ set in %popfmt).
/ trtvar            Treatment group variable (defaults to &_trtvar_ set in
/                   %popfmt) which must be a coded numeric variable or a short
/                   coded character variable (typically one or two bytes with no
/                   spaces).
/ trtfmt            (optional) format to apply to the treatment variable
/ trtlabel          (quoted) Default is not to show a treatment label
/ topline=no        Default is not to show a line at the top of the report. This
/                   is the best setting for ODS RTF tables and the like.
/ toplabel          Default is not to have a label at the top of the table but
/                   should be quoted if required.
/ spacing=2         Default spacing is 2 between the lowlvl terms and treatments
/ trtspace=4        Default spacing is 4 between treatment arms
/ byvars            (optional) by variables
/ highlvl           High level variable (optional)
/ highlvlord        (optional) variable to order high level terms
/ midlvl            Mid level variable (optional)
/ midlvlord         (optional) variable to order mid level terms
/ lowlvl            Low level variable
/ lowlvlord         (optional) variable to order low level terms
/ lowlvlw=0         Column width for lowlvl items. If 0 then it is automatically
/                   calculated to fit the page.
/ style3w=0         Column width for the style 3 report where all level terms
/                   are in the same column. If 0 then it is automatically
/                   calculated to fit the page.
/ nlen=3            Default digit length for the "n" count is 3. 
/ trtw1-19          You can specify individual column widths for the treatment
/                   arms. If left blank then this will be calculated for you.
/ trtsp1-19         You can specify individual column spacing for the treatment
/                   arms. If left blank then this will be filled in from the
/                   spacing= parameter setting (for the first treatment arm)
/                   and the trtspace= setting for the others.
/ indent=3          Default indentation of the level terms is 3 characters
/ highlvllbl        Label for the high level terms (must be a single line quoted
/                   or unquoted for style=1 reports but for style=2 reports it 
/                   can have several parts and each part must be quoted).
/ midlvllbl         Label for the mid level terms (must be a single line quoted
/                   or unquoted for style=1 reports but for style=2 reports it 
/                   can have several parts and each part must be quoted).
/ lowlvllbl         Label for the low level terms (must be a single line quoted
/                   or unquoted for style=1 reports but for style=2 reports it 
/                   can have several parts and each part must be quoted).
/ style3lbl         This is the column label to use for style 3, where all the
/                   information shares the same column. It can have several 
/                   parts and each part should be quoted.
/ trtord=99         If lowlvlord not specified then this is the treatment group
/                   value used for ordering lowlvl items.
/ trttotval=99      Extra observationbs are created for the total of all 
/                   treatment groups and the default value is 99.
/ total=yes         Default is to show the total for all treatment arms
/ anylowlvl="ANY AE"       Default displayed string for combined low level
/                   terms (required and must be quoted). If not wanted then it
/                   must still be specified but dropped in the droplowlvl= list.
/ toplowlvl         Defaults to whatever anylowlvl= is set to. This causes the
/                   lowlvl term to be displayed first in the midlvl group.
/                   Only set this if you are setting up your own effective
/                   anylowlvl= term in the input data.
/ anywhen=before    This affects how the "any lowlvl" is calculated. If
/ (anywhen=after)   "before" (default) then it will be calculated before any
/                   minimum percentage is applied to the display. If "after"
/                   then first the lowlvl terms to display are determined and
/                   then the "any" counts and percentages are based on just
/                   those lowlvl terms that will be displayed. You should make
/                   sure that the label supplied to anylowlvl= makes it clear
/                   what method is used. If no minimum percentage or minimum
/                   count is supplied then "anywhen=before" will override
/                   whatever is specified to this parameter.
/ droplowlvl        Low level value(s) to drop from table (quoted and separate
/                   with spaces if more than one). You can use this to create
/                   a total with your data by repeating each observation with
/                   the higher level terms set to say what you want such as
/                   "Total of subjects with adverse events" and set your low
/                   level term to "XXX", for example, and then specify to drop
/                   "XXX" to this parameter.
/ split=@           Split character for proc report (no quotes)
/ headskip=yes      Default is to skip a line at the top if proc report output
/ spantotal=yes     Default is for the treament label to span the total column
/ minpct            Minimum percentage value (in trtord= arm) to display
/ mincount          Minimum patient count (in trtord= arm) to display
/ minpctany         Minimum percentage value in any treatment arm to display
/ mincountany       Minimum patient count in any treatment arm to display
/ print=yes         By default, allow this macro to print the report
/ events=no         By default, do not display the number of events
/ nevlen            Digit length for the number of events. Defaults to the
/                   nlen= setting if not used.
/ dsout=_npcttab    Output dataset name if not printing (no modifiers)
/ trtvarlist        You should not need to use this except in special situations
/                   where you want to specify labels to go with the transposed 
/                   treatment variables in the proc report call at the end of
/                   this macro such as in this example:
/              trtvarlist=("__ DRUG 1 __" TRT1 TRT2) ("__ DRUG 2 __" TRT3 TRT4)
/                   You need to know what treatment variables there are and
/                   this will be put in the log by the %popfmt macro. If you
/                   are specifying column widths (usually not required) then if
/                   you change the natural order of these TRT variables then the
/                   trtw(n)= value that acts on a variable is the one that you
/                   would use if the natural order had not been changed.
/ pcttrt            If set to a list of values (separated by spaces, not commas)
/                   will calculate the percentage only for those treatment arm
/                   values. Will be ignored if event counts were requested.
/ pvalues=no        By default, do not calculate p-values
/ usetest           Set to C (Chi-square) or F (Fisher's exact) to override
/                   decision of macro to select Chi-square or Fisher's exact
/                   test based on expected cell counts. Can also be set to T2,
/                   T1L or T1R for the two-sided, one-sided-left or 
/                   one-sided-right Cochran-Armitage Trend test.
/ pvalkeep          Only keep p-values that satisfy this condition (e.g. <0.05)
/ pvalfmt=p63val.   Default format to use for p-values (uses 6.3 or <0.001)
/ pvalvar=TRT9999   Name for p-value variable
/ pvallbl="p-value" Label for p-value variable
/ pvaltrtlist       List of treatment arm values (separated by spaces) used for
/                   p-value calculation (defaults to not using the value
/                   assigned to trttotval= ).
/ chisqid=^         Character to use to identify the Chi-square test that is
/                   added at the end of the character p-value (no quotes)
/ fisherid=~        Character to use to identify the Fisher exact test that is
/                   added at the end of the character p-value (no quotes)
/ trendid           Character to use to identify the Cochran-Armitage Trend Test
/                   that is added at the end of the character p-value (unquoted)
/ nodata=nodata     Name of macro to call to produce a report if there is no
/                   data. This would normally put out a "NO DATA" message.
/ pagevar           Page-breaking variable if set up in input data
/ pgbrkpos          Page breaking position (no quotes - defaults to "before")
/ pctsign=no        By default, do not show the percent sign for percentages
/ pctfmt=5.1        Default format for the percentage
/ pctcompress=no    Whether to compress the percentage
/ pctwarn=yes       By default, put out a warning message if a percentage is
/                   greater than 100.01
/ odsrtf            Give the "file='filename' style=style" (unquoted) to create
/                   rtf output. "ods rtf   ;" and "ods rtf close;" will be
/                   automatically generated. If you want to suppress the plain
/                   text output then use "ods listing close;" before the call to
/                   the macro and "ods listing;" afterwards to reinstate listing
/                   output. Use of topline=no is advised with ods options.
/ odshtml           Works the same way as odsrtf
/ odshtmlcss        Works the same way as odsrtf
/ odscsv            Works the same way as odsrtf
/ odspdf            Works the same way as odsrtf
/ odslisting        Allows you to specify a file= statement
/ odsother          Works the same way as odsrtf but you have to supply the
/                   destination word as the first word.
/ spanrows=yes      Applies to sas v9.2 and later. Default is to enable the
/                   "spanrows" option for "proc report". You should leave this
/                   set to "yes" (no quotes) unless you have a clear need.
/ eventsort=yes     By default, use the event count as a secondary sort key.
/ keepwork=no       By default, do not keep the work datasets created by this
/                   macro.
/ keepmidlvlmin=no  By default, do not keep the mid-level term where the 
/                   percentage or count meets the minimum criteria but there are
/                   no low level terms that meet the criteria.
/ dsdenom           Dataset to use for the denominator for calculating
/                   percentages. Setting this overrides the pctcalc= process.
/                   The denominator value must be held in the variable _total.
/                   The sort order of this dataset will be used for the merge
/                   but if no sort order is known then a matching of variable 
/                   names will be done. Note that if you have "by" variables
/                   then these should normally be in this dataset as well.
/ denomshow=yes     By default, show the denominator value (ddd) held in the
/                   dsdenom dataset as part of the display string. The digits
/                   will be trimmed and will have one space on each side. A 
/                   slash will precede it as shown below.
/                   nnn / ddd (pct)
/ byrowvar          This is for when you want to show a "by" variable as a row
/                   following the variable you are analyzing.
/ byroword          Ordering variable for byrowvar if needed
/ byrowlabel        Label for byrowvar
/ byrowalign        Alignment of byrow (left/center/right)
/ byrowfmt          Format for byrow variable
/ byroww            Width for byrowvar
/ font_face_stats=Courier   Font to use for ODS output of the calculated stats
/                   values. For correct alignment of the decimal point for non-
/                   paired stats for MS Office then this must be set to
/                   "Courier" (no quotes).
/ font_face_other=Times     Font to use for ODS output for columns other
/                   than the calculated stats.
/ font_weight_other=Bold    Weight of font to use for columns other than 
/                   the calculated stats.
/===============================================================================
/ AMENDMENT HISTORY:
/ init --date-- mod-id ----------------------description------------------------
/ rrb  12Mar06         Version 4.0 with three level reporting and level
/                      parameter name changes.
/ rrb  08May06         Set _str=" " when total population and _count are 0
/ rrb  14Jul06         Header tidy
/ rrb  13Feb07         "macro called" message added
/ rrb  08Mar07         Use %dequote for parameter quote checks for v4.1
/ rrb  09Mar07         Allow use of formatted level variables for v5.0
/ rrb  10Mar07         dsall= parameter and processing added for v6.0
/ rrb  11Mar07         80 column limit strictly enforced on header
/ rrb  21Mar07         Added support for the "nocenter" option
/ rrb  30Mar07         Bug fixed with trtlabel and toplabel
/ rrb  03May07         pctsign=no parameter added to allow users to force the
/                      display of the percent sign if required.
/ rrb  16May07         pctfmt=5.1 parameter added to allow users to change the
/                      percent format.
/ rrb  17May07         Further checking of pctfmt= value added (v6.5)
/ rrb  30jul07         Header tidy
/ rrb  01Sep07         pctwarn=yes parameter added
/ rrb  18Sep07         odsrtf= parameter added
/ rrb  19Sep07         odshtml= and odspdf= parameters added
/ rrb  21Sep07         odsother= parameter added
/ rrb  23Sep07         Header tidy
/ rrb  30Sep07         topline=no is now the default which is better suited to
/                      ODS output.
/ rrb  10Nov07         Bug fixed to allow $ and $CHAR formats
/ rrb  27Jan08         odshtmlcss= and odscsv= parameters added and logic
/                      changed for when print=no so that other ods output is
/                      still produced.
/ rrb  15Mar08         odslisting= parameter added
/ rrb  16Mar08         "ods listing" statement used to free ods listing file
/ rrb  26Mar08         Bug in handling $ and $CHAR formats fixed
/ rrb  10Apr08         spanrows= parameter added
/ rrb  26Apr08         style=3 , style3w= , style3lbl= and eventsort= 
/                      parameters added for this major upgrade to v7.0
/ rrb  26Apr08         keepwork=no and keepmidlvlmin=yes parameters added
/ rrb  26Apr08         Duplicate style 3 label parameter removed
/ rrb  30Apr08         dsdenom= and denomshow= parameters added
/ rrb  30Apr08         pctcompress= and byrow= parameters added for v8.0
/ rrb  08May08         style(COLUMN)={HTMLSTYLE="mso-number-format:'\@'"} added
/                      to proc report call so that MS Office treats the cells as
/                      text-formatted cells. The same can be achieved using
/                      headtext=""
/                      in the ODS statement but it is hard to remember.
/ rrb  09May08         font_face_stats=, font_face_other= and font_weight_other=
/                      parameters added.
/===============================================================================
/ This is public domain software. No guarantee as to suitability or accuracy is
/ given or implied. User uses this code entirely at their own risk.
/=============================================================================*/

%put MACRO CALLED: npcttab v8.1;

%macro npcttab(dsin=,
              dsall=,
              dspop=_popfmt,
           uniqueid=,
             trtvar=,
             trtfmt=,
           trtlabel=,
            topline=no,
           toplabel=,
            spacing=2,
           trtspace=4,
             byvars=,
            highlvl=,
         highlvlord=,
             midlvl=,
          midlvlord=,
             lowlvl=,
          lowlvlord=,
            lowlvlw=0,
            style3w=0,
               nlen=3,
              style=3,
              trtw1=,
              trtw2=,
              trtw3=,
              trtw4=,
              trtw5=,
              trtw6=,
              trtw7=,
              trtw8=,
              trtw9=,
             trtw10=,
             trtw11=,
             trtw12=,
             trtw13=,
             trtw14=,
             trtw15=,
             trtw16=,
             trtw17=,
             trtw18=,
             trtw19=,
             trtsp1=,
             trtsp2=,
             trtsp3=,
             trtsp4=,
             trtsp5=,
             trtsp6=,
             trtsp7=,
             trtsp8=,
             trtsp9=,
            trtsp10=,
            trtsp11=,
            trtsp12=,
            trtsp13=,
            trtsp14=,
            trtsp15=,
            trtsp16=,
            trtsp17=,
            trtsp18=,
            trtsp19=,
             indent=3,
         highlvllbl=,
          midlvllbl=,
          lowlvllbl=,
          style3lbl=,
             trtord=99,
          trttotval=99,
              total=yes,
            anylowlvl="ANY AE",
            toplowlvl=,
            anywhen=before,
           droplowlvl=,
              split=@,
           headskip=yes,
          spantotal=yes,
             minpct=,
           mincount=,
          minpctany=,
        mincountany=,
              print=yes,
             events=no,
             nevlen=,
              dsout=_npcttab,
         trtvarlist=,
             pcttrt=,
            pvalues=no,
            usetest=,
           pvalkeep=,
            pvalvar=_pvalue,
            pvalfmt=p63val.,
            pvalstr=TRT9999,
            pvallbl="p-value",
        pvaltrtlist=,
           fisherid=^,
            chisqid=~,
            trendid=,
             nodata=nodata,
            pagevar=,
           pgbrkpos=,
            pctsign=no,
            pctwarn=yes,
             pctfmt=5.1,
        pctcompress=no,
             odsrtf=,
            odshtml=,
         odshtmlcss=,
             odscsv=,
             odspdf=,
         odslisting=,
           odsother=,
           spanrows=yes,
          eventsort=yes,
           keepwork=no,
      keepmidlvlmin=no,
            dsdenom=,
          denomshow=yes,
            byrowvar=,
            byroword=,
          byrowlabel=" ",
          byrowalign=,
            byrowfmt=,
              byroww=,
           byrow2var=,
           byrow2ord=,
         byrow2label=" ",
         byrow2align=,
           byrow2fmt=,
             byrow2w=,
           font_face=Courier
           );


    /*-----------------------------*
         check parameter settings
     *-----------------------------*/ 

%local error ls i totvar strlen numtrt trtwidth startcol repwidth rest 
       trtinlist val var pvalds pvalvar cwidth cwidths fwidth fwidths 
       pcttrtlist newmidlvl newhighlvl newlowlvl indentmid indentlow
       highfmt midfmt lowfmt center pctnfmt denomsortvars pctaction;

%global _strlen_;
   
%let error=0;


%if not %length(&font_face_stats) %then %let font_face_stats=Courier;
%if not %length(&font_face_other) %then %let font_face_other=Times;
%if not %length(&font_weight_other) %then %let font_weight_other=Bold;

%if not %length(&byvars) %then %let byvars=&byroword &byrowvar &byrow2ord &byrow2var;

%if not %length(&byrowalign) %then %let byrowalign=left;
%else %if %upcase(%substr(&byrowalign,1,1)) EQ L %then %let byrowalign=left;
%else %if %upcase(%substr(&byrowalign,1,1)) EQ C %then %let byrowalign=center;
%else %if %upcase(%substr(&byrowalign,1,1)) EQ R %then %let byrowalign=right;
%else %let byrowalign=left;

%if not %length(&byrow2align) %then %let byrow2align=left;
%else %if %upcase(%substr(&byrow2align,1,1)) EQ L %then %let byrow2align=left;
%else %if %upcase(%substr(&byrow2align,1,1)) EQ C %then %let byrow2align=center;
%else %if %upcase(%substr(&byrow2align,1,1)) EQ R %then %let byrow2align=right;
%else %let byrow2align=left;

%if not %length(&byrowlabel) %then %let byrowlabel=" ";
%if not %length(&byrow2label) %then %let byrow2label=" ";

%if not %length(&style) %then %let style=1;

%if not (&style=1 or &style=2 or &style=3) %then %do;
  %let error=1;
  %put ERROR: (npcttab) Style must be 1, 2 or 3 (unquoted) but you have style=&style;
%end;


%if %length(&dsdenom) %then %let denomsortvars=%sortedby(&dsdenom);

%if not %length(&pctcompress) %then %let pctcompress=no;
%let pctcompress=%upcase(%substr(&pctcompress,1,1));

%if not %length(&denomshow) %then %let denomshow=yes;
%let denomshow=%upcase(%substr(&denomshow,1,1));

%if not %length(&keepmidlvlmin) %then %let keepmidlvlmin=yes;
%let keepmidlvlmin=%upcase(%substr(&keepmidlvlmin,1,1));

%if not %length(&keepwork) %then %let keepwork=no;
%let keepwork=%upcase(%substr(&keepwork,1,1));

%if not %length(&eventsort) %then %let eventsort=no;
%let eventsort=%upcase(%substr(&eventsort,1,1));

%if not %length(&pctsign) %then %let pctsign=no;
%let pctsign=%upcase(%substr(&pctsign,1,1));

%if not %length(&pctwarn) %then %let pctwarn=yes;
%let pctwarn=%upcase(%substr(&pctwarn,1,1));

%if "&pctwarn" EQ "N" %then %do;
  %put WARNING: (npcttab) Percent > 100.01 checks disabled;
%end;

%*- if a high level variable specified but no mid one -;
%*- then shift high level values down to mid ones.    -;
%if %length(&highlvl) and not %length(&midlvl) %then %do;
  %let midlvl=&highlvl;
  %let midlvlord=&highlvlord;
  %let midlvllbl=&highlvllbl;
  %let highlvl=;
  %let highlvlord=;
  %let highlvllbl=;
%end;


%*- set indentation values -;
%if %length(&midlvl) %then %let indentlow=&indent;
%else %let indentlow=0;
%if %length(&highlvl) %then %let indentmid=&indent;
%else %let indentmid=0;


%if not %length(&pctfmt) %then %let pctfmt=5.1;
%let pctnfmt=%substr(&pctfmt,%verifyb(&pctfmt,0123456789.)+1);

%if not %length(&pctnfmt) or not %index(&pctnfmt,.) %then %do;
  %let error=1;
  %put ERROR: (npcttab) Format supplied to pctfmt=&pctfmt not valid;
%end;
%else %if "%substr(&pctnfmt,1,1)" EQ "." %then %do;
  %let error=1;
  %put ERROR: (npcttab) No format length supplied to pctfmt=&pctfmt;
%end;

%if not %length(&dsin) %then %do;
  %let error=1;
  %put ERROR: (npcttab) No input dataset name specified for dsin=;
%end;

%if not %length(&dspop) %then %do;
  %let error=1;
  %put ERROR: (npcttab) No population dataset specified for dspop=;
%end;

%if not %length(&lowlvl) %then %do;
  %let error=1;
  %put ERROR: (npcttab) No low level variable specified for lowlvl=;
%end;

%if not %length(&trtord) %then %do;
  %let error=1;
  %put ERROR: (npcttab) No treatment arm ordering value specified for trtord=;
%end;

%if not %length(&trttotval) %then %do;
  %let error=1;
  %put ERROR: (npcttab) No treatment arm total value specified for trttotval=;
%end;

%if not %length(&anylowlvl) %then %do;
  %let error=1;
  %put ERROR: (npcttab) No label specified for the combination of all low level terms for anylowlvl=;
%end;

%if not %length(&minpct.&mincount.&minpctany.&mincountany) %then %let anywhen=before;
%if not %length(&anywhen) %then %let anywhen=before;
%let anywhen=%upcase(%substr(&anywhen,1,1));

%if ("&anywhen" NE "B" and "&anywhen" NE "A") %then %do;
  %let error=1;
  %put ERROR: (npcttab) You must set anywhen=before or anywhen=after;
%end;

%if not %length(&pvaltrtlist) %then %let pvaltrtlist=ne &trttotval;
%else %let pvaltrtlist=in (&pvaltrtlist);

%if &error %then %goto error;



    /*-----------------------------*
         check parameter quoting
     *-----------------------------*/ 

%*- make sure the following is quoted -;


%let anylowlvl="%dequote(&anylowlvl)";


%*- make sure these are NOT quoted -;

%if %length(&highlvllbl) %then %let highlvllbl=%dequote(&highlvllbl);

%if %length(&midlvllbl) %then %let midlvllbl=%dequote(&midlvllbl);

%if %length(&lowlvllbl) %then %let lowlvllbl=%dequote(&lowlvllbl);



    /*-----------------------------*
        assign parameter defaults
     *-----------------------------*/

%let ls=%sysfunc(getoption(linesize));
%let center=%sysfunc(getoption(center));

%if not %length(&uniqueid) %then %do;
  %let uniqueid=&_uniqueid_;
  %put NOTE: (npcttab) Defaulting to uniqueid=&uniqueid;
%end;

%if not %length(&trtvar) %then %do;
  %let trtvar=&_trtvar_;
  %put NOTE: (npcttab) Defaulting to trtvar=&trtvar;
%end;

%if not %length(&dsout) %then %let dsout=_npcttab;

%if not %length(&toplowlvl) %then %let toplowlvl=&anylowlvl;

%if not %length(&trtfmt) %then %do;
  %if %length(&pcttrt) %then %let trtfmt=&_poptfmt_;
  %else %let trtfmt=&_popfmt_;
%end;

%if not %length(&spacing) %then %let spacing=2;

%if not %length(&trtspace) %then %let trtspace=4;

%if not %length(&lowlvlw) %then %let lowlvlw=0;

%if not %length(&events) %then %let events=no;
%let events=%upcase(%substr(&events,1,1));

%if not %length(&nlen) %then %let nlen=3;
%if not %length(&nevlen) %then %let nevlen=&nlen;

%if not %length(&spanrows) %then %let spanrows=yes;
%let spanrows=%upcase(%substr(&spanrows,1,1));
%if "&spanrows" EQ "Y" and 
  %sysevalf( %scan(&sysver,1,.).%scan(&sysver,2,.) GE 9.2 ) 
  %then %let spanrows=spanrows;
%else %let spanrows=;


%*----- work out the display length -----;
%*- add 3 for the space and round brackets -;
%let strlen=%eval(&nlen+3);
%*- add the length of the percent format -;
%let strlen=%eval(&strlen+%scan(&pctnfmt,1,.));
%*- add for a space and the event count if that is specified -;
%if "&events" EQ "Y" %then %let strlen=%eval(&strlen+1+&nevlen);
%*- if displaying percent sign then add 1 to the length -;
%if "&pctsign" EQ "Y" %then %let strlen=%eval(&strlen+1);
%*- if displaying denominator then add more -;
%if %length(&dsdenom) and "&denomshow" EQ "Y" 
  %then %let strlen=%eval(&strlen+&nlen+3);
%let _strlen_=&strlen;

%let cwidths=&_trtcwidths_;
%if "&total" EQ "Y" %then %let cwidths=&cwidths &_trttotcwidth_;

%let fwidths=&_trtfwidths_;
%if "&total" EQ "Y" %then %let fwidths=&fwidths &_trttotfwidth_;

%*- if percentages shown for certain trt only then make a list of the vars -;
%if %length(&pcttrt) %then %do;
  %do i=1 %to %words(&pcttrt);
    %let pcttrtlist=&pcttrtlist
&_trtpref_%sysfunc(compress(%scan(&pcttrt,&i,%str( )),%str(%'%")));
  %end;
%end;


%*- set default lengths for widths -;
%do i=1 %to 19;
  %if not %length(&&trtw&i) %then %do;
    %let var=%scan(&_trtvarlist_,&i,%str( ));
    %let cwidth=%scan(&cwidths,&i,%str( ));
    %let fwidth=%scan(&fwidths,&i,%str( ));
    %let trtw&i=&strlen;
    %if &cwidth GT &&trtw&i %then %let trtw&i=&cwidth;
    %if %length(&pcttrt) and not
%index(%quotelst(%upcase(&pcttrtlist)),"%upcase(&var)") %then %do;
      %let trtw&i=&nlen;
      %if &fwidth GT &&trtw&i %then %let trtw&i=&fwidth;
    %end;
  %end;
%end;


%if not %length(&topline) %then %let topline=no;
%let topline=%substr(%upcase(&topline),1,1);

%if not %length(&split) %then %let split=@;


%if not %length(&headskip) %then %let headskip=yes;
%let headskip=%upcase(%substr(&headskip,1,1));
%if "&headskip" EQ "Y" %then %let headskip=headskip;
%else %let headskip=;


%if not %length(&total) %then %let total=yes;
%let total=%substr(%upcase(&total),1,1);

%if not %length(&spantotal) %then %let spantotal=yes;
%let spantotal=%upcase(%substr(&spantotal,1,1));

%if not %length(&print) %then %let print=yes;
%let print=%upcase(%substr(&print,1,1));

%if %length(&highlvllbl) %then %let highlvllbl=%dequote(&highlvllbl);
%if %length(&midlvllbl) %then %let midlvllbl=%dequote(&midlvllbl);
%if %length(&lowlvllbl) %then %let lowlvllbl=%dequote(&lowlvllbl);

%if not %length(&pvalues) %then %let pvalues=no;
%let pvalues=%upcase(%substr(&pvalues,1,1));

%if not %length(&pgbrkpos) %then %let pgbrkpos=before;



    /*-----------------------------*
        dsall initial processing
     *-----------------------------*/

%if %length(&dsall) %then %do;
  proc sort nodupkey data=&dsall out=_npctdsall;
    by &highlvlord &highlvl &midlvlord &midlvl &lowlvlord &lowlvl;
  run;
%end;



    /*-----------------------------*
          set up new variables
     *-----------------------------*/

*- This version of npcttab allows you to specify formatted variables  -;
*- which it will automatically resolve into its uncoded form in a new -;
*- variable and this is where the processing is done. -;

*- keep only the needed variables -;
data _npctdsin;
  set &dsin;
  keep &byvars &trtvar &uniqueid 
       &highlvlord &highlvl &midlvlord &midlvl &lowlvlord &lowlvl;
run;


%*- default the new variable names to the old ones -;
%let newhighlvl=&highlvl;
%let newmidlvl=&midlvl;
%let newlowlvl=&lowlvl;


%*- find out if a user format is applied to these variables -;
%if %length(&highlvl) %then %do;
  %let highfmt=%varfmt(_npctdsin,&highlvl);
  %if not %index("" %sysfmtlist, "%sysfunc(compress(&highfmt,1234567890.))") 
    %then %let newhighlvl=__highlvl;
%end;
%if %length(&midlvl) %then %do;
  %let midfmt=%varfmt(_npctdsin,&midlvl);
  %if not %index("" %sysfmtlist, "%sysfunc(compress(&midfmt,1234567890.))")
    %then %let newmidlvl=__midlvl;
%end;
%if %length(&lowlvl) %then %do;
  %let lowfmt=%varfmt(_npctdsin,&lowlvl);
  %if not %index("" %sysfmtlist, "%sysfunc(compress(&lowfmt,1234567890.))")
    %then %let newlowlvl=__lowlvl;
%end;




*- do for input data -;
data _npctdsin;
  %*- decode any of these variables if need be -;
  %if ("&newhighlvl" NE "&highlvl") or ("&newmidlvl" NE "&midlvl") 
   or ("&newlowlvl" NE "&lowlvl") %then %do;
    length
    %if ("&newhighlvl" NE "&highlvl") %then &newhighlvl ;
    %if ("&newmidlvl" NE "&midlvl") %then &newmidlvl ;
    %if ("&newlowlvl" NE "&lowlvl") %then &newlowlvl ;
    $ 200;
  %end;
  set _npctdsin;
  %if ("&newhighlvl" NE "&highlvl") %then %do;
    &newhighlvl=put(&highlvl,&highfmt.);
  %end;
  %if ("&newmidlvl" NE "&midlvl") %then %do;
    &newmidlvl=put(&midlvl,&midfmt.);
  %end;
  %if ("&newlowlvl" NE "&lowlvl") %then %do;
    &newlowlvl=put(&lowlvl,&lowfmt.);
  %end;
  keep &byvars &trtvar &uniqueid 
       &highlvlord &newhighlvl &midlvlord &newmidlvl &lowlvlord &newlowlvl;
  *- cancel all formats except for by variables -;
  format &trtvar &uniqueid &highlvlord &newhighlvl &midlvlord &newmidlvl
         &lowlvlord &newlowlvl;
run;



%if %length(&dsall) %then %do;
  *- do for dsall data -;
  data _npctdsall;
    %*- decode any of these variables if need be -;
    %if ("&newhighlvl" NE "&highlvl") or ("&newmidlvl" NE "&midlvl") 
     or ("&newlowlvl" NE "&lowlvl") %then %do;
      length
      %if ("&newhighlvl" NE "&highlvl") %then &newhighlvl ;
      %if ("&newmidlvl" NE "&midlvl") %then &newmidlvl ;
      %if ("&newlowlvl" NE "&lowlvl") %then &newlowlvl ;
      $ 200;
    %end;
    set _npctdsall;
    %if ("&newhighlvl" NE "&highlvl") %then %do;
      &newhighlvl=put(&highlvl,&highfmt.);
    %end;
    %if ("&newmidlvl" NE "&midlvl") %then %do;
      &newmidlvl=put(&midlvl,&midfmt.);
    %end;
    %if ("&newlowlvl" NE "&lowlvl") %then %do;
      &newlowlvl=put(&lowlvl,&lowfmt.);
    %end;
    *- cancel all the formats -;
    format &highlvlord &newhighlvl &midlvlord &newmidlvl 
           &lowlvlord &newlowlvl;
  run;
%end;




    /*-----------------------------*
          add dummy variables
     *-----------------------------*/

*- if all level variables not present then set up dummy variables -;

%if not %length(&highlvl) or not %length(&midlvl) %then %do;
  %if not %length(&highlvl) %then %let newhighlvl=_dummyhigh;
  %if not %length(&midlvl) %then %let newmidlvl=_dummymid;

  data _npctdsin;
    %if not %length(&highlvl) %then %do;
      retain &newhighlvl "DUMMY";
    %end;
    %if not %length(&midlvl) %then %do;
      retain &newmidlvl "DUMMY";
    %end;
    set _npctdsin;
  run;

  %if %length(&dsall) %then %do;
    data _npctdsall;
      %if not %length(&highlvl) %then %do;
        retain &newhighlvl "DUMMY";
      %end;
      %if not %length(&midlvl) %then %do;
        retain &newmidlvl "DUMMY";
      %end;
      set _npctdsall;
    run;
  %end;

%end;



    /*-----------------------------*
        add total treatment group
     *-----------------------------*/

*- This is calculated even if not going to be displayed since it is  -;
*- possibly to be used as the treatment arm to base the ordering by. -;
data _npctdsin;
  set _npctdsin;
  output;
  &trtvar=&trttotval;
  output;
run;



    /*-----------------------------*
        in case there is no data
     *-----------------------------*/

%if %attrn(_npctdsin,nobs) EQ 0 and %length(&nodata) %then %do;
  %&nodata
  %goto skip;
%end;



    /*-----------------------------*
       sort ready for filter step
     *-----------------------------*/

*- sort ready for the filter step if needed -;
proc sort data=_npctdsin;
  by &byvars &newhighlvl &newmidlvl &newlowlvl &trtvar;
run;

%goto skipfilter;



    /*-----------------------------*
        filter for anywhen=after
     *-----------------------------*/

%filter:

*- If anywhen=after is set then only keep those terms that will -;
*- be displayed and repeat all the steps to calculate the count -;
*- and percentage for anylowlvl plus the default ordering variables. -;
data _npctdsin;
  merge _npctfilter(in=_filter) _npctdsin(in=_data);
  by &byvars &newhighlvl &newmidlvl &newlowlvl;
  if _filter and _data;
run;



%skipfilter:



    /*-----------------------------*
          summarise for events
     *-----------------------------*/


*- low level events -;
proc summary nway missing data=_npctdsin;
  by &byvars;
  class &highlvlord &newhighlvl &midlvlord &newmidlvl &lowlvlord &newlowlvl &trtvar;
  output out=_npctevlowlvl(drop=_type_ rename=(_freq_=_events));
run;

data _npctevlowlvl;
  set _npctevlowlvl;
  _lowlvlevord=1/_events;
run;

  
*- mid level events -;
proc summary nway missing data=_npctdsin;
  by &byvars;
  class &highlvlord &newhighlvl &midlvlord &newmidlvl &trtvar;
  output out=_npctevmidlvl(drop=_type_ rename=(_freq_=_events));
run;
 
data _npctevmidlvl;
  set _npctevmidlvl;
  _midlvlevord=1/_events;
run;


*- high level events -;
proc summary nway missing data=_npctdsin;
  by &byvars;
  class &highlvlord &newhighlvl &trtvar;
  output out=_npctevhighlvl(drop=_type_ rename=(_freq_=_events));
run;

data _npctevhighlvl;
  set _npctevhighlvl;
  _highlvlevord=1/_events;
run;





    /*-----------------------------*
          get rid of duplicates
     *-----------------------------*/

*- This part gets ready for the unique subject counts by dropping -;
*- duplicate records for the subjects. -;

proc sort nodupkey data=_npctdsin
                    out=_npctlowlvl;
  by &byvars &trtvar &uniqueid 
     &highlvlord &newhighlvl &midlvlord &newmidlvl &lowlvlord &newlowlvl;
run;

proc sort nodupkey data=_npctdsin(drop=&lowlvlord &newlowlvl)
                    out=_npctmidlvl;
  by &byvars &trtvar &uniqueid 
     &highlvlord &newhighlvl &midlvlord &newmidlvl;
run;

proc sort nodupkey data=_npctdsin(drop=&midlvlord &newmidlvl &lowlvlord &newlowlvl)
                    out=_npcthighlvl;
  by &byvars &trtvar &uniqueid 
     &highlvlord &newhighlvl;
run;



    /*-----------------------------*
      summarise for unique subjects
     *-----------------------------*/

*- Summarise for unique subjects and put the total in _count. -;
*- Merge in the event counts. -;

*- Low Level -;
proc summary nway missing data=_npctlowlvl;
  by &byvars;
  class &highlvlord &newhighlvl &midlvlord &newmidlvl &lowlvlord &newlowlvl &trtvar;
  output out=_npctlowlvl(drop=_type_ rename=(_freq_=_count));
run;

data _npctlowlvl;
  merge _npctevlowlvl _npctlowlvl;
  by &byvars &highlvlord &newhighlvl &midlvlord &newmidlvl &lowlvlord &newlowlvl &trtvar;
run;



*- Mid Level -;
proc summary nway missing data=_npctmidlvl;
  by &byvars;
  class &highlvlord &newhighlvl &midlvlord &newmidlvl &trtvar;
  output out=_npctmidlvl(drop=_type_ rename=(_freq_=_count));
run;

data _npctmidlvl;
  merge _npctevmidlvl _npctmidlvl;
  by &byvars &highlvlord &newhighlvl &midlvlord &newmidlvl &trtvar;
run;



*- High Level -;
proc summary nway missing data=_npcthighlvl;
  by &byvars;
  class &highlvlord &newhighlvl &trtvar;
  output out=_npcthighlvl(drop=_type_ rename=(_freq_=_count));
run;

data _npcthighlvl;
  merge _npctevhighlvl _npcthighlvl;
  by &byvars &highlvlord &newhighlvl &trtvar;
run;



data _npcthighlvl;
  set _npcthighlvl;
  _highlvlord=1/_count;
run;


*- Set up the anylowlvl term for later inclusion -;
*- in with the rest of low level terms -;
data _npctmidlvl;
  length &newlowlvl %varlen(_npctlowlvl,&newlowlvl);
  retain &newlowlvl &anylowlvl;
  set _npctmidlvl;
  _midlvlord=1/_count;
run;



    /*-----------------------------*
             Add in anylowlvl
     *-----------------------------*/

*- Note that the anylowlvl term gets added to the _npctlowlvl -;
*- dataset here. -;

data _npctlowlvl;
  set _npctmidlvl(drop=_midlvlord _midlvlevord)
      _npctlowlvl;
  _lowlvlord=1/_count;
run;


*- sort ready for a merge with zero values -;
proc sort data=_npctlowlvl;
  by &trtvar &byvars &highlvlord &newhighlvl 
     &midlvlord &newmidlvl &lowlvlord &newlowlvl;
run;



    /*-----------------------------*
           Create a zero grid
     *-----------------------------*/

%if %length(&dsall) %then %do;
  %zerogrid(zerovar=_count,
  var1=&trtvar,ds1=&dspop,
  %if %length(&byvars) %then %do;
    var2=&byvars,ds2=_npctlowlvl,
  %end;
  var3=&highlvlord &newhighlvl &midlvlord &newmidlvl &lowlvlord &newlowlvl,
  ds3=_npctdsall)
%end;
%else %do;
  %zerogrid(zerovar=_count,
  var1=&trtvar,ds1=&dspop,
  var2=&byvars &highlvlord &newhighlvl &midlvlord &newmidlvl &lowlvlord &newlowlvl,
  ds2=_npctlowlvl)
%end;

*- add extra required variables -;
data zerogrid;
  retain _events 0 _lowlvlord _lowlvlevord 99;
  set zerogrid;
run;



    /*-----------------------------*
       Merge on top of zero values
     *-----------------------------*/

data _npctlowlvl;
  merge zerogrid _npctlowlvl;
  by &trtvar &byvars &highlvlord &newhighlvl 
     &midlvlord &newmidlvl &lowlvlord &newlowlvl;
run;



    /*-----------------------------*
          calculate percentages
     *-----------------------------*/



%if %length(&dsdenom) %then %do;
  %if not %length(&denomsortvars) %then %do;
    %let denomsortvars=%match(%varlist(_npctlowlvl),%varlist(&dsdenom));
    proc sort data=&dsdenom out=_npctdenom;
      by &denomsortvars;
    run;
  %end;
  %else %do;
    data _npctdenom;
      set &dsdenom;
    run;
  %end;
  proc sort data=_npctlowlvl;
    by &denomsortvars;
  run;
%end;


%if "&pctcompress" EQ "Y" %then %let pctaction=compress(put(_pct,&pctfmt));
%else %let pctaction=put(_pct,&pctfmt);


*- Merge with the population or the denominator dataset -;
data _npctlowlvl;
  length _str $ &strlen _idlabel $ 120;
  %if %length(&dsdenom) %then %do;
    merge _npctdenom _npctlowlvl(in=_npct);
    by &denomsortvars;
  %end;
  %else %do;
    merge &dspop _npctlowlvl(in=_npct);
    by &trtvar;
  %end;
  if _npct;
  if _total in (.,0) and _count EQ 0 then do;
    _pct=0;
    _str=" ";
  end;
  else do;
    _pct=100*_count/_total;
    %if "&pctwarn" NE "N" %then %do;
    if _pct GT 100.01 then 
      put "WARNING: (npcttab) _pct GT 100.01 " (_all_) (=);
    %end;
    %if "&events" EQ "Y" %then %do;
      %if "&pctsign" EQ "Y" %then %do;
        %if %length(&dsdenom) and "&denomshow" EQ "Y" %then %do;
          _str=put(_count,&nlen..)||' / '||trim(left(put(_total,8.)))||" ("||&pctaction||"%) "||put(_events,&nevlen..);
        %end;
        %else %do;
          _str=put(_count,&nlen..)||" ("||&pctaction||"%) "||put(_events,&nevlen..);
        %end;
      %end;
      %else %do;
        %if %length(&dsdenom) and "&denomshow" EQ "Y" %then %do;
          _str=put(_count,&nlen..)||' / '||trim(left(put(_total,8.)))||" ("||&pctaction||") "||put(_events,&nevlen..);
        %end;
        %else %do;
          _str=put(_count,&nlen..)||" ("||&pctaction||") "||put(_events,&nevlen..);
        %end;
      %end;
    %end;
    %else %do;
      %if %length(&pcttrt) %then %do;
        %if "&pctsign" EQ "Y" %then %do;
          %if %length(&dsdenom) and "&denomshow" EQ "Y" %then %do;
            if &trtvar in (&pcttrt) then _str=put(_count,&nlen..)||' / '||trim(left(put(_total,8.)))||"("||&pctaction||"%)";
          %end;
          %else %do;
            if &trtvar in (&pcttrt) then _str=put(_count,&nlen..)||"("||&pctaction||"%)";
          %end;
          else _str=put(_count,&nlen..);
        %end;
        %else %do;
          %if %length(&dsdenom) and "&denomshow" EQ "Y" %then %do;
            if &trtvar in (&pcttrt) then _str=put(_count,&nlen..)||' / '||trim(left(put(_total,8.)))||"("||&pctaction||")";
          %end;
          %else %do;
            if &trtvar in (&pcttrt) then _str=put(_count,&nlen..)||"("||&pctaction||")";
          %end;
          else _str=put(_count,&nlen..);
        %end;
      %end;
      %else %do;
        %if "&pctsign" EQ "Y" %then %do;
          %if %length(&dsdenom) and "&denomshow" EQ "Y" %then %do;
            _str=put(_count,&nlen..)||' / '||trim(left(put(_total,8.)))||" ("||&pctaction||"%)";
          %end;
          %else %do;
            _str=put(_count,&nlen..)||" ("||&pctaction||"%)";
          %end;
        %end;
        %else %do;
          %if %length(&dsdenom) and "&denomshow" EQ "Y" %then %do;
            _str=put(_count,&nlen..)||' / '||trim(left(put(_total,8.)))||" ("||&pctaction||")";
          %end;
          %else %do;
            _str=put(_count,&nlen..)||" ("||&pctaction||")";
          %end;
        %end;
      %end;
    %end;
  end;
  _idlabel=put(&trtvar,&trtfmt);
  *- cancel possible treatment variable format picked up from dspop dataset -;
  format &trtvar;
run;


 
    /*-----------------------------*
           calculate p-values
     *-----------------------------*/

%if "&pvalues" EQ "Y" %then %do;

  data _forpvals;
    set _npctlowlvl(where=(&trtvar &pvaltrtlist));
    _response=1;
    output;
    _response=0;
    _count=_total-_count;
    output;
  run;

  proc sort data=_forpvals;
    by &byvars &highlvlord &newhighlvl 
       &midlvlord &newmidlvl &lowlvlord &newlowlvl &trtvar;
  run;


  %if %attrn(_forpvals,nobs) GT 0 %then %do;
    %let pvalds=_pvalues;
    %npctpvals(dsin=_forpvals,
               byvars=&byvars &highlvlord &newhighlvl &midlvlord &newmidlvl &lowlvlord &newlowlvl,
               trtvar=&trtvar,respvar=_response,countvar=_count,usetest=&usetest,
               pvalvar=&pvalvar,pvallbl=&pvallbl,pvalstr=&pvalstr,pvalfmt=&pvalfmt,
               pvalkeep=&pvalkeep,chisqid=&chisqid,fisherid=&fisherid,trendid=&trendid)
  %end;
  %else %let pvalds=;
  
  proc datasets nolist;
    delete _forpvals;
  run;
  quit;
  
    
%end;
%else %do;

  %*- setting this to null makes the "proc report" logic easier -;
  %let pvalstr=;

%end;



    /*-----------------------------*
               Transpose
     *-----------------------------*/
  
proc sort data=_npctlowlvl;
  by &byvars &highlvlord &newhighlvl &midlvlord 
     &newmidlvl &lowlvlord &newlowlvl &trtvar;
run;

*- transpose _str -;
proc transpose prefix=&_trtpref_ data=_npctlowlvl out=_npctstr(drop=_name_);
  by &byvars &highlvlord &newhighlvl &midlvlord 
     &newmidlvl &lowlvlord &newlowlvl ;
  var _str;
  id &trtvar;
  idlabel _idlabel;
run;

*- transpose _count -;
proc transpose prefix=_CNT data=_npctlowlvl out=_npctcnt(drop=_name_);
  by &byvars &highlvlord &newhighlvl &midlvlord 
     &newmidlvl &lowlvlord &newlowlvl ;
  var _count;
  id &trtvar;
  idlabel _idlabel;
run;

*- transpose _pct -;
proc transpose prefix=_PCT data=_npctlowlvl out=_npctpct(drop=_name_);
  by &byvars &highlvlord &newhighlvl &midlvlord 
     &newmidlvl &lowlvlord &newlowlvl ;
  var _pct;
  id &trtvar;
  idlabel _idlabel;
run;

*- Merge the transposed datasets back together -;
*- and add the pvalue dataset (if created).    -;
data &dsout;
  merge _npctstr _npctcnt _npctpct &pvalds;
  by &byvars &highlvlord &newhighlvl &midlvlord 
     &newmidlvl &lowlvlord &newlowlvl ;
run;



    /*-----------------------------*
          minpct= and mincount=
     *-----------------------------*/

%if %length(&minpct) or %length(&mincount) %then %do;

  *- minpct= and mincount= only apply to the treatment arm value -;
  *- specified to trtord= (by default the total of all treatment -;
  *- groups) so select on these. -;
  data &dsout;
    set &dsout;
    %if %length(&mincount) %then %do;
      if _cnt%sysfunc(compress(&trtord,%str(%'%"))) >= &mincount;
    %end;
    %if %length(&minpct) %then %do;
      if _pct%sysfunc(compress(&trtord,%str(%'%"))) >= &minpct;
    %end;   
  run;


%end;



    /*-----------------------------*
       minpctany= and mincountany=
     *-----------------------------*/

%if %length(&minpctany) or %length(&mincountany) %then %do;

  %*- Get a list of all the treatment values -;
  %let trtinlist=&_trtinlist_;
  %if "&total" EQ "Y" %then %let trtinlist=&trtinlist &_trttotstr_;

  *- Find the maximum _count and _pct for any displayed treatment -;
  *- arm, select of this and drop at the end. -;
  data &dsout;
    set &dsout;
     _count=max(0
    %do i=1 %to %words(&trtinlist);
      %let val=%sysfunc(compress(%scan(&trtinlist,&i,%str( )),%str(%'%")));
      , _cnt&val
    %end;
    );
    _pct=max(0
    %do i=1 %to %words(&trtinlist);
      %let val=%sysfunc(compress(%scan(&trtinlist,&i,%str( )),%str(%'%")));
      , _pct&val
    %end;
    );    
    %if %length(&mincountany) %then %do;
      if _count >= &mincountany;
    %end;
    %if %length(&minpctany) %then %do;
      if _pct >= &minpctany;
    %end;   
    drop _pct _count;
  run;

%end;



    /*-----------------------------*
             Keepmidlvlmin
     *-----------------------------*/

%if "&keepmidlvlmin" EQ "N" %then %do;
  proc summary nway missing data=&dsout;
    class &byvars &newhighlvl &newmidlvl;
    output out=_npctkeepmid(drop=_type_);
  run;

  data _npctkeepmid;
    set _npctkeepmid;
    if _freq_=1 then delete;
    drop _freq_;
  run;

  data &dsout;
    merge _npctkeepmid(in=_mid) &dsout;
    by &byvars &newhighlvl &newmidlvl;
    if _mid;
  run;

%end;



    /*-----------------------------*
         anywhen=after processing
     *-----------------------------*/

%if "&anywhen" EQ "A" %then %do;

  *- If anywhen=after then the anylvl3 count and percentage -;
  *- are based on only the terms being displayed so at this -;
  *- point find out what those terms are and branch back to -;
  *- near the start of the macro and select only those terms. -;
  proc sort nodupkey data=&dsout(keep=&byvars &newmidlvl &newlowlvl)
             out=_npctfilter;
    by &byvars &newhighlvl &newmidlvl &newlowlvl;
  run;
  
  %*- reset so that we do not do this a second time -;
  %let anywhen=B;
  %goto filter;

%end;



    /*-----------------------------------*
       Add _highlvlord ordering variable
     *-----------------------------------*/

%if not %length(&highlvlord) %then %do;

  data &dsout;
    merge _npcthighlvl(in=_highlvl keep=&byvars &trtvar
                         &newhighlvl _highlvlord _highlvlevord
                   where=(&trtvar=&trtord))
          &dsout(in=_tran);
    by &byvars &newhighlvl;
    if _tran;
    if not _highlvl then do;
      _highlvlord=9999;
      _highlvlevord=9999;
    end;
    drop &trtvar;
  run;

%end;



    /*----------------------------------*
       Add _midlvlord ordering variable
     *----------------------------------*/

%if not %length(&midlvlord) %then %do;

  data &dsout;
    merge _npctmidlvl(in=_midlvl keep=&byvars &highlvlord &newhighlvl 
                         &newmidlvl _midlvlord _midlvlevord &trtvar
                   where=(&trtvar=&trtord))
          &dsout(in=_tran);
    by &byvars &highlvlord &newhighlvl &newmidlvl;
    if _tran;
    if not _midlvl then do;
      _midlvlord=9999;
      _midlvlevord=9999;
    end;
    drop &trtvar;
  run;

%end;



    /*----------------------------------*
       Add _lowlvlord ordering variable
     *----------------------------------*/

%if not %length(&lowlvlord) %then %do;

  data &dsout;
    merge _npctlowlvl(in=_lowlvl 
                  keep=&byvars &highlvlord &newhighlvl &trtvar
                       &midlvlord &newmidlvl &newlowlvl _lowlvlord _lowlvlevord
                 where=(&trtvar=&trtord))
          &dsout(in=_tran);
    by &byvars &highlvlord &newhighlvl &midlvlord &newmidlvl &newlowlvl;
    if _tran;
    if not _lowlvl then do;
      _lowlvlord=9999;
      _lowlvlevord=9999;
    end;
    drop &trtvar;
  run;

%end;



    /*----------------------------------*
       Set default ordering variables
     *----------------------------------*/

%if not %length(&highlvlord) %then %let highlvlord=_highlvlord;
%if not %length(&midlvlord)  %then %let midlvlord=_midlvlord;
%if not %length(&lowlvlord)  %then %let lowlvlord=_lowlvlord;



    /*-----------------------------*
          Calculate report width
     *-----------------------------*/

%*- This is done here so the report width can be applied -;
%*- to the midlvl and highlvl variable in a length statement -;
%*- to make sure it is not too long for the report. -;

%let totvar=;
%if "&total" EQ "Y" %then %let totvar=&_trttotvar_;

%if not %length(&byroww) %then %let byroww=12;
%if not %length(&byrow2w) %then %let byrow2w=12;


%let numtrt=%words(&_trtvarlist_ &totvar);
%do i=1 %to &numtrt;
  %if &i EQ 1 and not %length(&trtsp1) %then %let trtsp1=&spacing;
  %else %if not %length(&&&trtsp&i) %then %let trtsp&i=&trtspace;
%end;
%let trtwidth=0;
%do i=1 %to &numtrt;
  %let trtwidth=%eval(&trtwidth+&&trtsp&i+&&trtw&i);
%end;

%if "&pvalues" EQ "Y" %then %let trtwidth=%eval(&trtwidth+&trtspace+8);

%let rest=%eval(&ls-&trtwidth-&indentlow-&indentmid);

%if %length(&byrowvar) %then %let rest=%eval(&rest-&byroww-2);
%if %length(&byrow2var) %then %let rest=%eval(&rest-&byrow2w-2);

%if &lowlvlw=0 %then %let lowlvlw=&rest;
%else %if &rest LT &lowlvlw %then %let lowlvlw=&rest;

%if &style3w=0 %then %let style3w=&rest;
%else %if &rest LT &style3w %then %let style3w=&rest;


%*- report width below is only meaningful and correct for style=1 output -;
%let repwidth=%eval(&lowlvlw+&trtwidth+&indentlow+&indentmid);


%if ¢er EQ NOCENTER %then %let startcol=1;
%else %let startcol=%eval((&ls-&repwidth)/2 + 1);



    /*---------------------------------*
       Limit length of midlvl variable
     *---------------------------------*/


*- midlvl variable can not be longer than the report width -;
data &dsout;
  %if &style EQ 3 %then %do;
    length _combtext $ %eval(&ls-&trtwidth);
  %end;
  length &_trtvarlist_ 
    %if "&total" EQ "Y" %then %do;
      &_trttotvar_ 
    %end;
    $ &strlen
    %if "&pvalues" EQ "Y" %then %do;
      &_trtpvalvar_ $ 8
    %end;
    &newhighlvl $ &repwidth
    &newmidlvl $ %eval(&repwidth-&indentmid)
  ;
  retain _indent '        ';
  set &dsout;
  %if &style EQ 3 %then %do;
    if &newlowlvl=&anylowlvl then _combtext=&newmidlvl;
    else _combtext=repeat("A0"x,&indentlow-1)||&newlowlvl;
  %end;
  %if %length(&droplowlvl) %then %do;
    if &newlowlvl in (&droplowlvl) 
    %if &style=3 %then %do;
      and &newlowlvl NE &anylowlvl
    %end;
    then delete;
  %end;
  if &newlowlvl=&toplowlvl then _primord=0;
  else _primord=1;
run;



    /*-----------------------------*
            Produce the report
     *-----------------------------*/


%*- make sure byroword and byrowvar are not in the by variables list -;
%if %length(&byvars) and %length(&byrowvar) %then
  %let byvars=%removew(&byvars,&byrowvar);

%if %length(&byvars) and %length(&byroword) %then
  %let byvars=%removew(&byvars,&byroword);

%*- make sure byrow2ord and byrow2var are not in the by variables list -;
%if %length(&byvars) and %length(&byrow2var) %then
  %let byvars=%removew(&byvars,&byrow2var);

%if %length(&byvars) and %length(&byrow2ord) %then
  %let byvars=%removew(&byvars,&byrow2ord);


%if "&print" EQ "N" %then %do;
  ods listing close;
%end;
%else %do;
  ods listing &odslisting;
%end;

%if %length(&odsrtf) %then %do;
  ods rtf &odsrtf ;
%end;

%if %length(&odshtml) %then %do;
  ods html &odshtml ;
%end;

%if %length(&odshtmlcss) %then %do;
  ods htmlcss &odshtmlcss ;
%end;

%if %length(&odscsv) %then %do;
  ods csv &odscsv ;
%end;

%if %length(&odspdf) %then %do;
  ods pdf &odspdf ;
%end;

%if %length(&odsother) %then %do;
  ods &odsother ;
%end;


proc report nowd missing headline &headskip split="&split" spacing=&spacing
&spanrows data=&dsout

  style(COLUMN)={font_face=&font_face_stats HTMLSTYLE="mso-number-format:'\@'"}
  style(LINES)={font_face=&font_face_other font_weight=&font_weight_other HTMLSTYLE="mso-number-format:'\@'"}

;

  %if %length(&byvars) %then %do;
    by &byvars;
  %end; 
 
  columns
  %if "&topline" EQ "Y" %then %do;
    ( "___" 
  %end;

  &pagevar &byroword &byrowvar &byrow2ord &byrow2var

           &highlvlord
 
           %if "&eventsort" EQ "Y" %then %do;
             _highlvlevord
           %end;

           &newhighlvl 
           &midlvlord 

           %if "&eventsort" EQ "Y" %then %do;
             _midlvlevord
           %end;

           &newmidlvl 
           _primord
           &lowlvlord 

           %if "&eventsort" EQ "Y" %then %do;
             _lowlvlevord
           %end;

  %if &style=3 %then %do;
    _combtext
  %end;

  %else %do;
    %if &indentlow GT 0 %then %do;
      _indent 
    %end;
  
    &newlowlvl 
  %end;
  
  (&toplabel 
 
  %if %length(&trtlabel) %then %do;
    %if "&spantotal" EQ "Y" %then %do;
      ( &trtlabel 
        %if %length(&trtvarlist) %then %do;
          &trtvarlist )
        %end;
        %else %do;
          &_trtvarlist_ &totvar ) &pvalstr
        %end;
    %end;
    %else %do;
      ( &trtlabel
        %if %length(&trtvarlist) %then %do;
          &trtvarlist )
        %end;
        %else %do; 
          &_trtvarlist_ ) &totvar &pvalstr
        %end;
    %end;
  %end;
  %else %do;
     %if %length(&trtvarlist) %then %do;
       &trtvarlist
     %end;
     %else %do;
       &_trtvarlist_ &totvar &pvalstr
     %end;
  %end;
    )

  %if "&topline" EQ "Y" %then %do;
    )
  %end;
  ;

  %if %length(&pagevar) %then %do;
    define &pagevar / order order=internal noprint;
  %end;

  %if %length(&byroword) %then %do;
    define &byroword / order order=internal noprint;
  %end;

  %if %length(&byrowvar) %then %do;
    define &byrowvar / order order=internal &byrowlabel width=&byroww &byrowalign flow spacing=0
           %if %length(&byrowfmt) %then f=&byrowfmt ;
           style(COLUMN)={font_face=&font_face_other font_weight=&font_weight_other}   
      ;
  %end;


  %if %length(&byrow2ord) %then %do;
    define &byrow2ord / order order=internal noprint;
  %end;

  %if %length(&byrow2var) %then %do;
    define &byrow2var / order order=internal &byrow2label width=&byrow2w &byrow2align flow spacing=2
           %if %length(&byrow2fmt) %then f=&byrow2fmt ;   
           style(COLUMN)={font_face=&font_face_other font_weight=&font_weight_other}
      ;
  %end;

  define &highlvlord / order order=internal noprint;

  %if "&eventsort" EQ "Y" %then %do;
    define _highlvlevord / order=internal noprint;
  %end;

  define &newhighlvl / order noprint;

  define &midlvlord / order order=internal noprint;

  %if "&eventsort" EQ "Y" %then %do;
    define _midlvlevord / order=internal noprint;
  %end;

  define &newmidlvl / order noprint;

  %if &indentlow GT 0 and &style NE 3 %then %do;
    define _indent / order spacing=0 width=%eval(&indentlow+&indentmid)

    %if %length(&highlvllbl) %then %do;
      %if %length(&highlvllbl) LE %eval(&indentlow+&indentmid) %then %do;
        "&highlvllbl" 
      %end;
      %else %do;
        "%substr(&highlvllbl,1,%eval(&indentmid+&indentlow))" 
      %end;
      %if %length(&midlvllbl) %then %do;
        %if %length(&midlvllbl) LE &indentlow %then %do;
          "%sysfunc(repeat(%str( ),%eval(&indentmid-1)))&midlvllbl" 
        %end;
        %else %do;
          "%sysfunc(repeat(%str( ),%eval(&indentmid-1)))%substr(&midlvllbl,1,&indent)" 
        %end;
        %if %length(&lowlvllbl) %then %do;
          " "
        %end;
      %end;
      %else %do;
        " " 
      %end;
    %end;

    %else %if %length(&midlvllbl) %then %do;
      %if %length(&midlvllbl) LE &indentlow %then %do;
        "&midlvllbl" 
      %end;
      %else %do;
        "%substr(&midlvllbl,1,&indentlow)" 
      %end;
      %if %length(&lowlvllbl) %then %do;
        " "
      %end;
    %end;

    %else %do;
      " " 
    %end;

    ;
  %end;

  define _primord / order order=internal noprint;

  define &lowlvlord / order order=internal noprint;

  %if "&eventsort" EQ "Y" %then %do;
    define _lowlvlevord / order=internal noprint;
  %end;


  %if &style NE 3 %then %do;

    define &newlowlvl / order width=&lowlvlw flow spacing=0

    %if &indentlow GT 0 %then %do;
      %if %length(&highlvllbl) GT %eval(&indentlow+&indentmid) %then %do;
        "%substr(&highlvllbl,&indentlow+&indentmid+1)"
        %if %length(&midlvllbl) GT &indentlow %then %do;
          "%substr(&midlvllbl,&indentlow+1)"
        %end;
        %else %do;
         " "
        %end;
        %if %length(&lowlvllbl) %then %do;
          "&lowlvllbl"
        %end;
      %end;
      %else %if %length(&midlvllbl) GT &indentlow %then %do;
        "%substr(&midlvllbl,&indentlow+1)"
        %if %length(&lowlvllbl) %then %do;
          "&lowlvllbl"
        %end;
      %end;
      %else %do;
        %if %length(&lowlvllbl) %then %do;
          "&lowlvllbl"
        %end;
        %else %do;
          " "
        %end;
      %end;
    %end;

    %else %do;
      %if %length(&lowlvllbl) %then %do;
        "&lowlvllbl"
      %end;
      %else %do;
        " "
      %end;
    %end;
    ;

  %end;

  %else %do;

    define _combtext / order width=&style3w flow 
    %if not %length(&byrowvar) %then %do;
      spacing=0
    %end;
    %else %do;
      spacing=2
    %end;

    %if %length(&style3lbl) %then %do;
      &style3lbl
    %end;
    %else %do;
      " "
    %end;
    style(COLUMN)={font_face=&font_face_other font_weight=&font_weight_other}
    ;

  %end;

  
  %do i=1 %to %words(&_trtvarlist_);
    define %scan(&_trtvarlist_,&i,%str( )) / width=&&trtw&i center display spacing=&&trtsp&i;
  %end;
  %if %length(&totvar) %then %do;
    define &totvar / width=&&trtw&i center display spacing=&trtspace;
  %end;
  %if %length(&pvalstr) %then %do;
    define &pvalstr / &pvallbl width=8 center display spacing=&trtspace;
  %end;

  %if &indentmid gt 0 and &style ne 3 %then %do;
    compute before &newhighlvl;
      line @&startcol &highlvl $char&repwidth..;
    endcomp;
  %end;

  %if &indentlow gt 0 and &style ne 3 %then %do;
    compute before &newmidlvl;
      line @%eval(&startcol+&indentmid) &midlvl $char%eval(&repwidth-&indentmid).;
    endcomp;
  %end;

  break after &newmidlvl / skip;

  %if %length(&pagevar) %then %do;
    break &pgbrkpos &pagevar / _page_;
  %end;

run;

%if "&print" EQ "N" or %length(&odslisting) %then %do;
  ods listing;
%end;

%if %length(&odsrtf) %then %do;
  ods rtf close;
%end;

%if %length(&odshtml) %then %do;
  ods html close;
%end;

%if %length(&odshtmlcss) %then %do;
  ods htmlcss close;
%end;

%if %length(&odscsv) %then %do;
  ods csv close;
%end;

%if %length(&odspdf) %then %do;
  ods pdf close;
%end;

%if %length(&odsother) %then %do;
  ods %scan(&odsother,1,%str( )) close;
%end;





    /*-----------------------------*
             Tidy up and exit
     *-----------------------------*/


%if "&keepwork" EQ "Y" %then %do;
%end;
%else %do;
  proc datasets nolist;
    delete _npctdsin _npcthighlvl _npctmidlvl _npctlowlvl 
           _npctstr _npctcnt _npctpct &pvalds
      _npctevhighlvl _npctevmidlvl _npctevlowlvl

    %if %length(&keepmidlvlmin) %then %do;
      _npctkeepmid
    %end;

    %if %length(&dsall) %then %do;
      _npctdsall
    %end;
    %if "&print" EQ "Y" %then %do;
      &dsout
    %end;
    ;
  run;
  quit;
%end;

%goto skip;
%error: %put ERROR: (npcttab) Leaving macro due to error(s) listed;
%skip:
%mend;