/*

/ Program   : misscnt.sas
/ Version   : 3.1
/ Author    : Roland Rashleigh-Berry
/ Date      : 09-Apr-2013
/ Purpose   : To create a list of variables and their missing value count
/ SubMacros : %nvarsc %nvarsn
/ Notes     : Version 2.0 and beyond is not compatible with version 1.0 as it
/             uses named parameters keep= and drop= and no longer the positional
/             parameter "drop" that was used in version 1.0.
/
/             It is not possible to implement this as a function-style macro due
/             to the data step boundary so the results will be written to a
/             global macro variable which by default is named "_miss_". What you
/             do with the list created is entirely up to you. The variables with
/             a non-zero missing count will be listed directly followed by an
/             equals sign directly followed by the missing value count.
/             Variables with zero missing values are not listed.
/
/             You can define both a keep list of variables and a drop list of 
/             variables. If you define both then only the keep list will be used
/             and the drop list will have no effect.
/
/ Usage     : %misscnt(dsname,keep=&keeplist);
/             %misscnt(dsname,drop=&droplist);
/             %misscnt(dsname,dsout=_misscnt);
/===============================================================================
/ PARAMETERS:
/-------name------- -------------------------description------------------------
/ ds                (pos) Dataset to analyze (unmodified)
/ keep=             List of variables to keep
/ drop=             List of variable to drop (gets overridden by keep= if
/                   keep= is specified)
/ globvar=_miss_    Name of the global macro variable to contain the list of
/                   variables with their missing count.
/ dsout=            Optional output dataset containing fields "name" and "count"
/===============================================================================
/ AMENDMENT HISTORY:
/ init --date-- mod-id ----------------------description------------------------
/ rrb  13Feb07         "macro called" message added
/ rrb  04May11         Code tidy
/ rrb  03Dec12         Keep= and drop= named parameters added (v2.0)
/ rrb  06Dec12         NOTEs disabled and changed so that if a character
/                      variable equates to a single period when left aligned
/                      then it will also be regarded as a missing value (v2.1)
/ rrb  08Apr13         dsout= parameter added (v3.0)
/ rrb  09Apr13         Make the dsout= dataset a zero observation dataset if the
/                      missing count is zero for all variables (v3.1) 
/===============================================================================
/ 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: misscnt v3.1;

%macro misscnt(ds,
               keep=,
               drop=,
               globvar=_miss_,
               dsout=
              );

  %local i bit dsname nvarsn nvarsc savopts;

  %let savopts=%sysfunc(getoption(notes));
  options nonotes;

  %global &globvar;
  %let &globvar=;
  %let dsname=&ds;

  %if %length(&drop) or %length(&keep) %then %do;
    %let dsname=_misscnt;
    data _misscnt;
      set &ds
      %if %length(&keep) %then %do;
        (keep=&keep)
      %end;
      %else %if %length(&drop) %then %do;
        (drop=&drop)
      %end;
      ;
    run;
  %end;


  %let nvarsn=%nvarsn(&dsname);
  %let nvarsc=%nvarsc(&dsname);

  data _null_;
    %if &nvarsn GT 0 %then %do;
      array _nmiss {&nvarsn} 8 _temporary_ (&nvarsn*0);
    %end;
    %if &nvarsc GT 0 %then %do;
      array _cmiss {&nvarsc} 8 _temporary_ (&nvarsc*0);
    %end;
    SET &dsname END=LAST;
    %if &nvarsn GT 0 %then %do;
      array _num {*} _numeric_;
    %end;
    %if &nvarsc GT 0 %then %do;
      array _char {*} _character_;
    %end;
    %if &nvarsn GT 0 %then %do;
      do i=1 to &nvarsn;
        if _num(i) EQ . then _nmiss(i)=_nmiss(i)+1;
      end;
    %end;
    %if &nvarsc GT 0 %then %do;
      do i=1 to &nvarsc;
        if left(_char(i)) IN (' ' '.') then _cmiss(i)=_cmiss(i)+1;
      end;
    %end;
    if LAST then do;
      %if &nvarsn GT 0 %then %do;
        do i=1 to &nvarsn;
          if _nmiss(i) GT 0 then call execute('%let &globvar=&&&globvar '||
            trim(vname(_num(i)))||'='||compress(put(_nmiss(i),11.))||';');
        end;
      %end;
      %if &nvarsc GT 0 %then %do;
        do i=1 to &nvarsc;
          if _cmiss(i) GT 0 then call execute('%let &globvar=&&&globvar '||
            trim(vname(_char(i)))||'='||compress(put(_cmiss(i),11.))||';');
        end;
      %end;
    end;
  run;


  %if "&dsname" EQ "_misscnt" %then %do;
    proc datasets nolist;
      delete _misscnt;
    quit;
  %end;


  %if %length(&dsout) %then %do;
    data &dsout;
      length name $ 32 count 8;
      if 0=1 then output;
      %let i=1;
      %let bit=%scan(&&&globvar,&i,%str( ));
      %do %while(%length(&bit));
        name="%scan(&bit,1,=)";
        count=%scan(&bit,2,=);
        output;
        %let i=%eval(&i+1);
        %let bit=%scan(&&&globvar,&i,%str( ));
      %end;
    run;
  %end;

  options &savopts;

%mend misscnt;