Loop with two counters - loops

Following is some C code. How do I do the same in sas?
For(i=30, j=1; i<=41, j<=12; i++, j++)
(
closure(i,j) /*calling function with input parameters I and j */
);
I basically want to do the following macro calls using a loop with two counters I and J
%closure(30,201201);
%closure(31,201202);
%closure(32,201203);
%closure(33,201204);
%closure(34,201205);
%closure(35,201206);
%closure(36,201207);
%closure(37,201208);
%closure(38,201209);
%closure(39,201210);
%closure(40,201211);
%closure(41,201212);
Please note that I do not want to use a nested loop.
Tips are appreciated.

Doing this in SAS depends on how your data is structured. It's possible to do this:
%do i = 1 to 12;
%closure(%eval(29+i),%eval(201200+i));
%end;
That's a bit odd, but it should work fine.
You could also do it in the %closure macro. Pass i and then determine the value of the other parameters inside the macro, if they always have this relationship. If they always have some relationship, but the 2012 and 18 parts are variable, then you have several options:
Define 2012 and 29 as macro variables ahead of this step, and replace them in the code with such.
%let year=2012;
%let startrec=29;
%do i = 1 to 12;
%closure(%eval(&startrec.+&i.),%eval(&year.00+&i.));
%end;
Use date functions to determine the value of j, if it is not always 01-12.
%closure(30,%sysfunc(intnx(month,'01JUN2011'd,&i.-1)))
(you may want to format the result in YYYYMM format, or you may be just as well off using the date result, depending on %closure)
Define all of these terms in a dataset, and call the macro from the dataset.
data to_call;
input i j;
datalines;
30 201201
31 201202
.... ;
run;
proc sql;
select cats('%closure(',i,',',j,')') into :calllist separated by ' ' from to_call;
quit;
&calllist.
That's a more 'SAS'sy way to do things, making the process data driven. Most commonly used when the i and j parameters are stored as data element somewhere (in a control table, for example, or derived from some other data source).

So If you want
%closure(30,201201);
%closure(31,201202);
%closure(32,201203);
%closure(33,201204);
%closure(34,201205);
%closure(35,201206);
%closure(36,201207);
%closure(37,201208);
%closure(38,201209);
%closure(39,201210);
%closure(40,201211);
%closure(41,201212);
then it would be better either you calculate the value of J and bring it to 201200 aur something near about.
Or you should start the j loop with 201201 and end it to 201212
simply go for
For(i=30, j=201201; i<=41, j<=201212; i++, j++)
(
closure(i,j)
);

Related

call execute inside a loop pulling from one table to execute a macro

i have bellow currently
%macro sqlloop (event_id);
...lots of code, mostly proc sql segments ...
%mend;
that generates an output table (named export_table2). I need to be able to run this code dozens of time for every value in another table (named vars). my trial code testing what I want it to do is below (basically manually typing in the first two values of this 68 row table)
data ;
%let empl_nbr_var = '222';
%let fleet = '7ER';
%let position = 'A';
%let base = 'BWI';
%sqlloop(event_id = 1);
run;
data summary_pilots;
set work.export_table2;
run;
data;
%let empl_nbr_var = '111';
%let fleet = '320';
%let position = 'B';
%let base = 'CHS';
%sqlloop(event_id = 2);
run;
data summary_pilots;
set summary_pilots work.export_table2;
run;
This produces the final output of each execution stacked into one table called summary_pilots. How can I do this in a loop, prehaps using call execute to iterate through each row of vars? The columns of vars are exactly what I need for the macro variables, and I want to iterate through every single row to assign those macro variable and run my %sqlloop again. Thanks for the help!
EDIT:
currently figuring out how call execute works and see how its helpful here but still a bit stuck... code below works exactly as youd think, printing out all the variables in the table vars into the log.
data ;
set work.vars;
call execute( '%put='|| strip(empl_nbr_var) || ';
%put = ' || strip(fleet) ||';
%put = '|| strip(position) ||';
%put = ' || strip(base) ||';' );
run;
I am trying to use the below code, but am getting a crazy amount of errors due to the macros being assigned weirdly. The types in the columns of vars match exactly what I want them to be in the macros, but it still looks like that might be the issue here?
data ;
set work.vars;
call execute( '
%let empl_nbr_var =' || strip(empl_nbr_var) || ';
%let fleet = ' || strip(fleet) ||';
%let position = '|| strip(position) ||';
%let base = ' || strip(base) ||';
%sqlloop(event_id = 17);' );
run;
and the event ID doesnt actually matter here so i just left that as a random number for now.
Assuming your work.Vars contain data like this:
empl_nbr_var
fleet
position
base
222
7ER
A
BWI
111
320
B
CHS
...
...
...
...
Consider extending your macro to receive such input parameters:
%macro sqlloop(event_id, empl_nbr_var, fleet, position, base);
...lots of code, mostly proc sql segments ...
%mend;
Then, build run macro with concatenated data values via call execute. Below passes 17 into event_id parameter.
data _null_;
set Work.Vars;
args = catx("', '", empl_nbr_var, fleet, position, base);
args = '%sqlloop(17,'''|| strip(args) || ''');';
put args $char.; /* VIEW CALL COMMAND */
call execute(args); /* RUN CALL COMMAND */
run;
It makes no sense to code %LET statements in the middle of a data step. The macro processor will evaluate them before it passes the text of the data step code to SAS to process. Avoid confusing yourself by moving the %LET statements before the data step.
If the macro needs values of macros variables, like FLEET, as input then make those things parameters to the macro. Don't create a macro that references "magic" macro variables, macro variables that are neither input parameters nor created by the macro. Instead the reference to them just appears in the middle of the macro definition as if their values will appear by magic somehow.
%macro sqlloop(empl_nbr_var,fleet,position,base);
... code that uses &fleet.
%mend;
If you have a lot of combinations of parameters you want run through your macro then collect them into a dataset first.
data inputs ;
input empl_nbr_var fleet $ position $ base $ ;
cards;
222 7ER A BWI
111 320 B CHS
;
Then you can use those dataset variables to generate the calls to the macro. You could try using call execute() to do this, but personally I find it a lot easier to use a data step to write the code to a file. Then you can examine the file and make sure the code generation logic is correct. Plus you can use the power of the PUT statement to make the code generation easier. For example if the variable names match the parameter names you can use named output.
filename code temp;
data _null_;
set inputs;
file code ;
put '%sqlloop(' empl_nbr_var= ',' fleet= ',' position= ',' base= ')';
run;
Which will generate code like:
%sqlloop(empl_nbr_var=222 ,fleet=7ER ,position=A ,base=BWI )
%sqlloop(empl_nbr_var=111 ,fleet=320 ,position=B ,base=CHS )
Once you are confident that it is generating the right code use the %INCLUDE command to run the code it generates.
%include code / source2;
If the macro does not have its own step for aggregating the results you could include that step in the code generation.
filename code temp;
data _null_;
set inputs;
file code ;
put '%sqlloop(' empl_nbr_var= ',' fleet= ',' position= ',' base= ')';
put 'proc append base=summary_pilots data=export_table force; run;' ;
run;
%include code / source2;

Apply SAS Program to several dataset with macro

I am very new to SAS so I apologize in advance. I am using the SAS university edition.
I have 20 datasets each from a certain year (1997-2017), all containing information captured in 30 variables. Now, I want to apply the same code to all of the datasets, however some code chunks only to variables of certain years. Therefore, I wanted to use a macro that ranges from 1997-2017 doing something like...
LIBNAME IN '/folders/myfolders/fake_data';
%let j= 1997 to 2017;
data fake_&j;
set fake_data;
proc import out= fake_&j datafile = "/folders/myfolders/fake_data/mz_&j.dta" replace
* Year;
year = j;
to access the dataset fake_1997.dta, create a year variable that takes on the value of the dataset's name (1997) apply the code (see below) to it, then do the same with mz_1998.dta and so on.
An example of the code that I want to apply to all of the data would be
* Weights;
if (j GE 1997 AND j LE 2004) then
shrf = x;
else if (j GE 2005 AND j LE 2017) then
shrf = y;
Thank you so much in advance!
Macro code, in part, is code that writes code. The writing is not so much an active process like a 'write' or 'print' or 'echo', but more akin to a boilerplate or template system.
The macro %DO loop can not exist in 'open code', so it must be coded inside a macro definition. The macro is 'invoked' in order to have it write (or generate) the code. You might sometimes see the term 'gencode' to mean generated code produced by invoking a macro.
Proc IMPORT is great for reading consistently data on a regular basis, or for dealing with first explorations. IMPORT does not do any data transformations or allow you to add new variables during the import. You will need a second step, a DATA step, to perform those actions.
Name you macros according to their purpose. Any macro variables used inside the macro should be declared as %LOCAL to prevent unwanted interaction with global macro variables.
Example:
%macro getData(fromYear=, toYear=);
%local year;
%DO year = &fromYear %to &toYear;
* step 1;
* get initial data set from raw data file;
* double dot needed because &<NAME>. is a token specifying macro variable resolution;
proc import
datafile = "/folders/myfolders/fake_data/mz_&YEAR..dta" /* double dot */
replace
out= import_fake_&YEAR.
;
* step 2;
data fake_&YEAR;
set import_fake_&YEAR.;
year = &YEAR;
%* macro %if codegens a data step statement specific to year;
%if &YEAR GE 1997 AND &YEAR LE 2004 %then %do;
/* anything that is not consumed by macro processing is emitted as a codegen */
/* so here the macro is emitting a data step assignment statement */
shrf = x;
%end;
%else
%if &YEAR GE 2005 AND &YEAR LE 2017 %then %o
shrf = y;
%end;
%else %do;
shrf = 1; * uniform weighting ;
%end;
run;
%END;
%mend;
%* invoke;
%getData(fromYear=1997, toYear=2017);
%* this point your might want to combine (stack) all the data sets together
%* so that other Procs can use the 'all' data and utilize CLASS, BY and WHERE
%* statements that are so effective in SAS;
data fake_duodecade;
set fake_1997-fake2017; %* special data set name list construct;
run;
Specify macro parameters in your macro definition to make it more useful and reuseable. Don't write macros the reproduce the capabilities of existing Procedures. Don't write macros when you don't need to. Don't gencode that you can't write yourself. Don't mix (misunderstand) the scope of macro variables and know how they are not the same as DATA step variables.
An approach that doesn't involve nesting macros is the magical data null and call execute. I use this all the time. It's most helpful if the datasets are already in a SAS format.
libname HAVELIB "path-to-sas-datasets";
data _null_;
set sashelp.vtable(where=(libname="HAVELIB"));
call execute("%mymacro(HAVELIB." || strip(memname) || ");");
run;
Creating a loop that imports a bunch of .dta files as sas7bdat is just as easy, create a dataset based on the output of infile pipedir and do a similar loop using call execute.
More info here:
https://www.lexjansen.com/phuse/2014/cc/CC06.pdf

reading a data set multiple times in SAS

I am new here. I am trying to read in a data set multiple times. so for example, assume that I have 3 observations in a data set (called tempfile) for a variable called temp. the three observations are 4,6, and 5.. so I want to read in the set x number of times so the 4th observation would be 4, fifth would be 6 and sixth, would be 5. the 7th would be 4, etc etc. I have tried this literally a few dozen ways, by doing something like
data new;
do i=1 to 100;
set tempfile;
end;
output;
run;
I have tried this by moving the do statement, moving the output statement, omitting the output statement..... every which way, trying macros also. can somebody help? thanks John
followup....
Hello:
Thanks for response. That did work. I would like to now do several things involving some “if then” statements inside the loop (more than just reading in the data set).
I want to read in a data set n number of times, and each time, there will be two if then statements
So, assume I read in 3 numbers any number of times; 7, 15, and 12
As each number is read, it will ask if it is less than 10. And each time it will create a random number.
If less than 10, then
If rand(uniform) < .4 then 1 is added to counter1, else 1 is added to counter2
And if >= 10,
Then
If rand(uniform) < .2 then 1 is added to counter1, else 1 is added to counter2
Any help is much appreciated.
Thanks
John
The way that most data steps actually stop is when SAS reads past the end of the input. So you need a method that prevents SAS from doing that.
The easiest way to replicate the data is to just execute multiple output statements. So the first record is repeated three times, then the second record is repeated three times, etc.
data want;
set tempfile ;
do i=1 to 3;
output;
end;
run;
Another method is to just list the dataset multiple times on the SET statement. So to read it in 3 times just use
data want;
set tempfile tempfile tempfile;
run;
You could probably use macro logic or even just a macro variable to make the number of repetitions variable.
data _null_; call symputx('list',repeat('tempfile ',3-1)); run;
data want; set &list; run;
Other method is to use the POINT= and NOBS= options on the SET statement so that SAS never reads past the end and you can jump back to the beginning. But since it never reads past the end of the input data you will need to manually tell it when to stop.
data want ;
do i=1 to 3;
do p=1 to nobs ;
set tempfile point=p nobs=nobs;
output;
end;
end;
stop;
run;
Or more in the spirit of your original post you might want to use the MOD() function to figure out which observation to read next.
data want;
if _n_ > 100 then stop;
p=1+mod(_n_-1,nobs);
set tempfile point=p nobs=nobs;
run;
If you have SAS/STAT software SURVEYSELECT.
data have;
do temp=4,6,5;
output;
end;
run;
proc surveyselect reps=10 rate=1 out=temp2 noprint;
run;
The data step is designed for serial processing. In this case, you need to "remember" previous observations. You can do it using only the data step, but for that use case, there are other solutions in the SAS environment that are simpler. The one I suggest is a macro that appends the original file n times:
%macro replicate( data=, out=, n=)/des='&out is &data repeated &n times.';
data &out;
set
%do i=1 %to &n;
&data
%end;
; /* This ; ends the data step `set` statement */
run;
%mend;
You could test your example with this helper:
%macro test;
data have; /* create the example data set */
temp = 4; output;
temp = 6; output;
temp = 5; output;
run;
%replicate( data=have, out=want, n=4 );
proc print; quit;
%mend;
Here is a portion of the SAS doc that adds lots of detail with many examples.

How to resolve macro variable in a loop in SAS

I am trying to figure out how to call a macro variable in a loop within a data step in SAS, but I am lost; so I have 14 macro variables and I have to compare each of them to the entries of a vector. I tried:
data work.calendrier;
set projet.calendrier;
do i=1 to 3;
if date= "&vv&i"D then savinglight = 1;
end;
run;
But it is not working. The variable vv1 up to vv3 are date variables. For instance this code works:
data work.calendrier;
set projet.calendrier;
*do i=1 to 3;
if date= "&vv1"D then savinglight = 1;
*end;
run;
But with the loop it can not resolve the macro variable.
If you want to reference a macro variable with a number index like vv1,vv2,vv3 you need to resolve &i first.
SAS has a separate macro processor that resolves values before they reach the data step processor.
Essentially, you need to add extra ampersands at the beginning of your macro variable:
&&vv&i -> &vv1 -> "Value of vv1"
&&vv&i -> &vv2 -> "Value of vv2"
&&vv&i -> &vv3 -> "Value of vv3"
What happens here is that SAS reads in the information after the ampersand until it finds a break. SAS then resolves && as a single &, it then continues reading across until it resolves &i as a numeric value. You're then left with your required &vvi variable.
A couple of sources about this interesting topic:
http://www2.sas.com/proceedings/sugi29/063-29.pdf
http://www.lexjansen.com/nesug/nesug04/pm/pm07.pdf
Macro variable references are resolved before SAS compiles and runs your data step. You need to first figure out how to do what you want using SAS statements then, if necessary, you can use macro code to help you generate those statements.
If you want to test if a variable's value matches one of a list of values then consider using the IN operator.
data work.calendrier;
set projet.calendrier;
savinglight = date in ("&vv1"d,"&vv2"d,"&vv3"d);
run;
you need to use a macro. Here's the basic approach:
%let vv1 = 9;
%let vv2 = 2;
%let vv3 = 10;
data have;
drop i;
do i = 1 to 5;
date = i;
output;
end;
run;
%macro test;
data test;
set have;
%do i=1 %to 3;
if date= &&vv&i then savinglight = 1;
%end;
run;
%mend test;
%test;

Condition on Upper Limit in Do To Loop

I am very new to SAS and have a basic question.
I am writing a macro containing a Do-To loop from i = 1 to n. I want n to be conditioned on whether a year is less than 2005 or greater than it. If less than than n=10, otherwise n=11.
The year variable is already contained within the macro call so I feel like this should be easy but I'm struggling.
For example something like this code would be ideal:
%do i= 1 %to (if &year. < 2005 then 10; else 11)
This, however, does not seem to work. Is there another way I could easily implement this? Or use something similar to what's above?
Thanks! Your help is greatly appreciated!
For this problem you can take advantage of the fact that SAS converts logical expressions to 0/1 results.
%do i= 1 %to %eval(10 + (&year >= 2005)) ;
For a more general condition just make another variable for the upper bound and use %IF/%THEN logic to set it.
%if &year < 2005 %then %let upper=10;
%else %let upper=11;
%do i= 1 %to &upper;

Resources