2

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 Answers 2

2

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;
Sign up to request clarification or add additional context in comments.

7 Comments

That's exactly the explanation I was looking for. I wanted to be extra careful using %qscan to make sure potential other users would not "mess" with bizarre names but I did not fully realize what it meant having %qscan actually quoting to avoid any unexpected resolution.
The idea of a unique temporary variable is a good one, but I actually need all the variables further outside the loop. As for making a unique loop : %do i = 1 %to %sysfunc(countw(&listvar,%str( ))); I do think it's much better, but since I am likely to share those macros with others, I prefer them to decompose the steps as to not confuse people and make it easier for them to reuse for other purpose.
I still have one interrogation though : if I did really need the %qscan to avoid bad resolution, could I still use : %unquote(&&var&i.._num) My guess would be no, since unquoting would lead to the potential unwanted resolution. Am I wrong ? If not, how would I deal with that ? (Truth be told it's a messed up situation, bu I'm just trying to fully understand the working of macro and the tools available to prevent unexpected results). Still, Thank you very much for the first and correct answer
I think I have found the answer to my last interrogation, it seems to work using : %sysfunc(cat(&&var&i,_num)). The problem really is that a macro sas quoted macro-variable cannot be "automatically" concatenated with another piece of string by the SAS compilator (maybe using the wrong term here, I'm still a bit unclear if it is the SASmacro or SAS compilator, but I think it's the latter). But using function cat does the trick since the compilator then understands what's intended. SAS macro as a "meta-layer" never ceases to surprise (and exhaust) me.
I think your last comment is wrong. A macro variable with quoted (masked) values can be concatenated to another value in the usual way, because even with the hidden characters, it's still text. The problem is the macro language is supposed to automatically unquote them at the final step before the sas compilers gets them,, and sometimes this doesn't happen. Using %sysfunc(catt()) would force the unquoting, but %unquote() is clearer. For more details, see Ian Whitlock's excellent papers, e.g. www2.sas.com/proceedings/sugi28/011-28.pdf
|
1

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

2 Comments

Your answer is correct, since the source of the error was the quoting realized by the %qscan func and since %scan works just fine for my immediate needs. Your short version is what I would aim to write for myself, but for various reasons (mainly I am likely to share the code and I do need individual macro var for each var) I prefer to keep it longer but probably clearer for other users. I'm going to give a look to your sql version, but it seems to me I cannot choose which variables I want to input, which is at the heart of my needs (some var are rightfully char in my initial dataset).
I could find ways around such as first creating a dataset containing only the var I want to input but I find it defies the purpose of a ready to use solution. Still, thank's for all the ideas and remarks

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.