DATA Step, Macro, Functions and more

do-loop within a macro

Reply
N/A
Posts: 0

do-loop within a macro

I am trying to write code that seems like it should be easy, but it continues not to work and I'm stumped. In brief, I want to take a subset of subjects who have scores within a particular range (e.g., 1-10) on a variable, (e.g.,Y), and then, for that subset, estimate proc corr alpha for those people on a set of other vars (e.g., x1-x10). THEN, I want to increment that range just a bit (e.g., people who have scores within the range of 1.5 to 10.5 on Y), then do a proc corr alpha for that new subset of people (which will overlap somewhat with the first) on the same x1-x10 vars, THEN go back and keep incrementing the range until the top of the range of Y is reached, each time producing a proc corr alpha for the new subset of people. (We are doing this to obtain a 'sliding estimate' of alpha as individuals move up a range of the subsetting variable.)

Below is the code I wrote. It doesn't produce any errors but it also doesn't produce any output. Any help is greatly appreciated!! (NOTE: below Y, per above, is sum_10sx and the variables on which the alpha is desired are in caps, as should be obvious. The maximum score possible on sum_10sx is 30, thus the "if high le 30 then continue" statement.)

data temp3;
set temp2;
low=0;
high=10;
increment=1;
%macro do_alpha;
%do %while (sum_10sx ge low and sum_10sx le high);
PROC corr alpha noprob nomiss nocorr;
VAR DEPRESS_mood IRRITBLT ANHEDONIA WEIGHT SLEEP PMOTOR FATIGUE ESTEEM THOUGHT SUICIDE;
run;
low = low + increment;
high = high + increment;
if high le 30 then continue;
%end;
%mend do_alpha;
SAS Super FREQ
Posts: 8,740

Re: do-loop within a macro

Hi:
Generally speaking, it is a good idea to start with a working SAS program -before- you "macro-ize" the program. In the regular SAS world, without any macro code involved, this would be problematic SAS code:
[pre]
data temp3;
set temp2;
low=0;
high=10;
increment=1;

*** --- *** PROC Step is step boundary to data step program;
PROC corr alpha noprob nomiss nocorr;
VAR DEPRESS_mood IRRITBLT ANHEDONIA WEIGHT SLEEP PMOTOR FATIGUE ESTEEM THOUGHT SUICIDE;
run; <--This is step boundary for PROC CORR step;

*** --- *** Next two statements would be in "open code;
low = low + increment;
high = high + increment;
if high le 30 then continue;
*** --- *** end of steps in open code;
[/pre]

The reason I say that the "non-macro" code is problematic is that the PROC CORR statement will act as the step boundary to the DATA step program. Defining a SAS macro program (what's inside %MACRO/%MEND) is not the same as -RUNNING- or invoking a SAS Macro program.

Consider this -working- SAS program (which uses SASHELP.CLASS):
[pre]
ods listing;
PROC corr data=sashelp.class alpha noprob nomiss nocorr;
where age = 12;
VAR age height weight;
run;

PROC corr data=sashelp.class alpha noprob nomiss nocorr;
where age = 13;
VAR age height weight;
run;

PROC corr data=sashelp.class alpha noprob nomiss nocorr;
where age = 14;
VAR age height weight;
run;

PROC corr data=sashelp.class alpha noprob nomiss nocorr;
where age = 15;
VAR age height weight;
run;
[/pre]

The program consists of 4 PROC CORR steps...each with a different WHERE statement. Each PROC CORR step is essentially the same except for the WHERE statement. A valid SAS macro program DEFINITION would then be:

[pre]
%macro docorr(age=);
ods listing;
PROC corr data=sashelp.class alpha noprob nomiss nocorr;
title "PROC CORR for Age = &age";
where age = &age;
VAR age height weight;
run;
%mend docorr;
[/pre]

But, that is just the DEFINITION. Now I have to INVOKE the macro program. To invoke the %DOCORR macro program, which causes the PROC CORR step to be actually sent to the SAS compiler, I need to do this:

[pre]
options mprint symbolgen mlogic;
%docorr(age=12)
%docorr(age=13)
%docorr(age=14)
%docorr(age=15)
[/pre]

The %DOCORR macro statement is actually where the macro program is INVOKED and the (age=12) or (age=13) is a keyword parameter that provides necessary information for the WHERE statement and the TITLE statement in the macro program.

Note how the %MACRO and %MEND do not actually cause any statements to be executed. All that happens when the macro compiles successfully is that the macro program is stored in the WORK.SASMACR catalog -- where the macro program sits and waits to be invoked.

I could have written the macro program differently -- to contain a %DO loop, like this:
[pre]
%macro altcorr;
ods listing;
%do age = 12 %to 15;
PROC corr data=sashelp.class alpha noprob nomiss nocorr;
title "Alternate PROC CORR for Age = &age";
where age = &age;
VAR age height weight;
run;
%end;
%mend altcorr;
[/pre]

And, then the above macro program would be invoked just 1 time
[pre]%altcorr;[/pre]

and the %DO loop would cause 4 PROC CORR steps to be written to the compiler. Each time a PROC CORR step was generated, the %DO loop would automatically increment the AGE macro variable. I used a simple %DO loop instead of a more complex loop because it is easier to explain.

There are ways to use a DATA step program to write code for you and use a CALL EXECUTE inside a DATA step program to place code into the queue for the compiler. You, cannot, however, create macro variables in a DATA step program and then use those macro variables in the same program. SAS must encounter a step boundary before those macro variables are available to you.

In a similar vein, you cannot use DATA step variables in your macro %DO loop, as you attempt in your %DO %WHILE loop. Your DATA step variables are not available to the macro processor. There are ways to make your macro program create and use DATA step variables out of the Program Data Vector, but the method you are trying won't work.

I recommend that you read the documentation on how the macro facility operates with a data step program before you go much further with this attempt.
http://support.sas.com/documentation/cdl/en/mcrolref/61885/HTML/default/getstart.htm
http://support.sas.com/documentation/cdl/en/mcrolref/61885/HTML/default/tw3514-definemacro.htm
http://support.sas.com/documentation/cdl/en/mcrolref/61885/HTML/default/a001302436.htm
http://support.sas.com/documentation/cdl/en/mcrolref/61885/HTML/default/tw3514-symput.htm
http://support.sas.com/documentation/cdl/en/mcrolref/61885/HTML/default/a001072359.htm

The documentation on the macro facility is very good. In addition, there are lots of previous forum postings on some of the macro facility basics and I find this user group paper is an excellent introduction to the topic.
http://www2.sas.com/proceedings/sugi28/056-28.pdf

A Google search would reveal other papers on the Macro facility -- from the beginner level to the advanced level. Here are some Google search strings to try:
SAS macro beginner
SAS macro tutorial
SAS macro %DO

cynthia
N/A
Posts: 0

Re: do-loop within a macro

Thanks very much! I did have a SAS program that worked without macros, but it was so long and required so many manual changes that I had hoped to automate it. I haven't worked with macros of this sort a lot, though, as you can tell, so I wasn't quite sure how to proceed. Your instructions make perfect sense and are very clear. Thank you very much for your time and expertise. I'll read the sources you recommend and hope to figure this out.
Thanks, again!
SAS Super FREQ
Posts: 8,740

Re: do-loop within a macro

Hi:
Here are a few more tips. Notice how I started with 4 working PROC CORR steps -- each for a separate age. The -first- step was making the %DOCORR macro program -- in which I condensed all 4 separate steps into 1 step and tested my theory that &AGE was the only macro variable I needed. Then I invoked that macro program 4 separate times.

The %ALTCORR macro program was a refinement of the %DOCORR macro program -- because it added macro logic to the program. If, for example, I had needed to -do- something or perform some other test between age 13 and age 14 before I continued with processing, then the first macro program might have been a better choice than the second macro program because with the first program, I can control when the macro gets invoked.

I like to call the SAS Macro facility a big, dumb, typewriter. The design of the Macro facility is quite powerful, but the ONLY thing the Macro facility does is resolve macro variable references and generate code to go to the compiler. The Macro facility doesn't actually -execute- or -run- any SAS programs. So the Macro facility has the instructions for how to write a program. By the time your program gets to the compiler, there are NO Macro variable references or & or % left in the program -- except by design.

You can use the Macro facility to generate part of a single statement, generate just one statement, generate part of a program, generate a whole program, and using conditional logic, based on values in the data set, generate one program if one condition is met or generate an alternate program if the condition is not met.

There are a few rules that I always follow when generating macro code:
1) Start with a working SAS program
2) Take the program from #1 and figure out where you can use macro variables. Every manual change you have to make to rerun a program is a candidate to become a macro variable. You need to note these places and start planning what you are going to call the macro variables and what their initial value needs to be and how they change from invocation to invocation. Use %LET statements to set values for the macro variables at the top of the program. Then use macro variable references in your code. Understand how to use the macro variable references appropriated. For example:

where age = &age;
title "PROC CORR for AGE = &age";

In the WHERE statement, there are no quotes because AGE is a numeric variable. Remember that the macro facility is just TYPING 12 or 13 or 14 in the slot or placeholder &AGE. Since no quotes belong to the WHERE statement, there are no quotes in my macro invocation. In the TITLE statement, however, the TITLE statement needs to have a quoted string. I use double quotes because the macro variable reference &AGE will not resolve inside single quotes.

If I had been testing for NAME, for example:
%LET want = Robert;
... more code ...
where name = "&want";

Then the quotes belong to the WHERE statement and not to the %LET statement.

3) Once I have identified -and TESTED- my macro variables now it's time to generate a simple macro program from program #2. I have to test the macro program invocation and test how I am setting my parameters and make sure that the output from step 3 is the same as the output from the programs in step 1 and step 2.

4) At this step, I might change my design and use a macro %DO loop or use some conditional %IF logic in the macro program. By the time you get to this step, your program logic (what goes to the compiler) should be pretty solid. And, by this point, you know what output to expect.

I usually don't jump from program #1 straight to program #4. For one thing, sometimes macro program errors can be a little opaque about where the error occurred. So it's helpful to use
[pre]options mprint symbolgen mlogic; [/pre]

as debugging statements in the early steps so that you resolve as many incorrectly quoted, unresolved macro variable references, single quotes, and missing & or incorrect &macvar references as you can.

Also, by the time you get to program #3 and program #4, you have to know enough about macro variables to understand "scope" issues -- where macro variables are global and where they are local -- which influences whether macro variable values are available after the macro program is over. If you need to reference some value that was generated or created by code -inside- a macro program, you need to make that macro variable a GLOBAL macro variable and assign it a value using the correct method so the value will be available to your for subsequent processing.

The SAS macro facility is fun in a geeky, meta-language, "I think it's cool to use one set of programming statements to generate another program or programs for me" kind of way. If you approach the writing of your macro program with the understanding that the Macro facility is ONLY generating code to send to the compiler, then you are off to a good start.

cynthia
N/A
Posts: 0

Re: do-loop within a macro

Wow. Again, thank you for all of your time and help. I appreciate that very, very much. Basically, the problem started for me when I couldn't get a SAS program to run without going back each time, by hand, and incrementing the range of the variable on which I wanted to subset people and then estimate alpha for them. I constructed a do-loop, much like I have in the past, but I kept getting an 'unclosed DO block' error message and then I read on the web somewhere that because I had a proc inside a do-loop, SAS was reading it as ending the loop prematurely and giving me that error message. The place I was reading said that to solve it, I had to do the whole thing inside a macro. That's where the trouble really started (!), since that threw me into a world of using (or trying to use) macros in SAS in a way I never had before.
At any rate, I've included the code below that gives me one alpha in SAS but won't go back through the do-loop to increment the range and continue to output the proc corr for a new subset of people. I tried moving the end and run statements to various places, but I continued to get the 'unclosed DO block' no matter what I did. Maybe the below will show you what I was trying to do and there is some simple way of doing it (without macros) that I haven't seen. I can also post this somewhere under 'do loops' if you think that's better. No matter what happens, THANK YOU for your help. Much appreciated!!!

data temp2;
set temp2;

*below, low and high denote the low and high end a range of scores
(here a 10-point range) across the variable whose actual range is 0-30
on which we want to subset people, increment = the amount we want to
increment that range of scores each time. Thus, below, the window of
10 spaces moves by .5 each time--so the first window is 0 to 10,
second is 0.5 to 10.5, etc.*;

low=0;
high=10;
increment=0.5;

*below, c is a counter, telling SAS to execute the procedure 60 times,
using data within a 10-point window and moving the window up .5 each
time,ending at the top of the possible range, which here is 30 (thus 30
will be hit as the top of the possible range of the subsetting
variable on the 60th time).'sum_10sx' is the variable on whose values we
are subsetting ;

do c = 1 to 60 by 1 while (sum_10sx ge low and sum_10sx le high);

proc corr alpha nomiss noprob nocorr;

*DEPRESS_mood--SUICIDE are the variables we want the alpha on for the subset of people*;
var DEPRESS_mood IRRITBLT ANHEDONIA WEIGHT SLEEP PMOTOR FATIGUE ESTEEM THOUGHT SUICIDE;
run;

low= low + increment;
high= high + increment;
end;
run;
Super Contributor
Super Contributor
Posts: 3,174

Re: do-loop within a macro

You do have another option - that is writing SAS code to a temporary sequential file and then using %INCLUDE ; to include the "generated" code.

Your DATA step, as coded, would need to use PUT statements to write whatever code you want to execute out to the temp file, based on your selection/subsetting criteria. Any additional conditional or variable SAS code that needs to be used with your subsequent code execution must be generated as part of the PUT statements.

An oversimplified example is shown below, where a SAS variable value for X is assigned to a new variable Y and that variable's value is put to the SASLOG:

FILENAME TEMPSAS '&&TEMPSAS';
DATA _NULL_;
X=10;
FILE TEMPSAS;
PUT 'data _null_;' /
'y=' x ';' /
'putlog y= ;' /
'run;' ;
stop;
run;
%include tempsas;

Scott Barry
SBBWorks, Inc.
N/A
Posts: 0

Re: do-loop within a macro

Thank you very much! I'll give that a shot and see if I can make it work. Again, thank you!!
Respected Advisor
Posts: 3,777

Re: do-loop within a macro

It would seem much easier to examine the values of SCORE and make observations with a new GROUPing variable. Then just run PROC CORR BY the new variable. No need to involve macro to make multiple calls to procs or any other code gen method for that matter. Consiser the following which should model your processs.

[pre]
data have;
do id = 1 to 50;
score = ranuni(12345);
output;
end;
run;
data needv / view=needv;
set have;
range = .2;
do center = 0 to 1 by .1;
if center-range le score le center+range then do;
group = cats(put(center,3.1),'+/-',range);
output;
end;
end;
run;
proc sort data=needV out=need;
by group;
run;
*proc corr or what ever BY GROUP;

[/pre]
Valued Guide
Posts: 2,174

Re: do-loop within a macro

data _null_ ;
commend your restrained politeness!
On sas-L David Cassell might have been much less polite ;-)
He really doesn't like to see macros being used where by group processing has extended the functionality of the stats procedures
N/A
Posts: 0

Re: do-loop within a macro

Thanks very much. Wasn't trying to make this any harder than it needed to be. Just wasn't sure the best way to do it. Your code makes sense. Will give it a try.
Thanks!
Ask a Question
Discussion stats
  • 9 replies
  • 433 views
  • 0 likes
  • 5 in conversation