SAS DO Loop with Macro - sas

To begin, this is for a class, i dont like this language. Its simple Do loop to print the square root of numbers. The objective is to replace the value in the do loop with macro variables. Here is my source code:
%LET Start_Value = 1;
%LET Stop_Value = 5;
DATA sqrt_table;
DO &Start_Value. TO &Stop_Value.;
Sqrt_n = SQRT(&Start_Value.);
OUTPUT;
END;
RUN;
TITLE 'Square root table from 1 to 5';
PROC PRINT DATA = sqrt_table noobs;
RUN;
TITLE;
The Log says the error is in the DO &Start_Value. "Symbol is not recognized"
I followed the the source coude given, i have decalred the macros as they should be, and i am accessing them as i read to do so. What is the issue?

Macro code in general, and in this case specifically, is just used to replace constant text. First get a working DO loop without any macro variables and then replace the parts that you want to vary with the macro variable references.
So the basic syntax for an iterative DO loop is:
do VAR=START to END;
...
end;
Where VAR is a variable name and start and end are numerical expressions.
Compare that to the pattern of your attempt and you can see that you have left off the VAR= part.
Also the assignment statement is going to assign the same value to SQRT_N on every iteration of the DO loop. Because you have essentially written.
Sqrt_n = SQRT(1);
Remember macro variables are just ways to help you generate the program that you want SAS to actually run.

If you are begginer in SAS don't mix macro lanauge wit 4GL. Here is what you need.
%LET Start_Value = 1;
%LET Stop_Value = 5;
DATA sqrt_table;
DO i = &Start_Value. TO &Stop_Value.;
Sqrt_n = SQRT(i);
OUTPUT;
END;
RUN;

Related

Matching SAS character variables to a list

So I have a vector of search terms, and my main data set. My goal is to create an indicator for each observation in my main data set where variable1 includes at least one of the search terms. Both the search terms and variable1 are character variables.
Currently, I am trying to use a macro to iterate through the search terms, and for each search term, indicate if it is in the variable1. I do not care which search term triggered the match, I just care that there was a match (hence I only need 1 indicator variable at the end).
I am a novice when it comes to using SAS macros and loops, but have tried searching and piecing together code from some online sites, unfortunately, when I run it, it does nothing, not even give me an error.
I have put the code I am trying to run below.
*for example, I am just testing on one of the SASHELP data sets;
*I take the first five team names to create a search list;
data terms; set sashelp.baseball (obs=5);
search_term = substr(team,1,3);
keep search_term;;
run;
*I will be searching through the baseball data set;
data test; set sashelp.baseball;
run;
%macro search;
%local i name_list next_name;
proc SQL;
select distinct search_term into : name_list separated by ' ' from work.terms;
quit;
%let i=1;
%do %while (%scan(&name_list, &i) ne );
%let next_name = %scan(&name_list, &i);
*I think one of my issues is here. I try to loop through the list, and use the find command to find the next_name and if it is in the variable, then I should get a non-zero value returned;
data test; set test;
indicator = index(team,&next_name);
run;
%let i = %eval(&i + 1);
%end;
%mend;
Thanks
Here's the temporary array solution which is fully data driven.
Store the number of terms in a macro variable to assign the length of arrays
Load terms to search into a temporary array
Loop through for each word and search the terms
Exit loop if you find the term to help speed up the process
/*1*/
proc sql noprint;
select count(*) into :num_search_terms from terms;
quit;
%put &num_search_terms.;
data flagged;
*declare array;
array _search(&num_search_terms.) $ _temporary_;
/*2*/
*load array into memory;
if _n_ = 1 then do j=1 to &num_search_terms.;
set terms;
_search(j) = search_term;
end;
set test;
*set flag to 0 for initial start;
flag = 0;
/*3*/
*loop through and craete flag;
do i=1 to &num_search_terms. while(flag=0); /*4*/
if find(team, _search(i), 'it')>0 then flag=1;
end;
drop i j search_term ;
run;
Not sure I totally understand what you are trying to do but if you want to add a new binary variable that indicates if any of the substrings are found just use code like:
data want;
set have;
indicator = index(term,'string1') or index(term,'string2')
... or index(term,'string27') ;
run;
Not sure what a "vector" would be but if you had the list of terms in a dataset you could easily generate that code from the data. And then use %include to add it to your program.
filename code temp;
data _null_;
set term_list end=eof;
file code ;
if _n_ =1 then put 'indicator=' # ;
else put ' or ' #;
put 'index(term,' string :$quote. ')' #;
if eof then put ';' ;
run;
data want;
set have;
%include code / source2;
run;
If you did want to think about creating a macro to generate code like that then the parameters to the macro might be the two input dataset names, the two input variable names and the output variable name.

Mixing macro-DO-loops with data step DO-loops

Some context:
I have a string of digits (not ordered, but with known range 1 - 78) and I want to extract the digits to create specific variables with it, so I have
"64,2,3" => var_64 = 1; var_02 = 2; var_03 = 1; (the rest, like var_01 are all set to missing)
I basically came up with two solutions, one is using a macro DO loop and the other one a data step DO loop. The non-macro solution was to fist initialize all variables var_01 - var_78 (via a macro), then to put them into an array and then to gradually set the values of this array while looping through the string, word-by-word.
I then realized that it would be way easier to use the loop iterator as a macro variable and I came up with this MWE:
%macro fast(w,l);
do p = 1 to &l.;
%do j = 1 %to 9;
if &j. = scan(&w.,p,",") then var_0&j. = 1 ;
%end;
%do j = 10 %to 78;
if &j. = scan(&w.,p,",") then var_&j. = 1 ;
%end;
end;
%mend;
data want;
string = "2,4,64,54,1,4,7";
l = countw(string,",");
%fast(string,l);
run;
It works (no errors, no warnings, expected result) but I am unsure about mixing macro-DO-loops and non-macro-DO-loops. Could this lead to any inconsistencies or should I just stay with the non-macro solution?
Your current code is comparing numbers like 1 to strings like "1".
&j. = scan(&w.,p,",")
It will work as long as the strings can be converted into numbers, but it is not a good practice. It would be better to explicitly convert the strings into numbers.
input(scan(&w.,p,","),32.)
You can do what you want with an array. Use the number generated from the next item in the list as the index into the array.
data want;
string = "2,4,64,54,1,4,7";
array var_ var_01-var_78 ;
do index=1 to countw(string,",");
var_[input(scan(string,index,","),32.)]=1;
end;
drop index;
run;

appending a counter to a string obtained by dereferencing macro variable

how do I get so inside the loop I get: var1, var2? I know it does not work to dereference j but the meaning gets more clear to what I want to do (see below)
%let var1 = apple;
%let var2 = pear;
data _null_;
do j=1 to j=2;
put &var&j; //<---?
end;
run;
in the log:
apple
pear
As noted above, J is not a macro variable so you cannot use it as such. You can use the SYMGET function to retrieve the value though. Assuming you want data step logic for some reason:
data _null_;
do i=1 to 2;
x= symget(catt('var', i));
put x;
end;
run;
Sounds like you want to resolve a macro variable whose name you are creating by appending the value of another macro variable to some constant prefix.
If you try to use code like this:
%let var1 = apple;
%let var2 = pear;
%let j=1 ;
%put &var&j;
You will get an error message that the macro variable named VAR does not exist.
You need to signal to the macro processor that it needs to delay trying to evaluate &var until after the suffix has been appended. The way to do this is to double the first &.
%put &&var&j;
The presence of double &'s will cause the macro processor to replace them with a single & and set a reminder to itself the re-scan the result for more macro variable references.
So the first pass will replace && with & and replace &j with 1. Then the second pass will replace &var1 with apple.

SAS 9.3 passing a variable with spaces to a macro

I need to pass a variable that contains spaces to a macro. And use this variable to make some logic and to buid a new column inside the macro.
I've tried something like:
%MACRO func(var);
if first.id then &var = 0;
retain &var;
if descr = %unquote(%str(%'&var%')) then &var = 1;
%MEND;
proc sort data=work.table5a;
by id;
run;
data temp;
set work.table5a;
by id;
%func(PLUTO)
%func(PAPERINO)
%func(BANDA BASSOTTI)
if last.id;
run;
ERROR is:
NOTE: Line generated by the macro variable "VAR".
37 BANDA BASSOTTI
_____
180
ERROR 180-322: Statement is not valid or it is used out of proper order.
If i comment %prova(BANDA BASSOTTI) it works. Any suggestions ?
thanks
You're using &var to create a variable name, and if you want to have a variable name with spaces in it, you need to use the variable name literals, e.g. "BANDA BASSOTTI"n. I haven't done this myself, seems like it makes the code uglier and harder to write, but something like this seems to work:
options validvarname=any;
%MACRO func(var);
retain "&var"n;
if first.id then "&var"n = 0;
if descr = "&var" then "&var"n = 1;
%MEND;

SAS Do loops: use loop variable inside the loop to create lagged variables

I would like to create variables containing lagged values of a given variable for a large number of lags. How could I do this? I try the following:
data out;
set in;
do i = 1 to 50;
%let j = i;
lag_&j = Lag&j.(x);
end;
run;
How can I get the loop variable i into the macro variable j or how to use it directly to create the appropriately named variable and for the Lag function?
Chris J answers the question, but here i'll provide my preferred way of doing this.
%macro lagvar(var=,num=);
%do _iter = 1 %to &num.;
lag_&_iter. = lag&_iter.(&var.);
%end;
%mend lagvar;
data out;
set in;
%lagvar(var=x,num=50); *semicolon optional here;
run;
This is a more modular usage of the macro loop (and more readable, assuming you use intelligent names - the above is okay, you could do even more with the name if you wanted to be very clear, and of course add comments).
You're mixing macro & datastep syntax incorrectly...
You need a macro-loop (%DO instead of do) to generate the datastep code (i.e. lag1-lag50), and macro-loops need to be within a macro.
%MACRO LAGLOOP ;
data out ;
set in ;
%DO J = 1 %TO 50 ;
lag_&J = lag&J(x) ;
%END ;
run ;
%MEND ;
%LAGLOOP ;