1
votes

I am fairly new to SAS and I am struggling to understand how to write programs in the SAS macro language that are data driven. However, proc lua makes sense to me. Nonetheless, I would like to know both.

The code below--silly as it may be--illustrates the concepts that I am struggling with. It makes a random list of cat names, and then finds out which cats have been naughty and which have been nice. It then prints out a Christmas list, which lets me know which cats should buy presents for and how much I can spend on each of them.

The parts of the code that I am having difficulties translating/implementing into the SAS macro language are:

1) The section that figures out the suffix for the jth cat, and then prints it to the log. How can you change the values of macro variables on the fly and then use them within a macro loop to write something to the log? Is there a way to use 'call symput' or 'symget' in a macro?

2) How can you write to multiple datasets while in a macro loop, similar to what I have done below.

3) How can you call custom functions compiled with proc fcmp in a sas macro to control the flow of the macro.

* This macro create a list of cat names;
%macro getsomecats(num);
    %local mycat;
    %let mycat = cat1;
    %do j = 2 %to #
        %let mycat = &mycat.%str( cat)&j.;
    %end;
    &mycat
%mend;

* SAS macro that duplicates the Excel RANDBETWEEN function. Taken from 
  http://blogs.sas.com/content/iml/2015/10/05/random-integers-sas.html;
%macro RandBetween(min, max);
   (&min + floor((1+&max-&min)*rand("uniform"))) 
%mend;

* Get the number of cats that will be in our list;
data _null_;
    seed = %randbetween(1,50);
    call symputx('myseed',seed);
run;
* Make a random list of cat names;
%let morecats = %getsomecats(&myseed.);

* Reference some custom functions compiled with proc fcmp;
options cmplib=(MY_FUNCS.PURE_MATH);
libname NUMBERS '/folders/myfolders';

* Make two data sets: one for all of my cats, and another for
  the cats that I should by Christmas presents;
proc lua;
submit;
    -- Import Lua libraries
    require 'string'
    require 'math'

    -- If the tables I want to create exist, then delete them.
    if sas.exists('my_cats') then
        sas.submit([[
            proc delete data=my_cats;
        ]])
        print('my_cats deleted')
    end

    if sas.exists('xmas_list') then
        sas.submit([[
            proc delete data=xmas_list;
        ]])
        print('xmas_list deleted')
    end

    -- Set up some data sets
    sas.new_table('my_cats', {
        {name='Name', type='C', length=8},
        {name='Status', type='C', length=8},
        {name='Gender', type='C', length=6}
    })
    sas.new_table('xmas_list', {
        {name='Name', type='C', length=8},
        {name='Status', type='C', length=8},
        {name='Gender', type='C', length=6},
        {name='Budget', type='N', length=8}
    })

    -- Create data set handels for our new data set 
    local dsh1 = sas.open('my_cats', 'u')
    local dsh2 = sas.open('xmas_list', 'u')

    -- Declare some useful variables
    local suffix, status, gender, name
    local ub = 1 -- upper bound for 'for' loop
    local mystr = sas.symget("morecats")

    -- Find out upper bound on number of cats
    for j = 1, string.len(mystr) do
        if mystr:sub(j,j) == ' ' then ub = ub + 1 end
    end
    mystr = nil -- we do not need mystr anymore

    print('Making my christmas list:') -- Write header in log
    for j = 1, ub do
        -- Create a suffix for jth cat; I am very confused about
        -- how to do this in the SAS macro language.
        if j % 10 == 1 and j % 100 ~= 11 then suffix = 'st'
        elseif j % 10 == 2 and j % 100 ~= 12 then suffix = 'nd'
        elseif j % 10 == 3 and j % 100 ~= 13 then suffix = 'rd'
        else suffix = 'th' end


    -- Find out if the jth cat has been naughty or nice.
    -- 'isprime' is a custom function compiled with proc fcmp,
    -- it returns 1 if a number is prime and 0 if it is composite.
    if sas.isprime(j) == 1 then
        status = 'naughty'
    else
        status = 'nice'
    end

    -- Assign the cat a gender randomly. I would like to 
    -- know how to this in the SAS macro language, including
    -- how to use a list so that I can reference the two different
    -- charchteristics of gender.
    if sas.ranuni(0) < .5 then 
        gender = {'male', 'he'}
    else
        gender = {'female', 'she'}
    end

    -- Get the cats name; scan the macro variable
    -- 'morecats' for the jth entry.
    name =sas.scan(sas.symget("morecats"),j)

    -- Write information in our log about this cat,
    -- again, I cannot figure out how to deal with the
    -- suffix part here.
    print('My '..j..suffix.." cat's name is "..name..
        ', and '..gender[2]..' is usually '..status)

    -- Add the jth cat to my data set of cats
    sas.append(dsh1)
    sas.put_value(dsh1,"Name", name)
    sas.put_value(dsh1,"Status", status)
    sas.put_value(dsh1,"Gender", gender[1])
    sas.update(dsh1)

    -- If the jth cat is usually nice then, add him or her
    -- to the data set of cats that need to by Christmas
    -- presents for.
    if status == 'nice' then 
        local budget = 10 * sas.phi(math.random(30))
        sas.append(dsh2)
        sas.put_value(dsh2,"Name", name)
        sas.put_value(dsh2,"Status", status)
        sas.put_value(dsh2,"Gender", gender[1])
        sas.put_value(dsh2,"Budget", budget)
        sas.update(dsh2)
    end             
end
sas.close(dsh1)
sas.close(dsh2)
endsubmit;
run;

proc print data=xmas_list;
     var _all_;
     sum Budget;
run;

Example output:

enter image description here

Example log:

 Making my christmas list:
 My 1st cat's name is cat1, and she is usually nice
 My 2nd cat's name is cat2, and he is usually naughty
 My 3rd cat's name is cat3, and she is usually naughty
 My 4th cat's name is cat4, and she is usually nice
 My 5th cat's name is cat5, and she is usually naughty
 My 6th cat's name is cat6, and he is usually nice
 My 7th cat's name is cat7, and she is usually naughty
 My 8th cat's name is cat8, and she is usually nice
3
Manipulating actual data is something you should do with SAS code, not MACRO code or LUA code.Tom
Hard to tell what your program is doing without a problem description. But looking at it quickly it looks like the only need for macro is if you wanted make some parameters such as how large a set of cats to generate or what random number seed to use. Otherwise it looks like straight data manipulation that does not require code generation.Tom

3 Answers

1
votes

1) The section that figures out the suffix for the jth cat, and then prints it to the log. How can you change the values of macro variables on the fly and then use them within a macro loop to write something to the log? Is there a way to use 'call symput' or 'symget' in a macro?

There are some macro statements like %PUT for displaying values to the log. If you want to do a loop in macro code then use %DO statement.

%do i=1 %to 5 ;
  %put I=&i ;
%end;

You can use %LET to assign macro variable values.

%let cat1=Fluffy;
%let cat2=Tiger ;

You can build macro variable reference from macro values. When you use && it will be replaced by & and trigger the macro processor to make another pass to resolve the reference.

%let i=2 ;
%put Cat &i is &&cat&i ;

2) How can you write to multiple datasets while in a macro loop, similar to what I have done below.

You don't write to datasets using macro code. You use macro code to generate SAS statements that can write to data sets. And you can write to multiple datasets without using any code generation at all.

data good bad;
  set cats;
  if status='nice' then output good;
  else output bad;
run;

3) How can you call custom functions compiled with proc fcmp in a sas macro to control the flow of the macro.

Not sure about this one, but why not just have a data step to call the function?

data _null_;
  call symputx('mvar',myfunc());
run;
%if (&mvar = 1) %then %do ;
  ...
%end;
1
votes

I rewrote the proc lua procedure as a data step, as Tom suggested, and it ran significantly faster. I was able to achieve the, desired log by creating a string with the message that I wanted to put in the log, and then passing it to the put function; before, when I had attempted something like this, I was trying to construct the string in the put statement, which returns an error.

options cmplib=(MY_FUNCS.PURE_MATH);
libname NUMBERS '/folders/myfolders';
option noquotelenmax; * Turn off warning when string length exceeds 256 bytes;
data    mycats (keep=Name Status Gender) 
    myxmaslist (keep=Name Status Gender Budget);
    length Name $ 8 Status $ 8 Gender $ 10 Budget 8.;
    put 'Making a Christmas list:';
    j = 1;
    do while(scan("&morecats",j,' ') ~= '');
        if mod(j, 10) = 1 & mod(j,100) ~= 11 then suffix = 'st';
        else if mod(j, 10) = 2 & mod(j,100) ~= 12 then suffix = 'nd'; 
        else if mod(j, 10) = 3 & mod(j,100) ~= 13 then suffix = 'rd'; 
        else suffix = 'th';

        Name = scan("&morecats", j, ' ');

        if isprime(j) then Status = 'naughty';
        else Status = 'nice';

        if rand("uniform") < 0.5 then Gender = 'male he';
        else Gender = 'female she';

        msg = "My "||strip(j)||suffix||" cat's name is "||strip(Name)||
                ", and "||scan(Gender, 2)||" is usually "||Status;
        put msg;

        Gender = scan(Gender, 1);

        output mycats;
        if Status = 'nice' then do;
            Budget = 10 * phi(j);
            output myxmaslist;
        end;
        j = j + 1;
    end;
run;
option quotelenmax;
0
votes

You do it by taking the lua out of proc lua. You can %inc lua code directly in macro so long as the file has a .lua extension. This approach is taken by the mv_webout macro of the sasjs framework, for reading in data sent from javascript.

Here is a short example.

%macro my_lua();
  filename mylua "%sysfunc(pathname(work))/some.lua";
  data _null_;
    file mylua;
    put 'sas.submit([[';
    put '    proc print data=sashelp.class;run;';
    put ']])';
    put 'print("some lua got in my macro code")';    
  run;
  %inc mylua;
%mend;
%my_lua()