Macro ShowComb Combinations from Check All That Apply Data

From sasCommunity
Jump to: navigation, search

SUGI22.204.1997: %ShowComb: a macro to produce a data set with frequency of combinations of responses from multiple-response data

Author: Ronald_J._Fehd

Notes:

Programs

Macro ShowComb

Note: ~600+ lines of macro and test programs.

 /* RJF2 01Oct11 * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* MACRO: SHOWCOMB                           NOTE: uses macros ARRAY NOBS
*
*  USAGE: 1) %SHOWCOMB(series-name);
*             *assumes CHECKALL dataset exists for this series
*          2) %SHOWCOMB(series-name,LIST=var1 var2 var3 .. varN);
*          3) %SHOWCOMB(series-name,DATA=dataset);
*          4) %SHOWCOMB(series-name,TRIMCHAR=-);
*          5) %SHOWCOMB(series-name,PRINT=1);
*          6) %SHOWCOMB(series-name,BY_VAR=var-name);
*          7) %SHOWCOMB(series-name,TRUE='Y');
*
*  DESCRIPTION: Processing of Check-all-that-apply questions
*      output dataset contains Combinations, Count Percent
*      and Columns containing each item of Combination
*
*  PROCESS:
*    1: macro: prepare ARRAY of variables
*    2: macro: concatenate elements into VAR_LIST
*    3: data: prepare subset of DATA and create Number for FREQ
*    4. proc: if BY_VAR present, then SORT data
*    5. proc: FREQ of Number to output dataset
*    6. data: recode FREQ output: convert Number to Combinations
*    7. data: save optimized dataset to library
*    8. proc: if wanted CONTENTS and/or PRINT of saved data set
*
*  NOTES: datasets created by %CHECKALL are named &SERIES.
*         these are input data sets to %ARRAY
*
*  NOTES: max number of variables to analyze is 51
*
*  KEYWORDS: APPEND array %ARRAY binary-coded call label()
*            data compression dim() dimension FREQ left()
*            multiple-response data put() trim()
*
*  Author: Ronald J. Fehd, 
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
%MACRO SHOWCOMB(
 SERIES    /* name of series of variables, output data set prefix    ***
           /* output data set name: &SERIES.CMB                       */
,fromProg = &ProgName. /*in data set label                            */
,LIST    =DATA  /* list of variables                                  */
           /* default is DATA previously prepared                     */
           /* whose name is &SERIES: output from %CHECKALL macro      */
,LIBRARY =LIBRARY/* library name                                      */
,LIB_OUT =LIBSMRY/* library name for output data-set                  */
,OUT2LIB =1      /* copy CHECKALL output to LIBRARY for use here      */
,DATA    =&DATA_SET./* DATASET is global variable, else hardcode here */
           /* master DATA set name                                    */
           /*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!              */
           /* DO NOT USE &SERIES AS NAME OF THIS PARM                 */
           /*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!              */
,OUT     =./* name of output data-set, if not &SERIES                 */
,OP      =EQ/* IF Value &OPERATOR &TRUE                               *
            /* default: EQ, provided for: IN (value1, value2)         */
,ORDER   =FREQ/* ?sort by descending Count?                           */
,ID      =MPEPNUM /* var for N_IDS                                    */
,LBL_LBL =Combinations of &SERIES./*label of variable Label           */
,LBLCOUNT=Number of Laboratories Responding/*                        ***
           /* label of Freq-Count                                     */
,LBLPCENT=Percentage of Laboratories Responding/*                    ***
           /* label of Freq-Percent                                   */
,BY_VAR  =./* var for subsetting                                      */
,BY_VALUE=' '/* char12 value of by_var(s)                             */
,SUBVARS =./* vars in SUBSET                                          */
,VALUECHR=' '/* display info                                          */
,WHERE   =1/* condition to where statement for subsetting             */
/************
,SUBSET  =./* condition to where statement for subsetting             */
           /* subset=var1 eq 'A'                                      **
           /* subset=var1 eq 'B' and var2 eq 'A'                      **
           /* subset=var1 eq 'B' and not var2 eq 'A'                  */
           /* NOTE: do not use dquotes around char-vars               */
,FORMAT  = /* format of single by-variable, use dot at end of format  */
,GRFXPTRN=Q00BARH/* graphics-pattern in (barh barv pie)               */
,EXCLUDE =1/* ?exclude BLANK and INVALID?                             */
,PRINT   =0    /* WantPrint output? suppresses printing               */
,TITLEN  =2   /* line # of title of print output                      */
,TESTING =0  /* TESTING=1 enables explanatory printouts               */
,TITLE   =ShowComb/*label of output dataset and title for graphics    **
            /* TITLE cannot contain commas                            */
,TRIMCHAR=: /* front-trim label to this char                          */
            /* labels expected to be in form:                         */
            /* "Q04B: Category - Specific"                            */
 /*,TRUE = 1 /* variable counted with this value: numeric    one      */
   ,TRUE ='1'/* variable counted with this value: character  one      */
 /*,TRUE ='Y'/* variable counted with this value: character 'Yes'     */
)/des = 'FREQ of comb: series of Check-All vars'/*              */
 /* ** store /* ** secure /* ** source /* */
;/*change notes - - - - - - - - - - - - - - - - - - - - - - - - - - - **
RJF2:92Jul17 fixed to handle multiple by_vars that are binary-valued
RJF2:96Apr09 when by_var present print ZFRQCOMB by Label &BY_VAR;
RJF2:96Jul00 polishing for SESUG
RJF2:96Oct17 max vars = 51 due to failure of sum at bit 52
RJF2 97May21 retrofit for rtvt
RJF2 97Jul28 added OUT=. parm
RJF2 97Aug11 added options: store, des=
RJF2 97Oct20 polishing: added SUBVARS + SUBSET to conform to CHECKALL
RJF2 97Oct21 added Value to conform to CHECKALL + FREQ1VAR
RJF2 98Feb25 LIB_OUT=LIBRARY
RJF2 98Apr02 dropped print of CHECKALL data-set when list provided
             section 9.3 moved inside test of by_var ne "" with 9.2
             suggest new parm: PRNT-CHKALL
RJF2 98Aug31 added N_ids,
RJF2 98Sep00 now use BinStrng, not N, V6.12 sort handles large char-var
RJF2 98Sep22 added ORDER=FREQ
RJF2 98Sep25 cut SUBVARS usage
LYL2 98Oct29 added subset to title
RJF2 99Jul26 proposed change N_??? to numeric
RJF2 00Feb02          change N_??? to numeric, polishing
RJF2 00Feb29 polishing: removed FREQ ORDER=FREQ for use w/Subset
RJF2 00Mar20 added where, multiple BY_VAR
RJF2 00May11 disabled keep to make WHERE work
RJF2 00May11 exit on NMBRRESP=0
RJF2 00May17 enabled ORDER=FREQ
RJF2 00Nov15 copied in #9 from CHECKALL
RJF2 00Dec22 adding EXCLUDE=1
RJF2 01Feb13 added LIB_OUT parm
RJF2 01May08 added OUT2LIB parm
RJF2 01May19 %IF BY_VAR eq '' %THEN BY_VAR eq '.'
RJF2 01Oct11 fixed copy from LIB_OUT to LIBRARY
RJF2 01Dec06 check exist before copy
RJF2 03Jun11 update for new ARRAY usage parm LIBNAME= 
RJF2 04Mar09 removed parm named libname from macro array 
RJF2 04Apr27 fixed by_value does not have by_var value:
By_Value = &By_Var.;                                   
RJF2 05Mar07 added parm FromProg to data set label
*** .................................... ............................ */
%IF &TESTING %THEN %DO;  
    %LET PRINT=1;
    options   mprint;                         
    %END;
%ELSE %DO;  
      options nomprint;                         
      %END;
%IF "&BY_VAR." = ""  %THEN %DO; 
    %LET BY_VAR =.;                    
    %END;
 
%IF &OUT2LIB
    and "&LIB_OUT." NE "&LIBRARY."
    and %sysfunc(exist(&LIB_OUT..&SERIES.)) %THEN %DO;
proc COPY in      = &LIB_OUT
          out     = &LIBRARY
          memtype = data;
          select    &SERIES.;                                      
          %END;
 
%*1: macro: prepare ARRAY of variables;
%local DIM_VAR; 
%LET DIM_VAR  = 51;
 
%DO I = 1 %TO &DIM_VAR;                 
    %local  VAR&I.;            
    %END;
 
%IF "&BY_VAR." ne "." %THEN %DO;
    %local DIM_BVAR;%LET DIM_BVAR = 10;
    %DO I = 1 %TO       &DIM_BVAR;         
        %local BVAR&I.;            
        %END;
    %ARRAY(BVAR,&BY_VAR.);
    run;                      
    %*IF BY_VAR NE .; 
    %END;
 
%IF       "&LIST" ne "DATA" %THEN       
    %ARRAY(VAR,&LIST.);
 
%ELSE %IF "&LIST" eq "DATA" %THEN %DO;  
    %*use CHECKALL dataset*;
    %IF "&BY_VAR." ne "." %THEN %DO;       
    %*make unique on ValueChr;
    proc FREQ
         data =  &LIBRARY..&SERIES.
         order=  freq;
         tables  ValueChr / noprint
         out  =  SHOWLIST(drop = Count Percent);
         weight  Count;                                              
    %END;
    %ELSE %DO;
        DATA SHOWLIST;
        do until(EndoFile);
           set &LIBRARY..&SERIES.
              (keep = ValueChr)
              end = EndoFile;
           output; 
           end; 
        stop;                                             
        %END;
 
    %*ARRAY(VAR,LIBNAME = WORK;
    %ARRAY(VAR,DATA    = SHOWLIST
              ,VAR     = ValueChr);                  
    %* IF &LIST=DATA; 
    %END;
run;
%* mac-vars created by %ARRAY are local to SHOWCOMB;
 
%*2: macro: concatenate elements into VAR_LIST
            NOTE: LIST is tested below;
%MACRO  VAR_LIST;
%DO I = 1 %TO &DIM_VAR; 
    &&VAR&I. 
    %END;           
%MEND;
%local  VAR_LIST;
%LET    VAR_LIST = %VAR_LIST;
 
%IF "&BY_VAR." ne "." %THEN %DO;
    TITLE&TITLEN. "&SERIES by &BY_VAR";
    %END;
%ELSE                       %DO;
    TITLE&TITLEN. "&SERIES";           
    %END;
TITLE%eval(&TITLEN.+1) "ShowComb: &WHERE.";
%local NMBRRESP; 
%LET NMBRRESP=0;
%*3: data: prepare subset of DATA and create Number for FREQ
     which is the binary-value of all the variables with value = &TRUE;
DATA ZBINNMBR(keep = &ID BinStrng
     %IF "&BY_VAR" ne "." %THEN   &BY_VAR.; );
     array CheckAll {*} &VAR_LIST;
     length  BinStrng $ &DIM_VAR.;
     length Label     $ 40; 
     drop   Label;
     retain  MaxChkd NmbrResp 0;
do until(EndoFile);
   set &LIBRARY..&DATA.
      (
   /******keep = &ID. &VAR_LIST.****************************DELETE BEGIN
    %IF "&BY_VAR" ne "." %THEN      &BY_VAR.;
/***%IF "&SUBSET" ne "." %THEN %DO; 
        &SUBVARS where=(&SUBSET.)   
        %END;
/***%IF "&WHERE." ne "." %THEN %DO;          
        where=(&WHERE.)       
        %END; 
                                                           *****DELETE*/
     where=(&WHERE.)
   ) end  = EndoFile
     nobs = N_Obs ;
 
   N = 0;  
   NmbrChkd = 0;
 
   do I = 1 to dim(CheckAll);
   /**if CheckAll{I} = &TRUE. then do;  
      %*N = sum(N, 2**(&DIM_VAR. - I));
                                                            ****DELETE*/
      if CheckAll{I} &OP. &TRUE. then do; 
         %*N = sum(N, 2**(&DIM_VAR. - I));
         NmbrChkd + 1;
         substr(BinStrng,I,1) = '1';  
         end;
      else substr(BinStrng,I,1) = '0';   
   %*do I *; 
   end;
 
   if NmbrChkd then do;                 
      output;
      MaxChkd = max(MaxChkd,NmbrChkd);
      NmbrResp + 1;
      %*if NmbrChkd*; 
      end;
    %* *do until(EndoFile)*; 
    end;
 
%*3.2 create array of labels of series;
 %local DIM_LBL; 
 %LET DIM_LBL = &DIM_VAR;
 MwLabel = 0;%*save Maximum Width of Label*;
%DO I = 1 %TO &DIM_VAR;
    call label(&&VAR&I.,Label);
    if index(Label,"&TRIMCHAR") then Label = left(substr(Label,
                                          index(Label,"&TRIMCHAR")+1));
    call symput("LBL&I.",trim(left(Label)));
    MwLabel = max(MwLabel,length(Label));                            
    %END;
 
%*3.3 create mac-vars:;%local MWLABEL MAXCHKD N_OBS NMBRRESP PCNTRESP;
 call symput("MWLABEL" ,trim(left(put(MwLabel , 2.))));
 call symput("MAXCHKD" ,trim(left(put(MaxChkd , 8.))));
 call symput("N_OBS"   ,trim(left(put(N_Obs   ,32.))));
 call symput("NMBRRESP",trim(left(put(NmbrResp,32.))));
stop; 
run;
%LET PCNTRESP = %eval(100* &NMBRRESP /&N_OBS);
%IF &TESTING %THEN %DO;
    %put MWLABEL<&MWLABEL.> MAXCHKD<&MAXCHKD.>;
    %put N_OBS<&N_OBS.> NMBRRESP<&NMBRRESP.>  PCNTRESP<&PCNTRESP.>;
    %END;
 
%IF not &NMBRRESP %THEN %DO; 
    %PUT @@SHOWCOMB: NO RESPONSE ;
    %GOTO ENDOMAC;                        
    %END;
 
%*5. proc: FREQ of Number to output dataset;%*prev: tables N;
proc FREQ
     data =   ZBINNMBR;
     /******************************************************DELETE*BEGIN
 %IF          "&ORDER." eq "FREQ" %THEN
     /*order =  freq*/;                                 %*proc closure;;
     /******************************************************DELETE END*/
     tables
 %IF "&BY_VAR." ne "." %THEN %DO;
     %DO I = 1 %TO &DIM_BVAR.;
         &&BVAR&I  *                                    
         %END; 
     %END;
              BinStrng / noprint
     out   =  ZFRQCOMB;
 
%*make N_OBS;%*see also in FXT/FREQXTAB FREQ1VAR;
proc MEANS noprint
     data =  ZFRQCOMB ;
     var     Count;
/****output  sum=Count **********************************DELETE LINE*/
     output  sum=N_Obs
     out  =  N_OBS(drop=_Type_ _Freq_);
 %IF   "&BY_VAR." ne "." %THEN %DO;
     by &BY_VAR.;                                             
     %END;
 
/***********************************************************DELETE BEGIN
%local WIDTH;%LET WIDTH=%length(&N_OBS.);
DATA    N_OBS(drop = Count);
 length N_Obs $ %eval(&WIDTH. +2);
 do until(EndoFile);
  set   N_OBS end = EndoFile;
        N_Obs = 'N=' !! trim(left(put(Count,&WIDTH..)));
  output; end; stop;
/***********************************************************DELETE END*/
%*make N_IDS;
proc FREQ
     data =  ZBINNMBR;
     tables
  %IF "&BY_VAR" ne "." %THEN
      %DO I = 1 %TO &DIM_BVAR.;
          &&BVAR&I  *                                          
          %END;
  /********* &BY_VAR. *;**/
             &ID. / noprint
     out =   FREQ_ID;
 
%IF "&BY_VAR" eq "." %THEN %DO;
    %local N_IDS;
/**OBS(N_IDS);run;DATA N_IDS;Count = &N_IDS;output;stop;%END;DELETE****/
    %NOBS(N_IDS);
    run;
    DATA N_IDS;
    N_Ids = &N_IDS;
    output;
    stop;           
    %END;
%ELSE %DO;
 proc FREQ
      data =  FREQ_ID;
      tables
  %DO I = 1 %TO &DIM_BVAR.;
      &&BVAR&I
      %IF &I lt &DIM_BVAR. %THEN *;                        
      %END;
 
      /****** &BY_VAR *************************************DELETE LINE*/
              / list noprint
      out  =  N_IDS(drop=Percent
                    rename = (Count=N_Ids));                       
      %END;
/*****out  =  N_IDS(drop=Percent);                   %END;DELETE BEGIN**
 
DATA    N_IDS;
 length N_Ids $ %eval(2 + %length(&N_OBS.));
 do until(EndoFile);
  set   N_IDS end = EndoFile;
        N_Ids = 'N=' !! trim(left(put(Count,%length(&N_OBS.).)));
  output; 
  end; 
  stop;
/***********************************************************DELETE END*/
%*6. data: recode FREQ output: convert Number to Combinations
      convert Number to binary string
      convert binary string to user-readable string in var Label & Lbl*
NOTE: long combinations may be truncated: max SAS char width is 200
      noted with asterisk in front of combinations;
DATA ZFRQCOMB;
 drop   Delimitr EmptyCol I
        Lbl1--Lbl&DIM_LBL.
        %IF not &TESTING %THEN BinStrng;
        WLabel WCount WPercent
        WCol1--WCol&&MAXCHKD.;
 length Label    $ 200
        Delimitr $   2
%*prev: BinStrng $ &DIM_LBL.;
  %DO I = 1 %TO &DIM_LBL.; 
      Lbl&I.                                  
      %END;
  %DO I = 1 %TO &MAXCHKD.; 
      Col&I.                                  
      %END;
  $ &MWLABEL.
  Col%eval(&MAXCHKD. + 1)    $ %length(&NMBRRESP.)%*:Count           *;
  Col%eval(&MAXCHKD. + 2)    $ 5                  %*:Pcnt, length end:;;
 array Lbls {*} $ Lbl1-Lbl&DIM_LBL.;
 array Cols {*} $ Col1-Col&MAXCHKD.;
 array WCol {*}   WCol1-WCol&MAXCHKD.;
 
 %* save to create mac-vars*;
 retain WLabel WCount WPercent
  %DO I = 1 %TO &MAXCHKD.;     
      WCol&I.                             
      %END;
      0 ;
 do until(EndoFile);%* - - - - - - - - - - - - - - - - - - - - - - - -*;
  set ZFRQCOMB end = EndoFile;
 
  %* initialize arrays *;
  do I = 1 to dim(Lbls); 
     Lbls{I} = ' ';                             
     end;
  do I = 1 to dim(Cols); 
     Cols{I} = ' ';                             
     end;
 
  Label    = '';
  Delimitr = '';%*change to ',_' after first item*;
 
  %*6.1 change Number to binary string
  loop: change           binary string to Label and Lbl*;
  %*prev: BinStrng = put(N,binary&DIM_LBL..);
  %DO I = 1 %TO &DIM_LBL.;%* - - - - - - - - - - - - - - - - - - - - -*;
      if substr(BinStrng,&I,1) = '1' then do;
         Label    = left(trim(Label)
                    !! Delimitr
                    !! "&&LBL&I.");
         Delimitr = ', ';
         Lbl&I    =    "&&LBL&I." ;    
         end;
      %* DO I =1:&DIM_LBL*; 
      %END;
  if length(Label) = 200 then Label = '*' !! substr(Label,1,199);
  EmptyCol = 1;
  %*fill Cols from Lbls*;
  do I = 1 to dim(Lbls);%* - - - - - - - - - - - - - - - - - - - - - -*;
     if Lbls{I} ne ' ' then do;         
        Cols{EmptyCol} = Lbls{I};
        WCol{EmptyCol}=max(WCol{EmptyCol},
                  length(Cols{EmptyCol}));
        %*avoid error msg: *;
        %* "array subscript out of bounds" *;
        if EmptyCol lt &MAXCHKD. then EmptyCol = EmptyCol + 1;      
        end;
     %* do I=1:dim(Lbls)*; 
     end;
 
  %*copy numeric Count, Percent to character vars: Col*;
  Col%eval(&MAXCHKD. + 1) =      put(Count  ,%length(&NMBRRESP.).0) ;
  Col%eval(&MAXCHKD. + 2) = left(put(Percent,                  5.1));
 
  %*6.2 set-up for mac-vars: widths of variables, note all are retained;
  WLabel   = max(WLabel  ,length(Label));
  WCount   = max(WCount  ,length(left(trim(put(Count  ,8.0)))));
  WPercent = max(WPercent,length(left(trim(put(Percent,5.1)))));
 
  output; 
  %* *do until(EndoFile)*; 
  end;
 
 %*6.3 create mac-vars of widths; %local WLABEL WCOUNT WPERCENT;
 call symput('WLABEL'  ,trim(left(put(WLabel  ,3.))));
 call symput('WCOUNT'  ,trim(left(put(WCount  ,8.))));
 call symput('WPERCENT',trim(left(put(WPercent,8.))));
 %DO I = 1 %TO &MAXCHKD.;
     %local       WCOL&I.;
     call symput("WCOL&I.",trim(left(put(WCol&I.,2.))));              
     %END;
stop;
run;
 
%IF &TESTING %THEN %DO; 
    %*LET LIB_OUT = WORK;
    %PUT @@WLABEL<&WLABEL.>WCOUNT<&WCOUNT.> WPERCENT<&WPERCENT.>;
    %DO I = 1 %TO &MAXCHKD.;  
        %PUT WCOL&I. :: &&WCOL&I.;              
        %END;
    %*%IF &TESTING *; 
    %END;
%IF "&OUT"="." %THEN %LET OUT = &SERIES.;
 
%*local SAVENAME; %*LET SAVENAME = &OUT.CMB /******************DEL BEGIN
/*DATA &LIB_OUT..&SAVENAME.******************************************
%*IF %length(&SAVENAME) gt 8 %THEN %LET SAVENAME=%substr(&SAVENAME,1,8);
                                                               /***END*/
 
%*7. data: save optimized dataset to library;
DATA &LIB_OUT..&OUT.CMB
     (label = "&FromProg.:ShowComb: &Series.=%bquote(&TRUE)");
 attrib    %local         LEN;
           %LET           LEN  = %length(&N_OBS.);
  N_Ids    length =     4
           format =&LEN..0     label = "N Ids"
  N_Obs    length =     4
           format =&LEN..0     label =
                   "N=&NMBRRESP data:&DATA Obs:&N_OBS Resp:&PCNTRESP.%"
  By_Value length = $    12
           format = $char12.       label = "by value"
  ValueChr length = $    12
           format = $char12.       label="&SERIES. Value=%bquote(&TRUE)"
  Label    length = $    &WLABEL.
           format = $char&WLABEL..   label  = "&LBL_LBL"
  Count    length = 4
           format =      &WCOUNT..0   label  = "&LBLCOUNT."
  Percent  format =      &WPERCENT..1 label  = "&LBLPCENT."
           %local         LEN;
           %LET           LEN = %length(&TITLE.);
/***********************************************************************
  Title    length = $    &LEN.
           format = $char&LEN..      label  = "&SERIES. Title"
  _By_var  length = $    1
           format = $char1.          label  = "by_var: &BY_VAR."
  _Subset  length = $    1
           format = $char1.          label  = "subset: &WHERE."
/**********************************************************************/
  %DO I = 1 %TO &MAXCHKD.;
  Col&I.   length = $    &&WCOL&I.
           format = $char&&WCOL&I... label  = "Col &I."            
           %END;
  Col%eval(&MAXCHKD. + 1)
           length = $    %length(&NMBRRESP.)
           format = $char%length(&NMBRRESP.).
           label  = "Count Col %eval(&MAXCHKD. + 1)"
  Col%eval(&MAXCHKD. + 2)
           length = $    &WPERCENT.
           format = $char&WPERCENT..
           label  = "Percent Col %eval(&MAXCHKD. + 2)"
  ;
%If "&By_Var." eq "." %then %do;
    retain By_Value ' ';         
    %end;
do until(EndoFile);%* -------------- ---------------------------- *;
   merge  N_IDS
          N_OBS
          ZFRQCOMB
          end = EndoFile;
   %IF "&BY_VAR." ne "." %THEN %DO;
       by    &BY_VAR.;
       ValueChr = &ValueChr.;
       By_Value = &BY_Var.;                                               
       %end;
   Label    = translate(Label,'!',"'");%*change <!> back to squote*;
   output;
   %* *do until(EndoFile)*; 
   end;
stop;
%IF    "&BY_VAR." ne "."
    or "&ORDER."  eq "FREQ" %THEN %DO;
proc SORT data =  &LIB_OUT..&OUT.CMB;
          by
    %IF "&BY_VAR." ne "." %THEN &BY_VAR.;
                  descending Count;                                
                  %END;
 
%*8. proc: if wanted PRINT and/or CONTENTS of saved data set;
%IF &PRINT %THEN %DO;%* - - - - - - - - - - - - - - - - - - - - - - --*;
    proc PRINT   /*double*/ noobs label
         data =  &LIB_OUT..&OUT.CMB;
    %*   data =  &LIB_OUT..&SAVENAME **********************************;
         var     N_Ids N_Obs
    %IF         &TESTING       %THEN BinStrng;
    %IF         &WLABEL gt 100 %THEN Col:    ;
    %ELSE                            Label Count Percent;%*var clos;;
    %IF    &BY_VAR." ne "." %THEN %DO;
        by &BY_VAR.;
        id &BY_VAR.;
        %IF         &FORMAT. ne %THEN %DO;
            format  &BY_VAR. &FORMAT.;                                    
            %END;
        %ELSE                         %DO;
            format  _ALL_;                                    
            %END;
       %*%IF BY-VAR not missing*; 
       %END;
    %* *%IF PRINT ; 
    %END;
 
%IF &TESTING %THEN %DO; 
    proc CONTENTS data =  &LIB_OUT..&OUT.CMB;  
    %END;
 
%*9: copy output back to &LIBRARY.;
%*RJF2 01Jul19 error occurs when LIBRARY AND LIB_OUT have same values;
%*LET LIBRARY=LIBRARY;
%*LET LIB_OUT=LIBRARY;
 
%IF     "&LIBRARY" ne "LIBRARY"
    and "&LIB_OUT." NE "&LIBRARY." %THEN %DO;
    proc COPY in      = &LIB_OUT.
              out     = &LIBRARY.
              memtype = data;
              select    &OUT;                                         
    %END;
 
%IF &OUT2LIB %THEN %DO;
    proc DATASETS library = &LIBRARY
                  memtype = data
                            nolist;
                  delete    &SERIES.;
                  quit;                                                
    %END;
 
%ENDOMAC:run; 
options nomprint ;*notes;
run; 
%MEND SHOWCOMB;

Testing Programs

 /*SHOWCOMB test data, enable by ending this line with slash (/)  - --**/
%LET DATA_SET = SURVEY2;
options details mprint nocenter;
*libname LIBRARY 'c:\saswinpd\sasuser';*default*;
 
*Step 1: label the variables;
 
DATA LIBRARY.SURVEY2;
label
Q04A     = "Q04a: Primary Classification"
Q04BCOM  = "Q04B: Blood Bank - Community"
Q04BREG  = "Q04B: Blood Bank - Regional"
Q04BPLA  = "Q04B: Blood Bank - Blood/Plasma center"
Q04BARC  = "Q04B: Blood Bank - American Red Cross"
Q04BPRI  = "Q04B: Blood Bank - Privately owned"
Q04BMIL  = "Q04B: Blood Bank - Military (Federal)"
Q04BHOS  = "Q04B: Blood Bank - Hospital blood bank"
Q04BOTR  = "Q04B: Blood Bank - Other"
Q04CCIT  = "Q04C: Hospital - City"
Q04CCNT  = "Q04C: Hospital - County"
Q04CSTA  = "Q04C: Hospital - State"
Q04CDIS  = "Q04C: Hospital - District"
Q04CCOM  = "Q04C: Hospital - Community"
Q04CREG  = "Q04C: Hospital - Regional"
Q04CMIL  = "Q04C: Hospital - Military (Federal)"
Q04CVET  = "Q04C: Hospital - Veterans Administration"
Q04CPRI  = "Q04C: Hospital - Privately owned"
Q04CUNI  = "Q04C: Hospital - University"
Q04CHMO  = "Q04C: Hospital - HMO owned & operated"
Q04CREL  = "Q04C: Hospital - Religious associated"
Q04COTR  = "Q04C: Hospital - Other"
;
input
@ 1 Q04A    $char1.
@ 3 Q04BCOM $char1.
@ 4 Q04BREG $char1.
@ 5 Q04BPLA $char1.
@ 6 Q04BARC $char1.
@ 7 Q04BPRI $char1.
@ 8 Q04BMIL $char1.
@ 9 Q04BHOS $char1.
@10 Q04BOTR $char1.
@12 Q04CCIT $char1.
@13 Q04CCNT $char1.
@14 Q04CSTA $char1.
@15 Q04CDIS $char1.
@16 Q04CCOM $char1.
@17 Q04CREG $char1.
@18 Q04CMIL $char1.
@19 Q04CVET $char1.
@20 Q04CPRI $char1.
@21 Q04CUNI $char1.
@22 Q04CHMO $char1.
@23 Q04CREL $char1.
@24 Q04COTR $char1.
;cards;
B 10000010 0000000000000
H 00000000 0000110000000
H 00000000 1100000000000
H 00000000 0000110000000
B 11000000 0000000000000
H 00000000 0000000100000
B 11000000 0000000000000
H 00000000 0000110000000
B 10000000 0000000000000
B 10000010 0000000000000
H 00000000 0000000100000
H 00000000 0000000000000
H 00000000 0000000000000
B 10000010 0000000000000
H 00000000 0000000100000
H 00000000 1100000000000
H 00000000 0000110000000
H 00000000 0000000100000
B 10000010 0000000000000
;
 
*Step 2: save CONTENTS of data set;
proc CONTENTS data = LIBRARY.&DATA_SET. noprint
              out  = LIBRARY.CONTENTS(keep = Name);
 
*Step 3: create and save data sets with series of variables;
data LIBRARY.VQ04
     LIBRARY.VQ04B
     LIBRARY.VQ04C;
 set LIBRARY.CONTENTS;
 if substr(Name,1,3) = 'Q04'  then output LIBRARY.VQ04;
 if substr(Name,1,4) = 'Q04B' then output LIBRARY.VQ04B;
 if substr(Name,1,4) = 'Q04C' then output LIBRARY.VQ04C;
 
 * end CHECK-ALL set-up ***********************************************;
*Step 4: run %CHECKALL:
         output dataset contains only variables used in the series,
         used as input for SHOWCOMB;
%CHECKALL(Q04);
%CHECKALL(Q04B,TRIMCHAR=-);
%CHECKALL(Q04C,TRIMCHAR=-);
 
 * end SHOWCOMB SOP set-up ********************************************;
 
%SHOWCOMB(Q04);
%SHOWCOMB(Q04,BY_VAR=Q04A,TRIMCHAR=-);
%SHOWCOMB(Q04B,TRIMCHAR=-);
%SHOWCOMB(Q04C,TRIMCHAR=-);
 
*show intermediate vars*:;
*SHOWCOMB(Q04B,BY_VAR=Q04A,TRIMCHAR=-,TESTING=1);
*user-provided list:;
*SHOWCOMB(Q04B,LIST = Q04BCOM Q04BPLA Q04BPRI Q04BREG
,TRIMCHAR=-);
*SHOWCOMB(Q04,TRIMCHAR=-);
 **TBIN64.SAS - - - - - - - - - - - - - - - - - - - - - - - - - - - -
RJF2: 96Oct10 SHOWCOMB: MAX VARS = 50 in PC SAS
      test accuracy of char to numeric conversion of binary64
      to check accuracy of algorith in SHOWCOMB
 /* .................................... ......................... */
 /*********************************************************************
data _null_;
 
length BinStrng StrngBin $ 64;
BinStrng = repeat('0',63);
do i = 64 to 1 by -1 ;
 substr(BinStrng,I,1) = '1';
 N        = input(BinStrng,binary64.);
 StrngBin =   put(N       ,binary64.);
 if BinStrng ne StrngBin then
  put I= N= comma32. / BinStrng= / StrngBin=;
 end;
stop;
run;
 /* .................................... .............. end test data *
 /*********************************************************************
%LET STUDY=EIA;
*LET STUDY=WBL;
%LET BATCH = P;
%LET KEY   =0;
%ATITLES(&SHIPMENT.,&PHASE.,&STUDY.,USEBATCH=1);
 
proc CONTENTS data = LIBRARY.&DATA_SET. noprint
              out  = VQC&STUDY.(keep = Name NPos);
 
proc SORT; by NPos;
 
DATA LIBRARY.VQC&STUDY.;
 do until(EndoFile);
 set         VQC&STUDY.(drop = NPos)
  end = EndoFile;
 where index(Name,'QC');
 output; end; stop;
 /*********************************************************************
%*,SUBVARS=CodeNonR;
%CHECKALL(QC&STUDY.,DATA=&DATA_SET.,TRUE='X'
,WHERE   =CodeNonR eq ' '
,TITLE   =QC usage &STUDY.
,TITLEN  =&TITLEN.
);
%*setup for SHOWCOMB: shorten labels to var-name;
proc DATASETS nolist;
              copy   in  = LIBRARY
                     out = WORK;
              select QC&STUDY.;
              quit;
 
%let DIM_VAR = .;
%*ARRAY(VAR,data=LIBRARY.VQC&STUDY.,var=Name);
%*ARRAY(VAR,LIBNAME = LIBRARY;
%ARRAY(VAR,DATA    = LIBRARY.VQC&STUDY.
          ,VAR     = Name
          ,_Global_=1);
run;
%macro LBL_LIST;%local I;
 %DO I = 1 %TO &DIM_VAR; &&VAR&I. =
     "%substr(&&VAR&I.,4,%length(&&VAR&I.)-3)" %END;%MEND;
 
%macro VAR_LIST;%local I;
 %DO I = 1 %TO &DIM_VAR; &&VAR&I.              %END;%MEND;
 /*********************************************************************
 
DATA WORK.&DATA_SET.;
 label %LBL_LIST;
 do until(EndoFile);
  set LIBRARY.&DATA_SET.(keep = CodeNonR Subset MpepNum RgnMnf FormNmbr
  %VAR_LIST )
  end = EndoFile;
  *Subset = RgnMnf;
  where CodeNonR eq ' ';
  output;                end;
stop;
 /*********************************************************************
 
%*,SUBVARS=CodeNonR;
%SHOWCOMB(QC&STUDY.,DATA=&DATA_SET.
,LIBRARY =WORK
,TRUE    ='X'
,BY_VAR  = FormNmbr
,WHERE   =CodeNonR eq ' '
,TITLE   =QC usage &STUDY combinations.
,TITLEN=&TITLEN.
,TESTING=1
);
%*,SUBVARS=CodeNonR;
%SHOWCOMB(QC&STUDY.,DATA=&DATA_SET.
,LIBRARY =WORK
,TRUE    ='X'
,SUBSET  =CodeNonR eq ' '
,TITLE   =QC usage &STUDY combinations.
,TITLEN  =&TITLEN.
,TESTING =1
);
 /*********************************************************************/

References

--Ronald_J._Fehd macro.maven == the radical programmer 16:18, 12 June 2012 (EDT)