【问题标题】:Implementing proc lua code in the SAS macro language用SAS宏语言实现proc lua代码
【发布时间】:2020-04-08 03:26:27
【问题描述】:

我对 SAS 还很陌生,我很难理解如何用 SAS 宏语言编写数据驱动的程序。但是,proc lua 对我来说很有意义。尽管如此,我想知道两者。

下面的代码——尽管它可能很愚蠢——说明了我正在努力解决的概念。它会随机列出猫的名字,然后找出哪些猫很顽皮,哪些猫很乖。然后它会打印出一份圣诞清单,让我知道应该为哪些猫买礼物,以及我可以为每只猫花多少钱。

我在翻译/实现为 SAS 宏语言时遇到困难的部分代码是:

1) 找出第 j 只猫的后缀的部分,然后将其打印到日志中。如何动态更改宏变量的值,然后在宏循环中使用它们将某些内容写入日志?有没有办法在宏中使用“call symput”或“symget”?

2) 如何在宏循环中写入多个数据集,类似于我在下面所做的。

3) 如何在sas宏中调用proc fcmp编译的自定义函数来控制宏的流程。

* 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;

示例输出:

示例日志:

 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

【问题讨论】:

  • 操作实际数据应该使用 SAS 代码,而不是 MACRO 代码或 LUA 代码。
  • 如果没有问题描述,很难判断您的程序在做什么。但是快速看一下,看起来对宏的唯一需要是如果您想要制作一些参数,例如要生成多大的一组猫或使用什么随机数种子。否则,它看起来像是不需要代码生成的直接数据操作。

标签: lua sas


【解决方案1】:

1) 找出第 j 只猫的后缀的部分,然后 将其打印到日志中。如何更改宏变量的值 在运行中,然后在宏循环中使用它们来写一些东西 日志?有没有办法在宏中使用“call symput”或“symget”?

有一些宏语句如%PUT 用于向日志显示值。如果您想在宏代码中执行循环,请使用%DO 语句。

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

您可以使用%LET 来分配宏变量值。

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

您可以从宏值构建宏变量引用。当您使用&amp;&amp; 时,它将被&amp; 替换并触发宏处理器进行另一次传递以解析引用。

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

2) 如何在宏循环中写入多个数据集, 类似于我在下面所做的。

您不会使用宏代码写入数据集。您使用宏代码生成可写入数据集的 SAS 语句。而且您可以写入多个数据集,而无需使用任何代码生成。

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

3) 如何在 sas 中调用使用 proc fcmp 编译的自定义函数 宏来控制宏的流程。

不确定这个,但为什么不只是有一个数据步骤来调用该函数?

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

【讨论】:

  • 关于 1) 如果程序发展到一千只猫,我认为它不会那么好用。也许我可以编写一个宏来创建数千个宏变量,但这似乎浪费内存,一个变量就足够了。如果我能在宏处理阶段改变它的值就好了。我知道如何使用 %put,但我在编写后缀时遇到了麻烦。
  • 使用 DATA 步和 PROC 来处理数据。不是代码生成工具。
  • 感谢您的建议,我会尝试写一个与上面代码等效的数据步骤。
【解决方案2】:

按照 Tom 的建议,我将 proc lua 过程重写为数据步骤,它的运行速度明显加快。我能够通过创建一个带有我想要放入日志的消息的字符串,然后将其传递给 put 函数来实现所需的日志;之前,当我尝试过这样的事情时,我试图在 put 语句中构造字符串,这会返回一个错误。

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;

【讨论】:

  • 数据步骤DO 循环比您习惯的更强大。尝试do j=1 by 1 while(....) 并避免手动启动和增加循环计数器。在调用 PUT 之前无需构建您的消息。对于直接处理 PUT 语句,您的消息看起来微不足道。将“他/她”放入另一个变量或生成我在此处引用的格式以将性别转换为正确的代词。 put 'My ' j +(-1) suffix "cat's name is ' name ', and ' gender $genderpronoun. 'is usually ' status +(-1) '.' ;
【解决方案3】:

您可以通过从proc lua 中取出lua 来做到这一点。只要文件具有.lua 扩展名,您就可以直接在宏中使用%inc lua 代码。这种方法由sasjs 框架的mv_webout 宏采用,用于读取从javascript 发送的数据。

这是一个简短的例子。

%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()

【讨论】:

  • %INCLUDE 的这种奇怪行为记录在哪里?它对任何其他文件扩展名都这样做吗?
  • 我确实在某处看到过,可能是在 viya 文档中。而不是据我所知!是的,有趣的是%inc 可以与非 sas 运行时一起使用,我想知道它是否包含在 proc lua 中,如果是这样,使用哪些选项.. 玩它的时间还不够长。不过很好:-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-08
  • 1970-01-01
  • 2010-09-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多