SAS do-loop for creating variables - arrays

Let's say I have a bunch of variables (call it eq3_xxxxx where the xxx are the variations) that all have 5 possible levels (1,2,3,4,5) and I want to create a dummy variable for each level of each variable.
I thought I could do something like:
%macro eq_levels(eq3:);
data mydata;
%do i = 1 %to 5;
x=cats(%eq3:,%i);
%end;
%mend;
But this doesn't seem to work. I'd rather not use SQL or anything like that, as I think the array and do-loop solutions should suffice, but I am open to it if the explanation can be made straightforward enough.

It may just be some minor syntax problems you have. Is this what you want?
%macro eq_levels(eq3);
data;
do i = 1 to 5;
x = cats(&eq3,i);
output;
end;
run;
%mend;
%eq_levels("eq3_");
Output:
i x
1 1 eq3_1
2 2 eq3_2
3 3 eq3_3
4 4 eq3_4
5 5 eq3_5

I am maybe misreading this, but I understand that OP wishes multiple variables:
%macro macro_evn;
data mydata;
%do i=1 %to 5;
var&i=&i.;
output;
%end;
%mend;
%macro_evn;
Resulting to
var1 var2 var3 var4 var5
1 . . . .
1 2 . . .
1 2 3
....
Which would be easy to fill as needed. Then again, maybe I misread the question.

Related

How to loop over list items in a macro in sas?

I wanted to perform data calculations with items from a list:
%let list = ("Apple" "Pear" "Grapes")
%macro px(list);
%do k=1 %to dim(&list.);
data data_1;
set output_&item_one.;
(...)
run;
proc sort data = data1;
by &variables.;
run;
data data_2;
set data1;
(...)
run;
proc export data = data_n
outfile= "&input_path.\file_name.xlsx"
dbms=xlsx replace;
sheet = "SUMMARY";
run;
%mend;
%px(list);
So I first take Apple without " do all data calculations and then take Pear etc.
Many thanks!
One way is to use COUNTW and SCAN
A macro list is typically just the items, no parentheses or quoting. However, how the items are utilized when resolved can change you design on what you start with.
However, if the list comes with parentheses and quoted values, you can compress those out.
Example:
%let items = Apple Pear Grapes;
%macro list_each(items=);
%* Many percents = The joy of escapism;
%let items = %sysfunc(compress(&items,%str(%(%"%))));
%local index item;
%do index = 1 %to %sysfunc(countw(&items, %str( )));
%let item = %scan(&items,&index,%str( ));
%put NOTE: &=index &=item;
%end;
%mend;
%list_each (items=&items)
%list_each (items=Alpha Beta Gamma)
%list_each (items=("A" "B" "C"))
Log
### %list_each (items=&items)
NOTE: INDEX=1 ITEM=Apple
NOTE: INDEX=2 ITEM=Pear
NOTE: INDEX=3 ITEM=Grapes
### %list_each (items=Alpha Beta Gamma)
NOTE: INDEX=1 ITEM=Alpha
NOTE: INDEX=2 ITEM=Beta
NOTE: INDEX=3 ITEM=Gamma
### %list_each (items=("A" "B" "C"))
NOTE: INDEX=1 ITEM=A
NOTE: INDEX=2 ITEM=B
NOTE: INDEX=3 ITEM=C
You cannot use a macro to loop over a list directly, but you can do it indirectly. If your list will not contain spaces (i.e. "Delicious Apple"), space-separating is a good way to go. If it will, pipe-separating, |, is the easiest route; however, you can use the Q modifier of %scan() to handle this if you'd like.
We'll do this by using %scan() to pull a single item from the list and loop until the end. We can use %sysfunc(countw()) to count the number of words in the list.
%let list = Apple Pear Grapes;
%macro px(list);
%do i = 1 %to %sysfunc(countw(&list.));
%let k = %scan(&list., &i.);
%put &i. &k.;
%end;
%mend;
%px(&list);
Output:
1 Apple
2 Pear
3 Grapes
If you pipe-separate it, simply add it as an argument to %sysfunc(countw()) and %scan():
%do i = 1 %to %sysfunc(countw(&list., |));
%let k = %scan(&list., &i., |);
To deal with that style of list you could just tell %SCAN() that space and parentheses are the delimiters. To remove the quotes use the DEQUOTE() function.
%macro test(list);
%local i next ;
%do i=1 %to %sysfunc(countw(&list,( ),q));
%let next=%sysfunc(dequote(%scan(&list,&i,( ))));
%put &=i &=next ;
%end;
%mend;
%let list = ("Apple" "Pear" "Grapes");
%test(&list);
Result:
I=1 NEXT=Apple
I=2 NEXT=Pear
I=3 NEXT=Grapes

Loop and join table

Let say I want to loop for i = 2002 to 2006 and in each loop, I create a key word like:
w2006
w2005
w2004
w2003
w2002
And each keyword will be the input for a macro, let say mymacro(keyword) which create a table named "result&key" (for example: resultw2006), so that I want to execute 5 time : %mymacro(w2006), %mymacro(w2005)... %mymacro(w2002) to have 5 tables and join them after that.
Do you have any idea of how to do this? In fact I tried but it doesn't work, here is my code:
%macro tojoin;
%do i=2002 %to 2006;
key=w&i;
%mymacro(&key);
%data result;
set result result&key;
%run;
output;
%end;
%mend;
You need to provide more information on what SAS code you are tying to use the macro processor to generate to get a real answer.
But assuming that %MYMACRO will generate a dataset named result&key then it looks like you want to run something like this to call it 5 times and append the results into a single dataset.
%macro tojoin;
%local key;
%do i=2002 %to 2006;
%let key=w&i;
%mymacro(&key);
proc append base=result data=result&key;
run;
%end;
%mend;
You don't need % in front of data or run, you need a %let statement, and you don't need the output command. So:
%macro tojoin;
%do i=2002 %to 2006;
%let key=w&i;
%mymacro(&key);
data result;
set result result&key;
run;
%end;
%mend;
Tom's method is cleaner, but your code was basically correct - just a bit of cleanup needed.

keep variables with a doop loop sas

Is there any form to keep variables with a doop loop in data step?
will be something as:
data test;
input id aper_f_201501 aper_f_201502 aper_f_201503 aper_f_201504
aper_f_201505 aper_f_201506;
datalines;
1 0 1 2 3 5 7
2 -1 5 4 8 7 9
;
run;
%macro test;
%let date = '01Jul2015'd;
data test2;
set test(keep=do i = 1 to 3;
aper_f_%sysfunc(intnx(month,&date,-i,begin),yymmn6.);
end;)
run;
%mend;
%test;
I need to iterate several dates.
Thank you very much.
You need to use macro %do loop instead of the data step do loop, which is not going to be valid in the middle of a dataset option. Also do not generate those extra semi-colons into the middle of your dataset options. And do include a semi-colon to end your SET statement.
%macro test;
%local i date;
%let date = '01Jul2015'd;
data test2;
set test(keep=
%do i = 1 %to 3;
aper_f_%sysfunc(intnx(month,&date,-i,begin),yymmn6.)
%end;
);
run;
%mend;
%test;
You can use the colon shortcut to reference variables with the same prefix, anything in front of the colon will be kept.
keep ID aper_f_2015: ;
There's also a hyphen when you have sequential lists
keep ID aper_f_201501-aper_f_201512;
You can use a macro but not sure it adds a lot of value here.

SAS Looping through macro variable and processing the data

I have a bunch of character variables which I need to sort out from a large dataset. The unwanted variables all have entries that are the same or are all missing (meaning I want to drop these from the dataset before processing the data further). The data sets are very large so this cannot be done manually, and I will be doing it a lot of times so I am trying to create a macro which will do just this. I have created a list macro variable with all character variables using the following code (The data for my part is different but I use the same sort of code):
data test;
input Obs ID Age;
datalines;
1 2 3
2 2 1
3 2 2
4 3 1
5 3 2
6 3 3
7 4 1
8 4 2
run;
proc contents
data = test
noprint
out = test_info(keep=name);
run;
proc sql noprint;
select name into : testvarlist separated by ' ' from test_info;
quit;
My idea is then to just use a data step to drop this list of variables from the original dataset. Now, the problem is that I need to loop over each variable, and determine if the observations for that variable are all the same or not. My idea is to create a macro that loops over all variables, and for each variable counts the occurrences of the entries. Since the length of this table is equal to the number of unique entries I know that the variable should be dropped if the table is of length 1. My attempt so far is the following code:
%macro ListScanner (org_list);
%local i next_name name_list;
%let name_list = &org_list;
%let i=1;
%do %while (%scan(&name_list, &i) ne );
%let next_name = %scan(&name_list, &i);
%put &next_name;
proc sql;
create table char_occurrences as
select &next_name, count(*) as numberofoccurrences
from &name_list group by &next_name;
select count(*) as countrec from char_occurrences;
quit;
%if countrec = 1 %then %do;
proc sql;
delete &next_name from &org_list;
quit;
%end;
%let i = %eval(&i + 1);
%end;
%mend;
%ListScanner(org_list = &testvarlist);
Though I get syntax errors, and with my real data I get other kinds of problems with not being able to read the data correctly but I am taking one step at a time. I am thinking that I might overcomplicate things so if anyone has an easier solution or can see what might be wrong to I would be very grateful.
There are many ways to do this posted around.
But let's just look at the issues you are having.
First for looping through your space delimited list of names it is easier to let the %do loop increment the index variable for you. Use the countw() function to find the upper bound.
%do i=1 %to %sysfunc(countw(&name_list,%str( )));
%let next_name = %scan(&name_list,&i,%str( ));
...
%end;
Second where is your input dataset in your SQL code? Add another parameter to your macro definition. Where to you want to write the dataset without the empty columns? So perhaps another parameter.
%macro ListScanner (dsname , out, name_list);
%local i next_name sep drop_list ;
Third you can use a single query to count all of variables at once. Just use count( distinct xxxx ) instead of group by.
proc sql noprint;
create table counts as
select
%let sep=;
%do i=1 %to %sysfunc(countw(&name_list,%str( )));
%let next_name = %scan(&name_list,&i,%str( ));
&sep. count(distinct &next_name) as &next_name
%let sep=,;
%end;
from &dsname
;
quit;
So this will get a dataset with one observation. You can use PROC TRANSPOSE to turn it into one observation per variable instead.
proc transpose data=counts out=counts_tall ;
var _all_;
run;
Now you can just query that table to find the names of the columns with 0 non-missing values.
proc sql noprint ;
select _name_ into :drop_list separated by ' '
from counts_tall
where col1=0
;
quit;
Now you can use the new DROP_LIST macro variable.
data &out ;
set &dsname ;
drop &drop_list;
run;
So now all that is left is to clean up after your self.
proc delete data=counts counts_tall ;
run;
%mend;
As far as your specific initial question, this is fairly straightforward. Assuming &testvarlist is your macro variable containing the variables you are interested in, and creating some test data in have:
%let testvarlist=x y z;
data have;
call streaminit(7);
do id = 1 to 1e6;
x = floor(rand('Uniform')*10);
y = floor(rand('Uniform')*10);
z = floor(rand('Uniform')*10);
if x=0 and y=4 and z=7 then call missing(of x y z);
output;
end;
run;
data want fordel;
set have;
if min(of &testvarlist.) = max(of &testvarlist.)
and (cmiss(of &testvarlist.)=0 or missing(min(of &testvarlist.)))
then output fordel;
else output want;
run;
This isn't particularly inefficient, but there are certainly better ways to do this, as referenced in comments.

SAS: Dynamically copy a certain number of rows

I have a data set that needs to be blown out a certain number of rows according to a dynamic value. Take the dataset below for example:
DATA HAVE;
LENGTH ID $3 COUNT 3;
INPUT ID $ COUNT;
DATALINES;
A 4
B 3
C 1
D 2
;
RUN;
ID=A needs to be blown out 4 rows, ID=B needs to be blown out 3 rows, etc. The resulting dataset would look as such (minus a bunch of other variables I have):
A 1
A 2
A 3
A 4
B 1
B 2
B 3
C 1
D 1
D 2
The following code works to an extent, but I'm having trouble dynamically setting the &COUNT. macro. I tried to insert a CALL SYMPUTX("COUNT",COUNT) statement so that as it loops over each row, the count is placed into the macro and the row is blown at that number of rows.
** THIS CODE ONLY WORKS IF YOU SET COUNT= TO SOME VALUE **;
%MACRO LOOPOVER();
DATA WANT; SET HAVE;
DO UNTIL(LAST.ID);
BY ID;
%DO I=1 %TO &COUNT.;
COUNT = &I.; OUTPUT;
%END;
END;
RUN;
%MEND;
%LOOPOVER;
** THIS CODE DOESN'T WORK BUT I'M NOT SURE WHY?? **;
%MACRO LOOPOVER();
DATA WANT; SET HAVE;
DO UNTIL(LAST.ID);
BY ID;
CALL SYMPUTX("COUNT",COUNT); /* NEW LINE HERE */
%DO I=1 %TO &COUNT.;
COUNT = &I.; OUTPUT;
%END;
END;
RUN;
%MEND;
%LOOPOVER;
It is unnecessary to use macro.
data want(rename=(_count=count));
set have;
do i=1 to count;
_count=i;
output;
end;
drop count;
run;

Resources