Modifying the value of macro variable in SAS - sas

I created two macro variables in my sas code using the %let option.
data sasdata1.dataone;
set sasdata1.dataone ;
%let week=1;
%let sum=0;
do i=1 to 53;
%let sum= _W&week._NRX + &sum.;
week=&week+1;
end;
drop i;
week=&week;
sum=&sum.;
run;
the value of the week variable remains 1 even after the loop has executed. Which is the correct way to change the value of Macro variables?

If your week variables are all next to each other in the dataset, you may want to consider a macro-less approach:
data sasdata1.dataone;
set sasdata1.dataone;
sum = sum(of _W1_NRX--_W53_NRX); *double dash means that the columns are next to each other with _W1_NRX as first and _W53_NRX as last;
run;
If your week variables end with the week number, they do not even need to be next to each other:
data sasdata1.dataone;
set sasdata1.dataone;
sum = sum(of _W1-_W53); *single dash implies that the suffix is numerically increasing;
run;
Clean and easy.

Here's a quick example showing how to do this in the context of a macro. Run this and you can see in the log what's happening.
* 'week' is in scope within the macro only
and iterates because of %do loop (no need
to explicitly increment it ;
%macro test1;
%do week=1 %to 10;
%put &week;
%end;
%mend;
%test1
* sum has global scope ;
%let sum=0;
* 'week' acts as per test1 above, but we
explicitly manipulate 'sum' within each iteration ;
%macro test2;
%do week=1 %to 10;
%let sum=%eval(&sum+&week);
%put in macro: week is &week, sum is now ∑
%end;
%mend;
%put before macro executes, sum is ∑
%test2
%put after macro executes, sum is now ∑

Related

Create SAS macro to create a macro variable

I have created a SAS macro, macro A, that takes in a variable name and returns transformed versions of that name i.e. if you run %A(asdf) you get out asdf_log asdf_exp asdf_10. I want to write another macro, macro B, that takes the output from the first macro and appends it together into a new macro variable.
%macro B(varList, outputName);
%let &outputName =
%A(var1);
%A(var2);
;
%mend
Is almost what I want to do, except that it obviously doesn't compile.
I am also not sure if this is possible in SAS.
As a further complication, the input to macro B is a list of variable that I want to run macro A for and append into one long list of variable names.
Why? Because I have a macro that runs on a list of variables and I want to run it on a transformed variable list.
Example:
I have %let varList = x y; and I want as an output x_log x_exp x_10 y_log y_exp y_10. To do this I want two macros one, macro A, that returns the transformed variables names:
%macro A(var);
&var._log
&var._exp
&var._10
%mend
I can't get the second macro (B as written above) to work properly.
So if the inner macro is just returning characters, that is it doesn't actually generate any non macro statements, then why not make the outer one work the same way?
%macro inner(x);
&x._log &x._exp &x._10
%mend;
%macro outer(list);
%local i;
%do i=1 %to %sysfunc(countw(&list));
%inner(%scan(&list,&i))
%end;
%mend outer;
%let want=%outer(X y Z);
This is not too hard. You need to loop over the values in varList, appending results to outputName. You also need to declare outputName as GLOBAL so it will be accessible outside %B
%macro B(varList, outputName);
%global &outputName;
%let &outputName = ;
%local i n var;
%let n = %sysfunc(countw(&varList));
%do i=1 %to &n;
%let var = %scan(&varList,&i);
%let &outputName = &outputName %A(&var);
%end;
%mend;

Macro to loop through variables and store results SAS

I have the following variables: A_Bldg B_Bldg C_Bldg D_Bldg. I want to multiply them by INTSF and store the result in a new variable, Sale_i. For example, A_Bldg * INTSF = Sale_A, B_Bldg * INTSF = Sale_B, and so on.
My code is:
%macro loopit(mylist);
%let n=%sysfunc(countw(&mylist));
%do J = 1 %to &n;
%let i = %scan(&mylist,&J);
data test;
set data;
sale_&i. = &i._Bldg * INTSF;
run;
%end;
%mend;
%let list = A B C D;
%loopit(&list);
This only produces Sale_D, which is the last letter in the list. How do I get Sales A-C to appear? The first four lines of code are so I can loop through the text A-D. I thought about doing it with arrays, but didn't know how to choose the variables based on the A-D indicators. Thanks for your help!
You're currently looping through your list and recreating the test dataset every time, so it only appears to have sale_d because you're only viewing the last iteration.
You can clean up your loop by scanning through your list in one data step to solve your problem:
%let list = A B C D;
%macro loopit;
data test;
set data;
%do i = 1 %to %sysfunc(countw(&list.));
%let this_letter = %scan(&list., &i.);
sale_&this_letter. = &this_letter._Bldg * INTSF;
%end;
run;
%mend loopit;
%loopit;
Your %DO loop is in the wrong place. But really you do not need to use macro code to do something that the native SAS code can already do.
data want;
set have ;
array in A_Bldg B_Bldg C_Bldg D_Bldg ;
array out sale_1-sale4 ;
do i=1 to dim(in);
out(i)=intsf*in(i);
end;
run;

SAS: Running macros inside do loop does not seem to work

I need to run a series of macros multiple times. To do so, I've built a new macro that has inside of it a do-loop, which is supposed to run "i" number of times, and with each iteration of the do-loop, the series of macros referenced above are supposed to run.
Here is the essence of code (note that the first proc sql essentially takes from a dataset called "DatesRange" and placed a range of dates into the variable "varlist_Dates"; the range of dates within this variable is used for Macro1, Macro2, Macro3).
%macro MultipleTimes;
proc sql noprint;
select distinct Date
into :varlist_Dates separated by ' '
from DatesRange
order by Date;
quit;
%do i = 1 %to 5;
%let date = %scan(&varlist_Dates.,&i.);
%Macro1;
%Macro2;
%Macro3;
%end;
%mend;
I'm finding that it stops at i=1 and never proceeds. I'm completely unclear on why. I've experimented by playing around with removing some macros and keeping others but nothing seems to work. I feel like there may be something more fundamental about my methodology that is off, because there's something I don't know about SAS and about the way a macro inside of a do-loop inside of a macro works.
Any help would be much appreciated. Thanks!
First: check if i is used in any of those macros. I bet it is. It's probably being changed to something higher than 5 (thus exiting after one loop).
In SAS, when you reference a macro variable that already exists, it is defined as having the scope of the most local symbol table that contains it, unless you use %local to specify it as local to the current macro. So if you throw %let i=1 to 5; around in multiple nested macros, it will be using the same &i.
As an example, see the following code:
%let i=A;
%macro outer;
%put &=i;
%do i=1 %to 5;
%put OUTER FIRST: &=i;
%inner;
%put OUTER LAST: &=i;
%end;
%mend outer;
%macro inner;
%do i=1 %to 5;
%put INNER: &=i.;
%end;
%mend;
%put GLOBAL FIRST: &=i;
%outer;
%put GLOBAL LAST:&=i;
Notice how &i is always the same value. That's not what you mean, now is it?
Now, on the other hand, &i does get a different value in each bit if you do it right:
%let i=A;
%macro outer;
%local i;
%put &=i;
%do i=1 %to 5;
%put OUTER FIRST: &=i;
%inner;
%put OUTER LAST: &=i;
%end;
%mend outer;
%macro inner;
%local i;
%do i=1 %to 5;
%put INNER: &=i.;
%end;
%mend;
%put GLOBAL FIRST: &=i;
%outer;
%put GLOBAL LAST:&=i;
And if you %put _all_ in INNER you will see this in action.
Second: Don't write it this way. You're going to so much effort, just write it a better way to begin with.
%Macro RunMyMacros(date=);
%Macro1; *I hope you use &date as a parameter in these and not as a global;
%Macro2;
%Macro3;
%mend RunMyMacros;
proc sql noprint;
select distinct cats('%RunMyMacros(date=',Date,')')
into :calllist_Dates separated by ' '
from DatesRange
order by Date;
quit;
&calllist;
That's far easier to use, troubleshoot, and run, and doesn't require hardcoding the number of valid dates or anything else in it.

Find three most recent data year for each row

I have a data set with one row for each country and 100 columns (10 variables with 10 data years each).
For each variable I am trying to make a new data set with the three most recent data years for that variable for each country (which might not be successive).
This is what I have so far, but I know its wrong because of the nest loop, and its has same value for recent1 recent2 recent3 however I haven't figured out how to create recent1 recent2 recent3 without two loops.
%macro test();
data Maternal_care_recent;
set wb;
keep country MATERNAL_CARE_2004 -- MATERNAL_CARE_2013 recent_1 recent_2 recent_3;
%let rc = 1;
%do i = 2013 %to 2004 %by -1;
%do rc = 1 %to 3 %by 1;
%if MATERNAL_CARE_&i. ne . %then %do;
recent_&rc. = MATERNAL_CARE_&i.;
%end;
%end;
%end; run; %mend; %test();
You don't need to use a macro to do this - just some arrays:
data Maternal_care_recent;
set wb;
keep country MATERNAL_CARE_2004-MATERNAL_CARE_2013 recent_1 recent_2 recent_3;
array mc {*} MATERNAL_CARE_2004-MATERNAL_CARE_2013;
array recent {*} recent1-recent3;
do i = 2013 to 2004 by -1;
do rc = 1 to 3 by 1;
if mc[i] ne . then do;
recent[rc] = mc[i];
end;
end;
run;
Maybe I don't get your request, but according to your description:
"For each variable I am trying to make a new data set with the three most recent data years for that variable for each country (which might not be successive)" I created this sample dataset with dt1 and dt2 and 2 locations.
The output will be 2 datasets (and generally the number of the variables starting with DT) named DS1 and DS2 with 3 observations for each country, the first one for the first variable, the second one for the second variable.
This is the sample dataset:
data sample_ds;
length city $10 dt1 dt2 8.;
infile datalines dlm=',';
input city $ dt1 dt2;
datalines;
MS,5,0
MS,3,9
MS,3,9
MS,2,0
MS,1,8
MS,1,7
CA,6,1
CA,6,.
CA,6,.
CA,2,8
CA,1,5
CA,0,4
;
This is the sample macro:
%macro help(ds=);
data vars(keep=dt:); set &ds; if _n_ not >0; run;
%let op = %sysfunc(open(vars));
%let nvrs = %sysfunc(attrn(&op,nvars));
%let cl = %sysfunc(close(&op));
%do idx=1 %to &nvrs.;
proc sort data=&ds(keep=city dt&idx.) out=ds&idx.(where=(dt&idx. ne .)) nodupkey; by city DESCENDING dt&idx.; run;
data ds&idx.; set ds&idx.;
retain cnt;
by city DESCENDING dt&idx.;
if first.city then cnt=0; else cnt=cnt+1;
run;
data ds&idx.(drop=cnt); set ds&idx.(where=(cnt<3)); rename dt&idx.=act&idx.; run;
%end;
%mend;
You will run this macro with:
%help(ds=sample_ds);
In the first statement of the macro I select the variables on which I want to iterate:
data vars(keep=dt:); set &ds; if _n_ not >0; run;
Work on this if you want to make this work for your code, or simply rename your variables as DT1 DT2...
Let me know if it is correct for you.
When writing macro code, always keep in mind what has to be done when. SAS processes your code stepwise.
Before your sas code is even compiled, your macro variables are resolved and your macro code is executed
Then the resulting SAS Base code is compiled
Finally the code is executed.
When you write %if MATERNAL_CARE_&i. ne . %then %do, this is macro code interpreded before compilation.
At that time MATERNAL_CARE_&i. is not a variable but a text string containing a macro variable.
The first time you run trhough your %do i = 2013 %to 2004 by -1, it is filled in as MATERNAL_CARE_2013, the second as MATERNAL_CARE_2012., etc.
Then the macro %if statement is interpreted, and as the text string MATERNAL_CARE_1 is not equal to a dot, it is evaluated to FALSE
and recent_&rc. = MATERNAL_CARE_&i. is not included in the code to pass to your compiler.
You can see that if you run your code with option mprint;
The resolution;
options mprint;
%macro test();
data Maternal_care_recent;
set wb;
keep country MATERNAL_CARE_: recent_:;
** The : acts as a wild card here **;
%do i = 2013 %to 2004 %by -1;
if MATERNAL_CARE_&i. ne . then do;
%do rc = 1 %to 3 %by 1;
recent_&rc. = MATERNAL_CARE_&i.;
%end;
end;
%end;
run;
%mend;
%test();
Now, before compilation of if MATERNAL_CARE_&i. ne . then do, only the &i. is evalueated and if MATERNAL_CARE_2013 ne . then do is passed to the compiler.
The compiler will see this as a test if the SAS variable MATERNAL_CARE_1 has value missing, and that is just what you wanted;
Remark:
It is not essential that I moved the if statement above the ``. It is just more efficient because the condition is then evaluated less often.
It is however essential that you close your %ifs and %dos with an %end and your ifs and dos with an end;
Remark:
you do not need %let rc = 1, because %do rc = 1 to 3 already initialises &rc.;
For completeness SAS is compiled stepwise:
The next PROC or data step and its macro code are only considered when the preveous one is executed.
That is why you can write macro variables from a data step or sql select into that will influence the code you compile in your next step,
somehting you can not do for instance with C++ pre compilation;
Thanks everyone. Found a hybrid solution from a few solutions posted.
data sample_ds;
infile datalines dlm=',';
input country $ maternal_2004 maternal_2005
maternal_2006 maternal_2007 maternal_2008 maternal_2009 maternal_2010 maternal_2011 maternal_2012 maternal_2013;
datalines;
MS,5,0,5,0,5,.,5,.,5,.
MW,3,9,5,0,5,0,5,.,5,0
WE,3,9,5,0,5,.,.,.,.,0
HU,2,0,5,.,5,.,5,0,5,0
MI,1,8,5,0,5,0,5,.,5,0
HJ,1,7,5,0,5,0,.,0,.,0
CJ,6,1,5,0,5,0,5,0,5,0
CN,6,1,.,5,0,5,0,5,0,5
CE,6,5,0,5,0,.,0,5,.,8
CT,2,5,0,5,0,5,0,5,0,9
CW,1,5,0,5,0,5,.,.,0,7
CH,0,5,0,5,0,.,0,.,0,5
;
%macro test(var);
data &var._recent;
set sample_ds;
keep country &var._1 &var._2 &var._3;
array mc {*} &var._2004-&var._2013;
array recent {*} &var._1-&var._25;
count=1;
do i = 10 to 1 by -1;
if mc[i] ne . then do;
recent[count] = mc[i];
count=count+1;
end;
end;
run;
%mend;

SAS: Creating lag variables with a do macro

I'm trying to create 4 new lag variables, each one adding an additional lag. The code below produces only the final lag variable, i.e. after running this code there is a new variable called lag_4, but lag_1, lag_2, and lag_3 are not created. Thanks
%macro makelags;
%do i=1 %to 4;
data work.test1;
set work.dataset;
lag_&i = lag&i(id_number);
run;
%end;
%mend makelags;
%makelags;
You need to loop inside the data step, not outside of it.
If you were to loop:
data work.test1;
set work.dataset;
%do i = 1 %to 4;
lag_&i. = lag&i.(id_number);
%end;
run;
(The whole datastep can be inside a macro, or just the %do loop).
The way I'd do it, if I needed a macro (Because, say, the number of lags varies):
%macro lagme(num_lags=);
%do _i = 1 %to &num_lags.;
lag_&_i. = lag&_i.(id_number);
%end;
%mend lagme;
data mydata;
set olddata;
%lagme(num_lags=4);
run;
Your code is overwriting dataset test1 4 times keeping only the version created by the last %do iteration.
Try moving the %do cycle inside the data step:
data work.test1;
set work.dataset;
%do i=1 %to 4;
lag_&i = lag&i(id_number);
%end;
run;