2
votes

Basically I'm writing a macro that would take as parameters my input table, output table, and a list of variables. My list of variables appears as a single parameter, for which I use the space character as the delimiter. My macro is supposed to separate my list into nbvar macro-variables, which are going to contain the names of my (SAS) variables. I then use a datastep to input my (SAS) variables from their original character format to numerical values.

Here is my piece of code:

%macro convert_car_to_num(input,output,listvar);

/* First I split my list into nbvar variables named var&i
%qscan to avoid macro resolution of names, not really necessary here
but still works fine. My delimiter is space character, hence 
%str( ) in the %qscan*/

%let nbvar=%sysfunc(countw(&listvar));
%do i = 1 %to &nbvar;
    %let var&i=%qscan(&listvar,&i,%str( ));
%end;

/*Here is my data step. &&var&i_num is resolved just fine*/
data &output;
set &input;
%do i = 1 %to &nbvar;
    &&var&i.._num = input(&&var&i,BEST16.);
%end;
run;

%mend;

Since &&var&i.._num and &&var&i are resolved, I would expect my code to work, but my log shows:

varname_num

180

Where the resolved name "varname" is underlined. And a bit after I find:

ERROR 180-322: Statement is not valid or it is used out of proper order.

Which is usually the standard error for a misplaced semi-colon. Yet I know my macro-variables are resolved since mprint shows:

MPRINT(CONVERT_CAR_TO_NUM): varname_num = input(varname,BEST16.)

NOTE: Line generated by the macro variable "VAR26".

MPRINT(CONVERT_CAR_TO_NUM): run;

Where varname is the right name for the 26th variable in my list, which indicates the resolution worked just fine.

To make it more incomprehensible for me, the same piece of code where I would indicate:

&&var&i.. = input(&&var&i,BEST16.);

DOES compile, even though it doesn't end up with the intended result (variables would still be char).

Similarly, the same code with:

&&var&i.._num = &&var&i;

does not compile either.

I've also tested changing the name of my macro-variable to num_&&var&i or n&&var&i, or even first declaring a macro-variable "name" which would contain &&var&i, all to the same effect. Not choosing the same name as the initial variable seems to cause the code to show the 180 error.

I guess the problem resides with trying to declare a variable, knowing that a previous and similar piece of code I wrote does work, with the datastep being a comparison (to transform missing values to zero from a list of variables also):

data &output;
set &input;
    %do i = 1 %to &nbvar;
        if &&var&i = . then &&var&i = 0;
    %end;
run;

But for this same piece of code, if I try to create a new variable (again with any name, for that matter), writing:

if num_&&var&i = . then &&var&i = 0;

I find myself with the resolved name underlined again, but now pointing to the following error:

ERROR 22-322: Syntax error, expecting one of the following: !, !!, &, (, *, **, +, -, /, ;, <, <=, <>, =, >, ><, >=, AND, EQ, GE, GT, IN, LE, LT, MAX, MIN, NE, NG, NL, NOTIN, OR, [, ^=, {, |, ||, ~=.

2

2 Answers

2
votes

This is a problem with SAS failing to unquote values automatically. The rule I learned is that if your SAS code (shown by MPRINT) looks valid, but you are getting errors, try unquoting.

In your case, changing to:

%unquote(&&var&i.._num) = input(&&var&i,BEST16.);

Makes the code work. Of course as per your comment you probably don't need %qscan which is introducing the problematic quoting characters. If you change that to %scan, you won't need to %unquote() it because it will not have been quoted in the first place.

Also agree with approach of @Foxer using a single macro variable to store the i_th variable in the list. Would also recommend making these %local variables in order to avoid collisions. Could be something like:

%macro convert_car_to_num(input,output,listvar);
  %local i vari;

  data &output;
    set &input;
    %do i = 1 %to %sysfunc(countw(&listvar,%str( )));
      %let vari=%scan(&listvar,&i,%str( ));
      &vari._num=input(&vari,best16.);
    %end;
  run;

%mend;
1
votes

Does the following give what you need? My guess is that you have too much going on in separate if-then-do statements:

%macro new(input,output,listvar);
data &output; set &input;
%do i=1 %to %sysfunc(countw(&listvar.));
    %let var=%scan(&listvar.,&i.);
        var&i._num = input(&var.,BEST16.);
%end;
run;
%mend;

%new(have,want,&listvar.);

Below may also do what you want if you're trying to create an individual macro variable for each variable you're looping over (though in this case it's probably not worth it but just shows another useful method for other applications):

** put variables into dataset **;
proc sql noprint;
    create table vars
    as select name,type
    from dictionary.columns
    where upcase(libname)="WORK" and 
        upcase(memname)="HAVE" and
        type = "char";
quit; 

** create total count and separate macro variable for each variable **;
data _null_; set vars end=last;
    by name;
    i+1;
    call symputx('name'||strip(put(i,8.)), name);
        if last then call symputx('count',i);
run;

%put &count.;
%put &name1.;
%put &name2.;
%put &name3.;

** loop over each variable using the total count **;
%macro new(input,output);
data &output; set &input;
%do i=1 %to &count.;
    &&name&i.._num = input(&&name&i,BEST16.);
%end;
run;

%mend;

%new(have,want);