Macro Invalid for Data Review

From sasCommunity
Jump to: navigation, search

2013-Jan-11 see another example in Macro_CallText

%Invalid: a data review macro using Proc Format option other = Invalid to identify and list outliers, Ronald Fehd

NESUG2001.008:

http://www.pace.edu/nesug/proceedings/nesug01/at/At1008.pdf

PharmaSUG2004.006:

http://www.lexjansen.com/pharmasug/2004/DataManagement/DM06.pdf

Macros

Note: ~600 lines of code, which includes test program!

Macro Invalid

 /*     name: Invalid.sas
------------: user requirements:
 description: provide summary reports and detail listings
              of variables whose formatted values eq "&Invalid."
     purpose: method for data review
------------: program specifications:
program type: routine
    SAS type: macro procedure
              list processor: calls another macro subroutine repeatedly
        note: user-written formats must have other = "&Invalid."
     input  : data set name, id-list
     process: writes calls to subroutine Check4Invalid contained here
     output : writes report of Invalid values to PRINT or file
NOTE: uses macros Add2Item, Array, and Nobs
NOTE: uses global macro variable Invalid
NOTE: this warning is acceptable
WARNING: Variable VALUENUM has different lengths on BASE and DATA files
                                                   (BASE 8   DATA 4).
usage:
%Invalid();           %*list formats: &Library.._ALL_ and &FmtLib;
 
output:
Invalid: compare formats in Data:Library._ALL_ and FmtLib:Library
 
                     Fmt
format       Data    Lib    Ok
 
$NOTUSED.      0      1     ..
$THRE_5_.      1      1     OK
2.             1      0     ??
NO_OTHER.      1      0     ??
ONE_3_.        1      1     OK
TWO_4_.        1      1     OK
 
 
 
%Invalid(Data = Data);%*list formats: &Library..Data  and &FmtLib;
%Invalid(Data     = Data
        ,IdList   = ID0            one ID
        ,IdList   = ID1 ID2        two IDs
        ,PrntFile = FileName.sas   write report to file
        );
KeyWords: %Array %Nobs "invalid values" "data review" "data checking"
          invalid outlier Format FmtLib other
Author: Ronald Fehd,           */
%put *****************************************************;
%put Invalid, version 2, 2007 Ronald J. Fehd;
%put *********************************************;
%MACRO invalid /*----------------------- ---------------------------- */
        (Data     = Library._All_ /*data set name                     */
        ,IdList   = .      /* list of primary-key(s) == by-vars       */
        ,LibName  = LIBRARY/*libname of DATA                          */
        ,Library  = LIBRARY/*libname of DATA                          */
        ,FmtLib   = LIBRARY/*libname of catalog FORMATS               */
        ,FmtName  = FORMATS/*name of format catalog                   */
        ,OtherLabel = Invalid/*label of other = '' in value statements  */
        ,LrecL    =80/* temp file width                               */
        ,Prntfile = PRINT  /*output destination, default = PRINT      **
                           /*may be fileref or 'external-file'        */
        ,Prntlist = PRINT  /*turn off detail listing w/PrntList=NULL  */
        ,Details  = 1/*?Print details? used when only summary wanted  */
        ,Summary  = 1/*?Print any of SUMMARY report(s)?               */
        ,SmryIds  = 1/*?Print Freq of IDS?                            */
        ,SmryName = 1/*?Print detail  of variables, by name?          */
        ,SmryVars = 1/*?Print Freq of variables with invalid?         */
        ,Testing  = 0/*?enable test msg and prints?                   */
        ,TestSuite= 0/*?disable PrintTo for use by TestSuite not works*/
        ,Titlen   = 2/*Title line #                                   */
)/des = 'method to list INVALID values in data set'
 /* ** store /*must have OPTIONS ...*/
;/**change notes
RJF2 99Feb25 begun: cutNpaste from COMPARWS
RJF2 00Mar09 added /store
RJF2 01Jan25 polishing for SESUG
RJF2 02Oct17 added PrintTo as in COMPARWS
RJF2 03Mar18 added COMMA to exclude list
RJF2 03Mar18 need to Print vars with formats in data but not in catalog
RJF2 04Jan23 PharmaSUG polishing Jan27 added Add2Item
RJF2 04Mar15 todo: put brief summary in titles 6:10
RJF2 05Feb14 remove Add2Item
             per Richard DeVenezia to SAS-L on 2005-Feb-10
%Let text     = XX YY ZZ AA;
%Let inclause = "%sysfunc(TranWrd(&Text,%str( )," "))";
move to %local
,List_Ids     = %Add2Item(list = &IdList)
         where        Name in (&List_Ids.)
          tables   %Add2Item(List =&IdList,separated_by =*,left=,right=)
%local List_Ids ListXtab;
  %Let List_Ids = "%sysfunc(TranWrd(&IdList,%str( )," "))";
  %Let ListXtab =  %sysfunc(TranWrd(&IdList,%str( ),*));
RJF2 05Feb14 removed Add2Item
RJF2 07May25 polishing for upload to sasComm.wiki
%Let LibName = %upcase(&LibName.) ;
%Let Library = %upcase(&Library.) ;%Let Data     = %upcase(&Data.);
%Let Prntfile= %upcase(&Prntfile.);%Let Prntlist = %upcase(&Prntlist.);
*** .................................... .............................*/
%**todo: data is two-level: deconstruct into libname and memname;
%let LibName = WORK;
%let MemName = %upcase(&Data.);
%if %index(&Data.,.) %then %do;
    %let LibName = %upcase(%scan(&Data.,1,.));
    %let MemName = %upcase(%scan(&Data.,2,.));
    %end;
 
%local SQLprint;%let SQLprint     =noprint;
%let Testing = %eval(   &Testing.
                     or %sysfunc(getoption(ECHOAUTO)) eq ECHOAUTO
                     or %sysfunc(getoption(MPRINT)  ) eq MPRINT
                     or %sysfunc(getoption(SOURCE2) ) eq SOURCE2
                     or %sysfunc(getoption(VERBOSE) ) eq VERBOSE);
%if &Testing. %then %let SQLprint = print;
 
%** output: if IdList blank: Print list of formats w/Invalid  exit;
%if "&IdList." eq "." %then %do;%* ----- ---------------------------- *;
PROC SQL &SQLprint.;
         create table Contents as
         select distinct Format
         from   Dictionary.Columns
         where  LibName eq "&LibName."
                %if "&MemName." ne "_ALL_" %then
           and  MemName eq "&MemName."          ;
           and         Format  ne ' '
           and  substr(Format,1,5) not in ('$CHAR','COMMA')
         order  by     Format;
         quit;
 
PROC Format library =&FmtLib.
            cntlout = FmtLib (keep   = FmtName Label Type
                              where  =(Label   = "&OtherLabel.")
                              rename =(FmtName = Format    ) );
 
PROC Sort   data    = FmtLib nodupkey;
            by        Format;
 
DATA FmtLib(drop = Label Type);
do   until(EndoFile);
set  FmtLib end = EndoFile;
if   Type = 'C' then Format = '$' !! Format;
if   substr(Format,1,length(Format)) ne '.'
     then   Format = trim(Format) !! '.';%*v9 SQL Contents has suffix=.;
output;
end;
stop;
 
PROC Sort data = FmtLib;
          by     Format;
 
DATA  FmtLib;
do    until(EndoFile); %* --------------- *;
merge Contents(in = haveC)
      FmtLib  (in = haveF)   end = EndoFile;
by    Format;
Data   = HaveC;
FmtLib = HaveF;
select; when(haveC and     haveF) Ok = 'OK';
        when(haveC and not haveF) Ok = '??';
        otherwise                 Ok = '..';
        end;
output;
end;    %*do until(EndoFile) ............ *;
stop;
 
PROC Print    data = FmtLib noobs;
              Title&TitleN. "Invalid: compare formats in Data:"
                            "&LibName..&Data. and FmtLib:&FmtLib.";
PROC Datasets library  = Work nolist;
              delete     Contents FmtLib (memtype = data);
              quit;
%RETURN; %*GoTo Exit; %*V8;
%end;%* %if IdList eq .......................... .................... *;
 
%** assert: required parms not blank;
%if "&Data." eq "" or "&IdList." eq "" %then %do;
    %put ERR%str()OR: &SysMacroName. missing required parameters:
         Data:(&Data.) IdList:(&Idlist.);
    %RETURN;%*GOTO EXIT;%*v8;
    %end;
 
%** assert: exist required data set;
%local   ExstData; %let ExstData = %sysfunc(exist(&Data.));
%if not &ExstData. %then %do;
    %put ERR%str()OR: &SysMacroName. not exist(Data=&Data.);
    %RETURN;%*GOTO EXIT;%*v8;
    %end;
 
%** assert prep: get nobs of for err-check;
%local DataNobs; %let DataNobs = %Nobs(data = &Data.   );
run; ****Title&TitleN. "comparison of &Base. and &Compare. Obs:&Basenobs.";
 
%** assert: data sets have obs;
%if not &DataNobs.     %then %do;  DATA _Null_;
                                   file &PrntFile.;
                            put "*err%str()or: comparison terminated" /
    %if not &BaseNobs. %then       "***no obs in &Base.;"             ;
                                   ;%*closure: put stmnt;
    %RETURN;%*GOTO EXIT;%*v8;
    %end;
 
 
%local DimId   ;%let DimId        = 0;%* how many IDs?       ;
%local I       ;%let I            = 0;%* loop index        ;
%local LenLabel;%let LenLabel     =60;%* in output data set;
%local LenName ;%let LenName      = 0;%* max(length(Contents.Name));
%local LenVchr ;%let LenVchr      = 0;%* max(Contents.Length) ;
%local List_Ids;*let List_Ids     = ;%*Add2Item(list = &IdList)
%local MaxMsg  ;%let MaxMsg       =72;%* RevuCels. and ItemList.Message width;
%local Nobs    ;%let Nobs         = 0;
%local DataNobs;%let DataNobs = 0;
%local DataVars;%let DataVars = 0;
%local DataCels;%let DataCels = 0;
%local NvldIds ;%let NvldIds  = 0;
%local NvldVars;%let NvldVars = 0;
%local NvldCels;%let NvldCels = 0;
%local PcntNobs;%let PcntNobs = 0;
%local PcntVars;%let PcntVars = 0;
%local PcntCels;%let PcntCels = 0;
%local ValidVarName;%let ValidVarName = %sysfunc(getoption(ValidVarName,keyword));
 
%Let    ValidVarName = &ValidVarName;%*resolve;
Options ValidVarName = UPCASE;%*align CONTENTS & user-supplied IdList;
%Let FmtName = %upcase(&FmtName.);
%Let IdList  = %upcase(&IdList.) ;%*Contents.Name is upcase *;
 
%**Else If IdList not Blank then produce report;
%** does Format $Invalid exist?  because of previous use of macro?;
%*func catalog-exist(LibName.MemName.ObjName.ObjType);
 
%if not %sysfunc(cexist(WORK.FORMATS.INVALID.FORMATC)) %then %do;%*---*;
%** get all formats which have Label = Invalid;
PROC Format library =&FmtLib
            cntlout = Format_Cntl(keep  = FmtName Label Type
                                  where = (Label = "&Invalid."));
%** if none w/Label = Invalid  exit;
%let Nobs = %Nobs(data = Format_Cntl);run;
%if not &Nobs. %then %do;  %put no formats with label=<&Invalid.>;
                          %GoTo EXIT;
    %end;
 
%** make PROC Format CntlIn data set for value $Invalid;
DATA   Format_Cntl           (drop=Type);
retain Label   '1'           %*one==True;
       FmtName '$Invalid'
       HLO     ' ';
do     until(EndoFile);
set    Format_Cntl (keep   =  FmtName   Type
                    rename = (FmtName = Start))
                    end    =  EndoFile;
if Type = 'C' then Start = '$' !! Start;
Start = trim(Start) !! '.';%*kludge to match SQL Contents;
output;
end;  %*do until(EoF);
HLO   = 'O';                 %*Oh==Other;
Label = '0';                 %*zero==False;
Start = '**OTHER**';
output;
stop;
 
%** make Format value $Invalid;
PROC Format cntlin  = Format_Cntl
            library = Work;
    %end;%* %if not cexist ............. ............................ *;
 
%** make Contents for macro calls and Array of Id(s) attribute;
PROC SQL &SQLprint.;
         create table Contents as
         select       Name ,Length ,Type ,Format ,VarNum
         from         Dictionary.Columns
         where        LibName eq "&Library."
                  and MemName eq "&Data.";
         create table List_Vars as select * from Contents
         where        input(put(Format,$Invalid.),1.);
         quit;
%if not &SqlObs. %then %do;%put @@data &Data has no formats w/&Invalid.;
                           %GoTo EXIT;
    %end;
 
%** read Contents: write macro calls + make Array of Id(s) attributes;
PROC SQL &SQLprint.;
         select    '%Chk4Nvld(Library='
                !! "&Library.,data=&Data.,IdList=&IdList."
                !! ',Var='    !! trim(Name)
                !! ',Type='   !! trim(Type)
                !! ',Format=' !! trim(Format)
                !! ',VarNum=' !! put(VarNum,3.) !! ');'
         into  :List separated by ' '
         from   List_Vars
        ;select max(length(Name)) into :LenName from List_Vars
        ;select max(Length)       into :LenVchr from List_Vars
        ;create table List_Ids  as select * from Contents
         where        Name in (&List_Ids.)
        ;quit; %Let DimId = &SqlObs;
 
%** make Array of Id(s) and attributes;
%do I = 1 %to &DimId;
    %local Ids&I. Type&I. Q&I. Len&I.;
    %end;
%Array(Ids ,&IdList.);
 /**********************************************************************
 RJF2 04Mar09 removed parm named libname from macro array
%Array(Type,data  = List_Ids ,var   = Type
           ,case  = case Type when 'num' then ' ' else '$' end);
%Array(Q   ,data  = List_Ids ,var   = Type
           ,case  = case Type when 'num' then ' ' else "'" end);
 /*********************************************************************/
%Array(Type,data  = List_Ids
           ,var   = case Type when 'num' then ' ' else '$' end);
%Array(Q   ,data  = List_Ids
           ,var   = case Type when 'num' then ' ' else "'" end);
%*          ,var   = case Type when 'num' then ' ' else '' end);
%Array(Len ,data  = List_Ids
           ,var   = Length);run;
 
%** prepare empty data set for PROC Append;
DATA   RevuCels        (label = 'RevuCels');
if 0   then
set    &Library..&Data.(keep  = &IdList.
                        obs   = 0         );%*copy structure;
length Msg      $ &MaxMsg.   %*Message;
       VarNum      4         %*integer prev:Item;
       Name     $ 32         %*variable name;
       Type     $  4         %*note: SQL Contents.Type is char;
       ValueChr $ &LenVchr.  %*char variable value;
       ValueNum    8         %*WARN: different lengths on BASE & DATA;
       ;stop;
 
DATA   ItemList        (label = 'List of items of DRexceptItem');
length VarNum                               %*integer prev:Item;
       N_Obs      4                         %*integer;
       Msg     $  &Maxmsg.;
       stop;
run;  &List.%*execute Chk4Nvld;
 
%let DataNobs = %Nobs(data = &Library..&Data.);
%let NvldCels = %Nobs(data = RevuCels);run;
 
Title&TitleN. "invalid values in &Library..&Data. Obs:&DataNobs.";
 
%if not &NvldCels           %then %do; DATA _Null_;
                                       file &PrntFile;
                                       put '***no invalid values;';
    %if &PrntFile. ne PRINT %then %do; file PRINT;
                                       put '***no invalid values;';
        %end;
                                      %GoTo EXIT;
    %end;
%** make values used in summary;
PROC Freq data   =  RevuCels;    %*do cross-tab of IDs;
          tables   %Add2Item(List =&IdList,separated_by =*,left=,right=)
                 / list noprint
          out    = Invalid_Ids;
          tables   Name
                 / list noprint
          out    = Invalid_Vars;
 
%** make summary percentages;
%let NvldIds  = %Nobs(data = Invalid_Ids );
%let NvldVars = %Nobs(data = Invalid_Vars);
%let DataVars = %Nobs(data = Contents    );run;
 
%Let DataCels = %eval(&DataNobs * &DataVars);
%MakePcnt(PcntNobs,NvldIds ,DataNobs);
%MakePcnt(PcntVars,NvldVars,DataVars);
%MakePcnt(PcntCels,NvldCels,DataCels);
 
%** print desired summary report(s);
%if &Summary. %then %do;%* ------------------------------------------ *;
OPTIONS pageno = 1 FormDlim = ' '; run;
%*If not &TestSuite *then *do;
PROC PrintTo print = &PrntFile. new;%*end;
 
DATA _Null_;
file PRINT noprint; put "  /* &PrntFile." ;
retain           C1 12           C2 22           C3 32;
put "*Brief  ;";
put "*Summary:" @C1"Ids"        @C2"Vars"       @C3"Cells      ;";
put "*** Data:" @C1"&DataNobs"  @C2"&DataVars"  @C3"&DataCels  ;";
put "*Invalid:" @C1"&NvldIds"   @C2"&NvldVars"  @C3"&NvldCels  ;";
put "*** Pcnt:" @C1"&PcntNobs%" @C2"&PcntVars%" @C3"&PcntCels% ;"; stop;
 
PROC Print data = ItemList noobs;
           sum    N_Obs;
           TITLE%eval(&TitleN.+1) "summary ItemList: occurrences of "
           "variables with formats w/other=&Invalid.";
 
%if &SmryIds %then %do;
PROC Print data = Invalid_Ids;
           sum    Count;
           TITLE%eval(&TitleN.+1) "summary IdList: "
           "invalid<&NvldIds.> / data<&DataNobs.> = &PcntNobs.%";
    %end;
%if &SmryVars %then %do;
PROC Print data = Invalid_Vars;
           sum    Count;
           TITLE%eval(&TitleN.+1) "summary VarList: "
           "invalid<&NvldVars.> / data<&DataVars.> = &PcntVars.%";
    %end;
%if &SmryName %then %do;
PROC Sort  data = RevuCels;
           by     Name Value: &IdList.;
PROC Print data = RevuCels; %*label;
           var    Value: &IdList.;
           by     Name;
           id     Name;
           TITLE%eval(&TitleN.+1) "detail of Values, by Var-Name "
           "invalid<&NvldCels.> / data<&DataCels.> = &PcntCels.%";
     %end;
 
DATA _Null_;
file PRINT noprint;put '-------------- details -------------- */'; stop;
%*If not &TestSuite *then *do;
PROC PRINTTO;%*off;          %*end;
run;
%end;%* ..................................... ......... %if &Summary *;
 
%if &Details. %then %do;%* ------------- ---------------------------- *;
%** write corex to PrntFile; OPTIONS pageno = 1;
TITLE&TitleN. "invalid values in &Data. Obs:&DataNobs.";
 
PROC Sort data = RevuCels;
          by     &IdList.;
 
DATA   _Null_;
file   &PrntFile
%if    &PrntFile. ne PRINT %then %do;
       lrecl = &LrecL. pad mod n = 1 noprint %end;      ;%*file closure;
retain B -1                %*move pointer back one column for character;
       Q "'";              %*single quote;
do     until(EndoFile);
set    RevuCels end = EndoFile;
by     &IdList.;
if first.&&Ids&DimId then do;
                            put "*;if &Ids1 = &Q1"   &Ids1  +B  "&Q1. "
   %if &DimId. ge 2 %then
   %do I = 2 %to &DimId.; " and &&Ids&I = &&Q&I." &&Ids&I +B "&&Q&I. "
       %end;                " then do;";             %*if first.*;
   end;
select(Type);             %Let LenName = %eval(2 + &LenName + 2);
   when('num' ) put '* ' Name @&LenName '=' +1      ValueNum      ';' @;
   when('char') put '* ' Name @&LenName '=' +1 Q +B ValueChr +B Q ';' @;
   otherwise;
end; put @%eval(    &LenName + 3 + &LenVchr + 3) Msg;
if last.&&Ids&DimId then  put '*;end;';
end;   %*do until(EndoFile);
stop;
%end;  %* %if &Details ................. ............................ *;
 
%if    "&PrntList" eq "PRINT"
    and &PrntFile  ne  PRINT %then %do;%* --------------------------- *;
OPTIONS pageno = 1;                    %*echo detail list*;
data   _Null_;
TITLE%eval(&TitleN.+1);
file   PRINT;
do     until(EndoFile);
infile &PrntFile end = EndoFile pad lrecl = &LRECL.;
input  @1 Line $char&LrecL..;
put    @1 Line $char&LrecL..;
end;   %*do until(EndoFile)*;
stop;
%end;  %* %if PrntList eq PRINT & PrntFile ne PRINT ................. *;
 
*EXIT; run;TITLE%eval(&TitleN.);
OPTIONS &ValidVarName. FormDlim = ''; %*reset to page-eject;
%if     &Testing %then %do;
    %put _local_;
    %end;
%if not &Testing %then %do;
PROC Datasets library  = WORK     nolist;
              delete     Contents Details Format_Cntl
                         Invalid_Ids Invalid_Vars
                         Item ItemList List List_Ids List_Vars RevuCels
              (memtype = data);
              quit;
    %end;
%Exit: run; 
%Mend invalid;

Macro MakePcnt

%macro MakePcnt /*and trim value to &D decimals*/
(Var,Num,Denom,D = 3, I = 0, TrimLen = 0);
%Let &Var = %sysevalf(100 * &&&Num  / &&&Denom);
%Let I    =  %index(&&&Var,.);%*if dot in value trim number of decimals;
%if &I %then %do; %Let TrimLen = %eval(%length(&&&Var) - &I);
                  %if &TrimLen gt &D %then %Let TrimLen = &D;
                  %Let &Var =%substr(&&&Var,1,&I + &TrimLen);
    %end;
%Mend;

Macro Chk4Nvld

%Macro Chk4Nvld(Library=,Data=,IdList=,Var=,Type=,Format=,VarNum=,Nobs=0
               ,Where    = put(&Var.,&Format.) eq "&Invalid."
               ,Message  = put(&Var.,&Format.) eq &Invalid.
               ,SqlPrint = noprint);*Let SqlPrint = print;%*testing;
 
DATA   List             (keep = &Var.) %*obs=0  for rotate;
       Details          (drop = N_Obs);
retain                          N_obs 0;
do     until(EndoFile);
set    &Library..&Data.
       (keep  =  &IdList. &Var.
        where = (&Where.
       )) end =   EndoFile;     N_Obs++1;
                                output Details;
end;   %*do until(EndoFile);
call   symput('Nobs',compress(put(N_Obs,32.)));
stop;
run;
 
DATA   Item;
if 0   then
set    ItemList (obs = 0);%*copy structure;
retain VarNum  &VarNum.
       N_Obs   &Nobs.
       Msg    "*&Message.;";
output; 
stop; 
run;
 
%** make mvar List containing var assignments;
PROC Sql &SqlPrint.;
         select    'Name  ="' !! trim(Name)     !! '";'
                !! 'Type  ="' !! trim(Type)     !! '";'
                !!  case Type when 'char' then 'ValueChr'
                              else             'ValueNum' end
                !!       '='  !! trim(Name)     !! ';'
         into     :List separated by ' '
         from      Dictionary.Columns
         where     LibName eq 'WORK'
               and MemName eq 'LIST'           ;quit;
 
%** rotate structure;
DATA   Details (drop = &Var.);
if 0   then
set    RevuCels(obs  = 0     );%*copy structure;
retain VarNum   &VarNum.
       Msg    "*&Message.;";
do     until(EndoFile);
set    Details   end = EndoFile;
&List. %*Name= Type= Value?=Name;
output;
end;   %*do until(EndoFile);
stop;
 
PROC Append base = RevuCels
            data = Details;
 
PROC Append base = ItemList
            data = Item; %* ............ *;
%Exit: run; %Mend;

Macro Nobs

There are several copies of this.

%MACRO nobs(Data = )
/ des = 'site: macro function returns Nobs of data set'
;/* 3/21/2007 RJF2 for fully-documented version see macro array package
NOTE: no checking of dsid and rc because ComparWS does exist(data)  **/
%local dsid; %let dsid = %sysfunc( open(&Data.     ));
%local Nobs; %let Nobs = %sysfunc(attrn(&dsid.,Nobs));
%local rc  ; %let rc   = %sysfunc(close(&dsid.     ));
%ReturnValue:&Nobs.%Mend nobs;


Testing program

 /*\begin{testSuite:macro}: ************ **/
%Inc SASautos(array) /nosource2;
*PROC Catalog catalog = Work.Formats kill;quit;%*zap previous;
%Let Invalid = Invalid;%*in either AutoExec or LetFrmt;
PROC  Format library = Work;
value one_3_    1,2,3          = 'OK' other = "&Invalid.";
value two_4_      2,3 , 4      = 'OK' other = "&Invalid.";
value $thre_5_     '3','4','5' = 'OK' other = "&Invalid.";
value no_other  1,2,3 , 4 , 5  = 'OK';
value $notused             '5' = 'OK' other = "&Invalid.";
 
DATA   TestInvalid;
attrib Id1Chr  length = $ 3                    label = 'Id1Chr'
       Id1Nmbr length =   4 format = 2.        label = 'Id1Num'
       Id2Char length = $ 2                    label = 'Id2Chr'
       Id2Nmbr length =   4                    label = 'Id2Num'
       A       length =   4 format = one_3_.   label = 'A: number 1'
       B       length =   4 format = two_4_.   label = 'B: number 2'
       C       length = $ 1 format = $thre_5_. label = 'C: char var'
       I       length =   4 format = no_other. label = 'loop counter';
retain Id2Char '01' Id2Nmbr 1;
do I = 1 to 5;  Id1Chr = put(I,z3.);  Id1Nmbr =     I;
                A      =     I;       B       = sum(I,2);
                C      = put(I,1.);   output;
   end;
stop;
%INVALID(LIBRARY = WORK
        ,FmtLib  = WORK
        ,Testing = 0);
%INVALID(Data    = TestInvalid
        ,LIBRARY = WORK
        ,FmtLib  = WORK
        ,TestSuite = 1
        ,Testing = 0);
%INVALID(Data    = TestInvalid
        ,IdList  = Id1Chr Id2Nmbr
        ,Library = WORK
        ,FmtLib  = WORK
        );
%INVALID(Data    = TestInvalid
        ,IdList  = Id1Nmbr Id2Char
        ,LIBRARY = WORK
        ,FmtLib  = WORK
        ,TestSuite = 1
        );%* disable write to file
        ,PrntFile='zInvalid.SAS'
        );%*NOTE hard-coded output fileref;
run; /* \end{testSuite:macro} ********** */

References

-- created by User:Rjf2 15:23, 12 June 2012 (EDT)

--Ronald_J._Fehd macro.maven == the radical programmer