Macro CallMacr

From sasCommunity
Jump to: navigation, search

This is the companion for Macro_CallText.

Author: Ronald_J._Fehd

This articles show list processing examples using the macro CallMacro.



Suggested Reading:

Q: Why use CallMacro when CallText can call a macro?

As shown in Fehd and Carpenter, it is a Good Idea to separate the Reporting macro from the macro do-loop that calls it. This separation allows unit testing of the Reporting macro before submitting it with a list.


 /*       Name:
      location: <UNC>\SAS-site\macros\
        Author: Ronald J. Fehd  2012
Summary       : description  : call macro using
                               all values in data set row as parameters
                purpose      : provide generic method to call macro
                               using list::control data set of parms
Contexts      : program group: list processing token generator
                program  type: routine
                SAS      type: macro clause generator
                uses routines: macro named in parameter
Specifications: input  : required: Data, MacroName
                         optional: MacroParms, Hex16
                process: assemble macro-call, call
                output : from MacroName
Parameters    : Data       = one- or two-level data set name
               ,MacroName  = name of macro to call
               ,MacroName  = put :: default, for testing
               ,MacroParms = additional parameters for called macro
                             *-Constraint-*: must be enclosed in nrstr:
               ,MacroParms = %nrstr(data=sashelp.class,var=Sex)
               ,Hex16      = 1 :: default, convert numerics to hex16
                             used to pass real numbers accurately
                                  across step boundaries
               ,Hex16      = 0 :: pass numerics as decimals
               ,Semicolon  = 0 :: no semicolon after macro call
               ,Semicolon  = 1 :: use when macroname is a statement
                                  note: auto-reset when macroname=put
               ,Testing    = 0 :: default, no extra messages in log
               ,Testing    = 1 :: for testing, note: auto-reset when
                                  options mprint source2;
Bells,Whistles: writes real time used to log
                note: CALLMACR used real time  0:00:00.016
Usage Example:
PROC Freq data   = sashelp.Class;
          tables   Sex
                 / noprint
             out =     Work.Freq_Class_Sex;
%callmacr(Data       = Work.Freq_Class_Sex
         ,MacroName  = Put note:
         ,MacroParms = %nrstr(data=sashelp.class,var=sex)
NOTE CAREFULLY: character variables may contain special characters:
16  label = 'x&y';
17  output Work.Special_ampersand;
18  label = 'x,y';
19  output Work.Special_comma;
20  label = 'x%y';
21  output Work.Special_pe_C_Rcent;
24  %CallMacr(Data = Work.Special_ampersand)
WARNING: Apparent symbolic reference Y not resolved.
28         %CallMacr(Data = Work.Special_comma)
ERROR: More positional parameters found than defined.
29         %CallMacr(Data = Work.Special_percent)
WARNING: Apparent invocation of macro Y not resolved.
word count: UltraEdit information: <Alt> <Search> <Word Count>
               DateTime: 6/27/2012 7:14:54 AM
                  Words:  663
                  Lines:  157
Characters(no   spaces): 4941
Characters(with spaces): 7079
*** .................................... */
%MACRO callmacr
         (Data       = sashelp.class /* required  */
         ,MacroName  = put /* default for testing */
         ,MacroParms = /* %nrstr(data=sashelp.class,var=Sex) */
         ,Hex16      = 1 /* convert numerics to hex16? */
         ,Semicolon  = 0 /* is each call (end of) a statement? */
         ,Testing    = 0
)/ des = 'site: make macro statement then call'
   /*  ** store source /* */
;/* RJF2 3/21/2007 working
*** NOTE: _C_*: avoid name collisions w/data set vars ***/
%let Testing = %eval(   &Testing
                     or(    %sysfunc(getoption(MPRINT))  eq MPRINT
                        and %sysfunc(getoption(SOURCE2)) eq SOURCE2));
%local _C_Col _C_Dsid _C_ListParms _C_Name _C_Nobs _C_Nvars
       _C_Rc  _C_Row  _C_Type
       _C_TimeStart _C_TimeEnd;
%let   _C_TimeStart = %sysfunc(datetime(),hex16.);
%**  description: assertions;
%**  purpose    : if fail then exit;
%if  not(%sysfunc(exist(&Data.))) %then %do;
     %put Err%str()or: &SysMacroname. exiting: not exist(&Data.);
%let _C_Dsid  = %sysfunc(open (&Data.         ));
%let _C_Nobs  = %sysfunc(attrn(&_C_Dsid.,Nobs ));
%let _C_Nvars = %sysfunc(attrn(&_C_Dsid.,Nvars));
%if  not &_C_Nobs. or not &_C_Nvars. %then %do;
  %put Err%str()or: &SysMacroName. &Data. obs=&_C_Nobs. vars=&_C_Nvars.;
     %goto CloseExit;
 %put note: &SysMacroname. reading &Data. obs=&_C_Nobs. vars=&_C_Nvars.;
%** description: read data;
%** purpose    : make macro call, submit;
%do _C_Row = 1 %to &_C_Nobs.;
    %if   %length(&MacroParms.) %then
          %let _C_ListParms = %unquote(&MacroParms.),;
    %else %let _C_ListParms = ;
    %*** note:                  fetchobs==read row;
    %let _C_Rc       = %sysfunc(fetchobs(&_C_Dsid.,&_C_Row. ));
    %do _C_Col = 1 %to &_C_Nvars.;
        %let _C_Name = %sysfunc(varName (&_C_Dsid.,&_C_Col. ));
        %let _C_Num  = %sysfunc(varNum  (&_C_Dsid.,&_C_Name.));
        %let _C_Type = %sysfunc(varType (&_C_Dsid.,&_C_Num. ));
        %*** append parmN=;
        %let _C_ListParms = &_C_ListParms.&_C_Name.=;
        %*** append: for Type=C: value, Type=N: hex16(value);
        %if   &_C_Type. eq C
           or(&_C_Type. eq N and not &Hex16.) %then
              %let _C_ListParms = &_C_ListParms.%left(%sysfunc(
        %else %let _C_ListParms = &_C_ListParms.%left(%sysfunc(
        %** append comma;
        %if &_C_Col. lt &_C_Nvars. %then
            %let _C_ListParms = &_C_ListParms.,;
        %if &Testing. %then %put note: testing: &_C_ListParms.;
    %*** submit: NOTE! no ending semicolon!;
    %*** for testing MacroName=put note  add a Semicolon;
    %let Semicolon = %eval(&Semicolon.
         or %upcase(%scan(&MacroName. ,1,%str( ))) eq PUT);
    %if &Semicolon. %then %do;
%CloseExit: %let _C_Rc = %sysfunc(close(&_C_Dsid.));
%let _C_TimeEnd = %sysfunc(datetime(),hex16.);
%Put note: &SysMacroName used real time %sysfunc
%mend callmacr;


This macro function performs the following assertions:

  • exist data?
  • data has rows==Nobs and columns==Nvars

Code and demonstrations have been moved to: Macro_Assertions_for


Hard-coded Macro Statments

This program shows the AutoMagic encapsulated in macro CallMacro.

 /*     name:;
*description: demo of assembly of macro call from tokens
*purpose    : test suite for callmacr
RJF2 2012-May-12
%MACRO ProcFreq(data=, var=);
PROC Freq data = &Data.;
          tables &Var.;
%MACRO ProcPrint(data=, var=);
PROC Sort  data = &Data.
           out  = work.Sorted;
           by     &Var.;
PROC Print data = &SysLast.;
           by     &Var.;
           id     &Var.;
           title3 &Data. by &Var.;
%Let MacroParms= data=sashelp.class,var=Sex;
%let MacroName = ProcFreq;
%let MacroName = ProcPrint;

Report for sashelp.Shoes

This program shows a Reporting macro and calling it with a list.

Macro Report for sashelp.Shoes

%Macro Report(InData  = sashelp.Shoes
             ,Region  =
             ,OutPath = /*here: same folder*/
             ,Count   =
             ,Percent =
from SGF.2007 HandsOn Workshops 
List Processing Basics: Creating and Using Lists of Macro Variables
Ronald J. Fehd and Art Carpenter
%local Filename; 
%Let   Filename = &Region.;
%** change slash to hypen in
    Central America/Caribbean;
%If %index(&Region,/) %then %Let Filename =
ods html file  = "&OutPath.&Filename..html"
         style = journal;
Title "Sales in &Region.";
title2 "Count: &Count.";
title3 "Percent: &Percent.";
PROC Report data = &InData.
            where  Region = "&Region.";
            column Product Subsidiary, Sales;
            define Product    / group;
            define Subsidiary / across;
            define Sales      / analysis '';
ods  html close;
%mend Report;

Macro Report testing program

This programs tests the macro

ods listing close;
%Report(Region = Africa);
%Report(Region = Asia  );
%Report(Region = Central America/Caribbean);
%Report(Region = Pacific
       ,OutPath = c:\temp\
ods listing;%*open;

Macro Report calling program

This program creates a list and calls the macro

 /*     name: <UNC>\SAS-site\macros-tests\
*description: make list of tables for testing;
*             call testing subroutine;
*             call writing subroutine;
*purpose    : test suite for CallMacro;
RJF2 2012-May-12
options mprint;* source2;*testing;
*options mprint source2;*testing;
PROC Freq data   = sashelp.Shoes
         (keep   = Region);
          tables   Region
                 / list missing noprint
             out = Work.Regions;
%callmacr(data      = Work.Regions
         ,MacroName = Report
         ,hex16     = 0

Here are excerpts from the log:

note: CALLMACR reading Work.Regions obs=10 vars=3
MPRINT(REPORT):   ods html file = "Africa.html" style = journal;
NOTE: Writing HTML Body file: Africa.html
MPRINT(REPORT):   Title "Sales in Africa";
MPRINT(REPORT):   title2 "Count: 56";
MPRINT(REPORT):   title3 "Percent: 14.1772151898734";
MPRINT(REPORT):   PROC Report data = sashelp.Shoes nowindows;

[... Big Snip ...]

MPRINT(REPORT):   ods html file = "Western Europe.html" style = journal;
NOTE: Writing HTML Body file: Western Europe.html
MPRINT(REPORT):   Title "Sales in Western Europe";
MPRINT(REPORT):   title2 "Count: 62";
MPRINT(REPORT):   title3 "Percent: 15.6962025316455";

NOTE: There were 62 observations read from the data set SASHELP.SHOES.
      WHERE Region='Western Europe';
NOTE: The PROCEDURE REPORT printed page 10.
NOTE: PROCEDURE REPORT used (Total process time):
      real time           0.01 seconds

MPRINT(REPORT):   ods html close;
note: CALLMACR used real time  0:00:05.016
NOTE: SAS Institute Inc., SAS Campus Drive, Cary, NC USA 27513-2414
NOTE: The SAS System used:
      real time           5.31 seconds

Macro ProcFreq for SmryEachVar

Posted to SAS-L on 2013-Jan-11

Compare with SmryEachVar:

*options mprint;
*proc delete data = Work.freq;
*proc delete data = Work.list;
%Let In_Lib_Data = sashelp.class;
PROC Contents data = &In_Lib_Data
              out  = Work.ListVars
             (keep = Libname Memname Name Type Length);
             *NOTE : parameters of macro ProcFreq;
PROC Print data = &Syslast.;
%Macro ProcFreq
        ,name   =
        ,type   =
        ,length =
%let Testing= %eval(   &Testing
                    or(    %sysfunc(getoption(MPRINT))  eq MPRINT
                       and %sysfunc(getoption(SOURCE2)) eq SOURCE2));
%if &Testing %then %put _local_;
PROC Freq data   = &Libname..&Memname.;
          tables  &Name
                 / noprint
             out = Work.Freq
         (rename = (&Name = 
                   %if       &Type eq 1 %then ValueN;
                   %else %if &Type eq 2 %then ValueC;
         )         );
DATA Work.Freq;
     attrib Name   length = $32
            ValueC length = $32
            ValueN length =   8;
     retain Name "&Name";
set Work.Freq;
%if &Testing %then %do;
    proc print data = &Syslast.;
PROC Append base = Work.List
            data = &Syslast;
%CallMacr(data     = Work.ListVars
         ,MacroName= ProcFreq
         ,hex16    = 0)
Proc Print data = Work.List;

Macro AddNames to report Variable Names in all data sets in Libref

/*     name: ListVarNames
description: merge list of variable names from Contents.output
             by Memname
    purpose: find common variable names amongst data sets
1. disable creation of test data
2. enable libref:
%let Libref = Library;
*let Libref = sashelp;
*let Libref = Work;
                                                   CLASS_  CLASS_
age      Age       1     8       3                 Age     Age
date     Date      1     8       7                 Date
         Date      2    10       6                         Date
date_c   date_C    2    10       6                 date_C
date_n   date_N    1     8       7                         date_N
gender   Gender    2     1       2                 Gender
from SAS-L post by Jordan, Lewis
> I have several data sets that, for the most part,
> have the same variables in them.
> Not exact, but pretty close.
> My question is how can I find
> the list of variables common to all data sets?
> I know PROC COMPARE would be a 1-to-1 comparison,
> is there a "multivariate" PROC COMPARE.
end header ***************************************/
/* disable Test Data: remove slash (/) and end of this line ***/
DATA work.Class_Sex   (rename = (Date_C = Date))
     work.Class_Gender(rename = (Sex    = Gender
                                 Date_N = Date));
    if 0 then set sashelp.class;
    attrib date_C length = $10
           date_N length =   8
    retain Date_C '.'
           Date_N  . ;
do until(EndoFile);
   set sashelp.class
       end = EndoFile;
   output work.Class_Sex;
   output work.Class_Gender;
PROC SQL; describe table work.Class_Sex;
          describe table work.Class_Gender;
%let Libref = Library;
%let Libref = sashelp;
%let Libref = Work;
%let _Testing = 0;
%let out_Report        =work._Report;
%let out_List_Names    =work._List_Names;
%let out_List_MemNames =work._List_MemNames;
PROC Contents data =&Libref.._all_
      out = &Out_List_Names
     (keep = Libname Memname Name Type Length Format Label VarNum);
Proc Print data = &syslast;
           title3 &syslast;)
PROC Sort data = &syslast
         (keep = Libname MemName)
          out  = &Out_List_Memnames;
          by MemName;
DATA &Out_Report;
     attrib Name_lc length = $32;
     set &Out_List_Names(drop = Libname MemName
                         obs  = 0);
options mprint;
%Macro AddNames(libname =
               ,memname =
               ,testing = &_Testing);
DATA &Out_Report(drop = Libname MemName);
     if 0 then set &Out_Report;
     attrib &Memname length = $32;
do until(EndoFile);
   merge &Out_List_Names
        (where = (MemName eq "&MemName")
         in    = HaveName)
         end = EndoFile;
   by Name Type Length;
   if HaveName then &Memname = Name;
   else             &Memname = ' ';
   Name_lc = lowcase(Name);
%if &Testing %then %do;
    proc print;
    title3 &Out_Report;
%CallMacr(data      = &Out_List_Memnames
         ,MacroName = AddNames
PROC Sort  data = &Out_Report;
           by     Name_lc Name Type Length;
Proc Print data = &Out_Report;
           by     Name_lc;
           id     Name_lc;




  • Macro_Array-Do
    • make macro arrays from data with with sql select into
    • then macro do-loop to call macro for each row of data
    • successor of Macro_CallMacr
  • Macro_Array_Package macro function returns dim(data) and data step to make array of macro variables
  • Macro_Do-Loop parameters: vars, dimension, and text
    • for vars: creates macro arrays
    • dimension: %do i =1 to &Dim.
    • returns tokens in text
    • successor of Macro_CallText

Papers and articles

--Ronald_J._Fehd macro.maven == the radical programmer 07:12, 24 April 2012 (EDT)