SAS - Как вернуть значение из макроса SAS?

Я хотел бы вернуть значение из созданного мной макроса SAS, но я не уверен, как это сделать. Макрос вычисляет количество наблюдений в наборе данных. Я хочу, чтобы количество наблюдений было возвращено.

%macro nobs(library_name, table_name);
  proc sql noprint;
    select nlobs into :nobs
    from dictionary.tables
    where libname = UPCASE(&library_name)
      and memname = UPCASE(&table_name);
  quit;

  *return nobs macro variable;
  &nobs
%mend;

%let num_of_observations = %nobs('work', 'patients');

Кроме того, мне бы хотелось, чтобы макрокоманда &nobs, которая используется в макросе, была локальной для этого макроса, а не глобальной. Как я могу это сделать?

Ответ 1

Я отвечу на основной вопрос, который Бэмби задал в комментариях:

Моя главная задача здесь - как вернуть значение из макроса.

Я собираюсь поспорить с Дирком здесь важным способом. Он говорит:

Макрос SAS вставляет код. Он никогда не сможет вернуть значение, хотя в некоторых случаях вы можете имитировать функции

Я не согласен. Макрос SAS возвращает текст, вставленный в поток обработки. Возвращает это абсолютно подходящий термин для этого. И когда текст оказывается одним числом, то можно сказать, что он возвращает значение.

Тем не менее, макрос может возвращать только одно значение, если он имеет только операторы макроса в дополнение к этому значению. То есть каждая строка должна начинаться с %. Все, что не начинается с %, будет возвращено (и некоторые вещи, которые начинаются с % также могут быть возвращены).

Поэтому важный вопрос: как мне вернуть только значение из макроса.


В некоторых случаях, таких как этот, это возможно только с помощью макрокодов. На самом деле, во многих случаях это технически возможно - хотя во многих случаях это больше, чем нужно делать.

Связанная статья Джека Гамильтона включает пример, который уместен здесь. Он отклоняет этот пример, но в основном потому, что его статья посвящена подсчету наблюдений в тех случаях, когда NOBS неверен - либо с предложением WHERE, либо в некоторых других случаях, когда наборы данных были изменены без обновления метаданных NOBS.

В вашем случае вы, кажется, совершенно счастливы доверять NOBS - так подойдет этот пример.

Макрос, который возвращает значение, должен иметь ровно один оператор, который либо не является оператором синтаксиса макроса, либо оператором синтаксиса макроса, который возвращает значение в поток обработки. %sysfunc является примером оператора, который делает это. Такие вещи, как %let, %put, %if и т.д., Являются синтаксическими операторами, которые ничего не возвращают (сами по себе); так что вы можете иметь столько, сколько хотите.

У вас также должен быть один оператор, который помещает значение в поток обработки: иначе вы вообще ничего не получите из своего макроса.

Вот урезанная версия макроса Джека в конце страницы 3, упрощенная для удаления nlobsf:

 %macro check;

   %let dsid = %sysfunc(open(sashelp.class, IS));
   %if &DSID = 0 %then
   %put %sysfunc(sysmsg());

   %let nlobs = %sysfunc(attrn(&dsid, NLOBS));

   %put &nlobs;

   %let rc = %sysfunc(close(&dsid));

 %mend;

Этот макрос не является макросом стиля функции. Он ничего не возвращает потоку обработки! Это полезно для просмотра журнала, но не полезно для получения значения, с которым вы можете программировать. Тем не менее, это хорошее начало для макроса стиля функции, потому что вы действительно хотите, чтобы это &nlobs, верно?

 %macro check;

   %let dsid = %sysfunc(open(sashelp.class, IS));
   %if &DSID = 0 %then
   %put %sysfunc(sysmsg());

   %let nlobs = %sysfunc(attrn(&dsid, NLOBS));

   &nlobs

   %let rc = %sysfunc(close(&dsid));

 %mend;

Теперь это макрос стиля функции: у него есть один оператор, который не является оператором синтаксиса макроса, &nlobs. на простой линии все само по себе.

Это на самом деле больше, чем нужно одним утверждением; помните, как я сказал, что %sysfunc возвращает значение в поток обработки? Вы можете удалить %let часть этого оператора, оставив вас с

 %sysfunc(attrn(&dsid, NLOBS))

И тогда значение будет помещено непосредственно в сам поток обработки, что позволит вам использовать его напрямую. Конечно, не так легко отладить, если что-то пойдет не так, но я уверен, что вы можете обойти это, если вам нужно. Также обратите внимание на отсутствие точки с запятой в конце оператора - это потому, что точки с запятой не требуются для выполнения макрофункций, и мы не хотим возвращать лишние точки с запятой.

Давайте будем вести себя хорошо, добавим несколько %local чтобы получить это красиво и безопасно, и сделаем имя набора данных параметром, потому что природа не терпит макрос без параметров:

 %macro check(dsetname=);

   %local dsid nlobs rc;

   %let dsid = %sysfunc(open(&dsetname., IS));
   %if &DSID = 0 %then
   %put %sysfunc(sysmsg());

   %let nlobs = %sysfunc(attrn(&dsid, NLOBS));

   &nlobs

   %let rc = %sysfunc(close(&dsid));

 %mend;

 %let classobs= %check(dsetname=sashelp.class);

 %put &=classobs;

Там у вас есть: макрос стиля функции, который использует функцию nlobs чтобы узнать, сколько строк в каком-либо конкретном наборе данных.

Ответ 2

Какая проблема заключается в написании макросов типа функции?

то есть. макросы, которые вы можете использовать как %let myVar = %myMacro(myArgument)

  • Вы можете использовать свой написанный пользователем макрос, как если бы это была функция, если все, что вы делаете, это
    • вызов некоторых %doSomething(withSometing) функций макросов
    • присваивать значения макропеременным с помощью оператора %let someVar =
    • "вернуть" ваш результат, как правило, написав &myResult. в последней строке перед вашим %mend
  • Как только вы включите шаг proc или data в свой макрос, это больше не работает
  • К счастью,% sysFunc() приходит на помощь, поэтому мы можем использовать любую функцию шага данных
  • Это включает в себя функции низкого уровня, такие как open, fetch и close, которые могут даже получить доступ к вашим данным.
  • Зачарованные люди могут делать с ним много, но даже если вы бодрее, ваш босс редко даст вам время для этого.

Как это решить?, т.е. какие блоки используются для решения этой проблемы?

  • proc fcmp позволяет упаковывать некоторые инструкции шага данных в подпрограмме или функции
  • Эта функция, предназначенная для использования на шаге данных, может использоваться в %sysfunc()
  • В этой функции вы можете вызвать run_macro, чтобы выполнить любой макрос В НЕМЕДЛЕННОМ СПИСКЕ

Теперь мы готовы к практическому решению

Шаг 1: напишите вспомогательный макрос

  • без параметров,
  • с использованием некоторых глобальных макропеременных
  • "возвращает" свой результат в глобальной макропеременной

Я знаю, что это плохая привычка кодирования, но для снижения риска мы квалифицируем эти переменные с помощью префикса. Применяется к примеру в вопросе

** macro nobsHelper retrieves the number of observations in a dataset
    Uses global macro variables:
        nobsHelper_lib: the library in which the dataset resides, enclosed in quotes
        nobsHelper_mem: the name of the dataset, enclosed in quotes
    Writes global macro variable:
        nobsHelper_obs: the number of observations in the dataset 

    Take care nobsHelper exists before calling this macro, or it will be ost
**;
%macro nobsHelper();
    ** Make sure nobsHelper_obs is a global macro variable**;
    %global nobsHelper_obs;

    proc sql noprint;
        select nobs
        into :nobsHelper_obs
        from sashelp.vtable
        where libname = %UPCASE(&nobsHelper_lib)
          and memname = %UPCASE(&nobsHelper_mem);
    quit;
    %* uncomment these put statements to debug **;
    %*put NOTE: inside nobsHelper, the following macro variables are known;
    %*put _user_;
%mend;

Шаг 2: напишите вспомогательную функцию;

**Functions need to be stored in a compilation library;
options cmplib=sasuser.funcs;

** function nobsHelper, retrieves the number of observations in a dataset
    Writes global macro variables:
        nobsHelper_lib: the library in which the dataset resides, enclosed in quotes
        nobsHelper_mem: the name of the dataset, enclosed in quotes
    Calls the macro nobsHelper

    Uses macro variable:
        nobsHelper_obs: the number of observations in the dataset 
**;
proc fcmp outlib=sasuser.funcs.trial;
    ** Define the function and specity it should be called with two character vriables **;
    function nobsHelper(nobsHelper_lib $, nobsHelper_mem $);
        ** Call the macro and pass the variables as global macro variables 
        ** The macro variables will be magically qouted **;
        rc = run_macro('nobsHelper', nobsHelper_lib, nobsHelper_mem);
        if rc then put 'ERROR: calling nobsHelper gave ' rc=;

        ** Retreive the result and pass it on **;
        return (symget('nobsHelper_obs'));
    endsub;
quit;

Шаг 3: напишите удобный макрос для использования помощников;

** macro nobs retrieves the number of observations in a dataset
    Parameters:
        library_name: the library in which the dataset resides
        member_name: the name of the dataset
    Inserts in your code:
        the number of observations in the dataset 
    Use as a function
**;
%macro nobs(library_name, member_name);
    %sysfunc(nobsHelper(&library_name, &member_name));

    %* Uncomment this to debug **;
    %*put _user_;
%mend;

Наконец, используйте;

%let num_carrs = %nobs(sasHelp, cars);
%put There are &num_carrs cars in sasHelp.Cars;

Data aboutClass;
    libname = 'SASHELP';
    memname = 'CLASS';
    numerOfStudents = %nobs(sasHelp, class);
run;

Я знаю, что это сложный, но, по крайней мере, все проворные работы выполняются.   Вы можете копировать, вставлять и изменять это за время, которое примет ваш босс. ;

Ответ 3

Маска SAS вставляет код. Он никогда не сможет вернуть значение, хотя в некоторых случаях вы можете имитировать функции, обычно вам нужна работа, например

%nobs(work, patients, toReturn=num_of_observations )

** Чтобы помочь вам понять, что произойдет, советую распечатать код, вставленный макросом в ваш журнал:;

options mprint;

Мы передаем имя макропеременной, чтобы заполнить макрос,. Я нахожу его наиболее практичным для

  • не требует, чтобы пользователь моего макроса помещал кавычки вокруг имени библиотеки и членов
  • введите имя переменной с именованной макропеременной, поэтому мы можем указать ее по умолчанию;

    % macro nobs (имя_библиотеки, имя_таблицы, toReturn = nobs);

Убедитесь, что возвращаемая переменная существует

  • Если он существует, он известен вне этого макроса.
  • Otherwisse, если мы создадим его здесь, он по умолчанию будет локальным и потерян, когда мы оставим макрос;

    %if not %symexist(&toReturn.) %then %global &toReturn.;
    

В SQL, I

  • используйте SASHELP.VTABLE, представление, предоставленное SAS по его метаданным.
  • добавить кавычки, которые я пропустил в вызове макроса ("", а не '': макропеременные не заменяются в одном Qoutes)
  • используйте функцию макросохранения вверх, вместо функции ожидания SAS, поскольку она иногда повышает производительность;

    proc sql noprint;
        select nobs
        into :&toReturn.
        from sashelp.vtable
        where libname = %UPCASE("&library_name.")
        and memname = %UPCASE("&table_name.");
    quit;
    

    % латай;

Обратите внимание, если вы вызываете макрос в макросе, запустите этот код и прочитайте журнал, чтобы понять, почему:

%macro test_nobs();
    %nobs(sashelp, class); ** will return the results in nobs **;

    %nobs(sashelp, shoes, toReturn=num_of_shoes);

    %let num_of_cars = ;
    %nobs(sashelp, cars, toReturn=num_of_cars);

    %put NOTE: inside test_nobs, the following macro variables are known;
    %put _user_;
%mend;

%test_nobs;
%put NOTE: outside test_nobs, the following macro variables are known;
%put _user_;

Ответ 4

Вы не можете "вернуть" значение из макрофайла в стиле функции, если вы не написали его, используя только макрокоманды. Ссылка на канал Quentin дает пример того, как это сделать.

Например, вы не можете использовать свой макрос так, потому что proc sql не может выполняться в середине инструкции %put (это возможно с другими более сложными обходными методами, например dosubl, но не так, как вы написано).

%put %nobs(mylib,mydata);

Лучшее, что вы можете сделать без значительных изменений, - создать глобальную макро переменную и использовать ее в последующих операциях.

Чтобы создать макрокоманду, которая является локальной для исходного макроса, вы должны сначала объявить ее с помощью инструкции %local в определении макроса.

Ответ 5

Я знаю, что очень опоздал к этой дискуссии, но подумал о комментировании, так как я столкнулся с этим. Это еще один способ сделать это, я думаю:

%macro get_something_back(input1, input2, output);
  &output = &input1 + &input2;
%mend;

data _test_;
  invar1 = 1; invar2 = 2;
  %get_something_back(invar1, invar2, outvar);
end;

Это также будет работать за пределами шага данных.


%global sum;

%macro get_something_back(input1, input2, outvar);
  %let &outvar = &sysevalf(&input1 + &input2);
%mend;

%get_something(1, 2, sum);