简体   繁体   中英

Passing an equals sign to a macro in SAS

I'm trying to generate code in a data step, and then pass the code into a macro which then runs the code. It's a bit roundabout, I know, but I can't think of a better solution, since the contents of my code are based on what's in the dataset.

In my dataset, "test2", I have only one observation of the variable "statement", which is equal to

j1=input(j,anydtdtm.); drop j; rename j1=j; k1=input(k,anydtdtm.); drop k; rename k1=k; l1=input(l,anydtdtm.); drop l; rename l1=l;

I have a macro which is basically

%macro dummy(ds,statements);
    data &ds.2;
        set &ds.;    
        &statements.
    run;
%mend;

then I use call execute to the following:

data test3;
    set test2;
    call execute('%dummy('||strip(ds)||','||strip(statement)||')');
run;

But I get the following error:

ERROR: The keyword parameter J1 was not defined with the macro.

Clearly SAS is interpreting my "=" sign as something other than the contents of a macro variable. I tried using %str and altering my call execute to:

data test3;
    set test2;
    call execute('%dummy('||strip(ds)||','||%str(strip(statement))||')');
run;

but it didn't work. Anyone have any ideas?

Thanks for your help!!

First off, if you use named parameters, this isn't a problem.

%macro dummy(ds=,statements=);
    data &ds.2;
        set &ds.;    
        &statements.
    run;
%mend;


data class;
  set sashelp.class;
run;

data fixes;
  infile datalines truncover;
  length statement $40 all_statements $512 execstr $1024;
  do _n_ = 1 to 2;
      input @1 statement $40.;
      all_statements=catx(';',all_statements,statement);
  end;
  put all_statements;
  execstr = cats('%dummy(ds=class,statements=',all_statements,';)');
  call execute(execstr);
  datalines;
if sex='M' then m_height=height
if sex='F' then f_height=height
;;;;
run;

Then SAS sees the named-parameter equal sign and knows that everything from that to the next comma is the value of that parameter.

Of course, if you have a comma, you still need to do something. Here is where you were close-but-not-quite. %str needs to be quoting the macro call , not the construction of the macro call - in other words, it needs to be inside the quotes.

data fixes;
  infile datalines truncover;
  length statement $40 all_statements $512 execstr $1024;
  do _n_ = 1 to 2;
      input @1 statement $40.;
      all_statements=catx(';',all_statements,statement);
  end;
  put all_statements;
  execstr = cats('%dummy(ds=class,statements=%nrstr(',all_statements,';))');
  call execute(execstr);
  datalines;
if sex in ('M','F') then mfheight=height
if sex='F' then f_height=height
;;;;
run;

Personally I like %nrstr here since it also eliminates those pesky ampersands and percent signs, which might have some meaning.

Putting it there means that when SAS runs that call execute, it passes along that %str or %nrstr and quotes the value that is being sent on.

When you had it in the other place:

execstr = cats('%dummy(ds=class,statements=',%nrstr(all_statements),';)');

What was being protected/quoted was not the text inside all_statements but actually the characters all_statements (in other words, the variable name ). That doesn't really do much for you.

Passing an equal sign is easy, just use named parameters in the call.

%dummy(ds=x,statement=x=2)

It is passing the semi-colons that is really hard. For that you will need to quote the values in the macro call. You can do it with macro quoting, but I find it is easier to use normal quotes and remove them in the macro code.

Have the macro remove any quotes that might surround the parameter value by using the DEQUOTE() function.

%macro dummy(ds,statements);
data &ds.2;
  set &ds.;    
  %sysfunc(dequote(&statements))
run;
%mend;

Let's setup your example statements as a dataset.

data have;
  ds='x';
  statements=
   'j1=input(j,anydtdtm.); drop j; rename j1=j;'
|| 'k1=input(k,anydtdtm.); drop k; rename k1=k;'
|| 'l1=input(l,anydtdtm.); drop l; rename l1=l;'
  ;
run;

And some sample data for those statements to operate on.

data x;
 j='01JAN1960';
 k='10FEB2010';
 l='2014-05-01';
run;

Now you can generate the call using your data set. Use the QUOTE() function to enclose the statements in quotes.

options mprint;
data _null_;
  set have ;
  call execute(cats('%nrstr(%dummy)(',ds,',',quote(trim(statements)),')'));
run;

Here is the LOG.

1    + %dummy(x,"j1=input(j,anydtdtm.); drop j; rename j1=j;k1=input(k,anydtdtm.); drop k; rename
k1=k;l1=input(l,anydtdtm.); drop l; rename l1=l;")
MPRINT(DUMMY):   data x2;
MPRINT(DUMMY):   set x;
MPRINT(DUMMY):   j1=input(j,anydtdtm.);
MPRINT(DUMMY):   drop j;
MPRINT(DUMMY):   rename j1=j;
MPRINT(DUMMY):  k1=input(k,anydtdtm.);
MPRINT(DUMMY):   drop k;
MPRINT(DUMMY):   rename k1=k;
MPRINT(DUMMY):  l1=input(l,anydtdtm.);
MPRINT(DUMMY):   drop l;
MPRINT(DUMMY):   rename l1=l;
MPRINT(DUMMY):   run;

This is one of the cases where it may be easier to generate the code in a file, and then submit it with an %include statement:

filename tempsas temp;
data test3;
  set test2;
  file tempsas;
  put
    'data ' ds +(-1) '2;' /
    '  set ' ds ';' /    
    '  ' statements /
    'run;'
    ; 
run;

You can then start out by looking at the file (use the 'include tempsas' command in an edit window), submitting one datastep to see if everything goes well, and when you are sure that everything is fine, you put

%include tempsas;

in your original code.

After reading the comments here (Thanks for your help guys!), I figured out a different solution (though changing my macro to accept named parameters worked too):

data test3;
    set test2;
    statement=tranwrd(statement,"=",'%nrstr(=)');
    call execute('%dummy('||strip(ds)||','||strip(statement)||')');
run;

Basically, I just altered the "code" to look like this:

j1%nrstr(=)input(j,anydtdtm.); drop j; rename j1%nrstr(=)j; k1%nrstr(=)input(k,anydtdtm.); drop k; rename k1%nrstr(=)k; l1%nrstr(=)input(l,anydtdtm.); drop l; rename l1%nrstr(=)l;

I noticed that if I changed the macro to

%macro dummy(ds=,statements=);
data &ds.2;
  set &ds.;    
  &statements.
run;
%mend;

then this solution didn't work. It said "more positional parameters found than defined". Any ideas why?

So rather pass statements to a macro, since I'm assuming you're also generating your statements to pass to the macro, I would only pass the variable names, original and desired and use an array.

%macro converter(var_in= , var_out=);
    data want;
      set data;
      array have(*) &var_in;
      array want(*) &var_out;

      do i=1 to dim(have);
         want(i)=input(have(i), anydtdtm.);
      end;
      drop &var_in;
     run;
 %mend;


 %converter(var_in= j1 k1 l1, var_out= j k l);

EDIT: Version 2 based on comments.To simplify the process, I rename the variables to temp1-temp{numvars} and then recode them into the variables desired. This was tested successfully.

%macro converter(var_in= j k l);
%let n_vars = %sysfunc(countw(&var_in));
    data want;
      set data (rename = (%do i=1 %to &n_vars;
                            %scan(&var_in, &i)=temp&i
                            %end;));
      array have(*) temp1-temp&n_vars.;
      array want(*) &var_in;

      do i=1 to dim(have);
         want(i)=input(have(i), anydtdtm.);
      end;
      drop temp1-temp&n_vars;
     run;
 %mend;

 %converter(var_in= j k l);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM