how to put &&var&i into if condition - if-statement

/*create macro variables*/
PROC SQL NOPRINT;
SELECT RESTRICTIONS
INTO :RESTRI1 - :RESTRI35
FROM SASDATA.RESTRICTIONLIST;
QUIT;
%PUT &RESTRI2;
/*the resolved value is: */
gender = 'M' and state = 'CA'
I want to create a data set sasdata.newlist&i when the ith restriction
is &&restri&i (eg: gender = 'M' and state = 'CA').
I only want the observations which meet the restriction &&restri&I* in this new created dataset
While the sasdata.newlist2 contains all data in sasdata.oldlist, the if condition doesn't work. Anybody can help me to solve this problem?
%Macro testing(I);
data sasdata.newlist&i;
set sasdata.oldlist;
%if &&restri&i %then;
run;
%mend testing;
%testing(2)

You are not resolving the macro variables in the proper context. When applying the restriction code, resolve it so it can be compiled (data step-wise) as part of the DATA step.
%Macro testing(I);
data sasdata.newlist&i;
set sasdata.oldlist;
/* %if &&restri&i %then; NO-no-no, incorrect context */
* apply ith restriction as a sub-setting IF statement;
if &&restri&i;
run;
%mend testing;
%testing(2)

While it's hard to tell when to use macro statement, when not.
for example: Do I need to put % in the if -then-else statement and do while statement in code below? By the way, can I use "Do i = 1 to n while (condition)" statement here like this?
%MACRO FUNDSOURCE(I);
DATA SASDATA.STUDENT&I;
SET SASDATA.STUDENTLIST
DO M = 1 TO 310 WHILE(&&BUDG&I > 0); /*loop through all observations_ALL
STUDENTS*/
IF &&BUDG&I LE 3000- FA_TOT1 THEN do;
DISBURSE = &&BUDG&I;
FA_TOT1+DISBURE;
&&BUDG&I - DISBURSE;
end;
ELSE IF &&BUDG&I GT (3000- FA_TOT1) THEN DO;
DISBURSE = 3000-FA_TOT1;
FA_TOT1+DISBURSE;
&&BUDG&I - DISBURSE;
END;
END;
IF _n_ > M THEN DELETE; /*if budget are all gone, delete other observations,
keep observations only for the student who get funds*/
RUN;
%MEND FUNDSOURCE;

Related

Comparing date columns using sas macros

I I’m trying to compare two columns, both of which are dates. I know that some are the same so I expect to have some flagged as 1 within the macro. Which I can see when I don’t use the macro. Can anyone help.
Data test;
Set source;
*this works;
If date_1=date_2 then do;
X=1;
End;
else do;
X=0;
End;
*this doesnt work;
%macro flags(start=,flag=);
%If &start=date_2 %then %do;
&flag=1;
%End;
%else %do;
&flag=0;
%End;
%mend flags(start=flag_1,flag=x);
Run;
I tried to compare columns i was expecting a 1 if dates were the same and 0 if not.
Everything is 0 within a macro but ok if not using one.
If you want the macro to generate the same data step then make sure that it generates the IF statements also. You will need to call the macro inside of a data step so that the generated SAS code is run in a valid location.
%macro flags(start=,flag=);
if &start=date_2 then do;
&flag=1;
end;
else do;
&flag=0;
end;
%mend ;
data test;
set source;
%flags(start=flag_1,flag=x);
run;
PS SAS will evaluate a boolean expression as 1 for true and 0 for false so your code could be reduced to
%macro flags(start=,flag=);
&flag = (&start=date_2);
%mend ;
Which really does not need to be a macro.
data test;
set source;
x = (flag_1 = date_2);
run;

Concatenated where conditional in SAS

I am trying to write a macro to perform a proc summary operation on a dataset.
This macro takes in a few variables:
lower bound date (start = '01Jan2013'd)
upper bound date (end = '01Jan2016'd)
an additional 'and' condition (sometimes '', sometimes 'and state = XXX')
These variables are fed into the where statement in the macro below. Note that there will sometimes be an 'and' condition, and sometimes not at all.
%macro program1(start,end,cond); %macro _(); %mend _;
proc summary data = data1 nway missing;
where &start. < month < &end. || &cond.;
class month_inc;
var var1 var2;
output out =summ_data1 sum=;
run;
%mend;
I am having trouble getting the macro variables to work correctly in the where statement. The || doesn't work.
I tried to use CATX like so:
where catx(' ',&start. le month_inc le &end.,&cond.);
And that works to an extent. The &start. and &end. variables resolves as numerical values instead of the SAS dates, thus not summarizing my results correctly.
If you literally pass in the keyword AND as part of the value of the parameter COND (cond=and state='XXX') then just remove the concatenation operator from your WHERE statement.
where (&start<month<&end) &cond ;
Otherwise use macro logic to conditionally generate the part that references the optional COND value.
I like to use the WHERE ALSO (also known as where and) statement to augment existing WHERE statements.
%macro program1(start,end,cond);
proc summary data = data1 nway missing;
where &start. < month < &end. ;
%if %length(&cond) %then %do;
where also &cond.;
%end;
class month_inc;
var var1 var2;
output out =summ_data1 sum=;
run;
%mend;
So then your example call could look like this:
%program1(start = '01Jan2013'd
,end = '01Jan2016'd
,cond = state = 'XXX')

Create dummy using category variables more efficiently in SAS

I have data on with state variation in U.S. Now i want to creat many dummies to control state fix effect. In stata it's an easy work while in sas it seems I have to create all dummies manually.However logit regression with fix effects runs quite slow in stata. I wonder whether there's a more efficient way to create dummy from char variables(not numerical, which I know a few methods to apply) in sas since I have too many char variables need to be created as dummies.
Cheers,
Eva
proc logistic supports the class statement. Place your variables in the class statement and you can specify the type of parameterization you'd like as well. The most common method is referential coding.
proc logistic data=sashelp.heart;
class sex bp_status/param=ref;
model status = sex ageAtStart height weight bp_status;
run;
https://support.sas.com/documentation/cdl/en/statug/63347/HTML/default/viewer.htm#statug_logistic_sect006.htm
Not all procs support the class statement, in those cases you can use proc glmmod or a variety of other method to create your dummy variables.
http://blogs.sas.com/content/iml/2016/02/22/create-dummy-variables-in-sas.html
If you absolutely need to manually create dummy variables you can use a macro like this one. You would need to call it for each variable.
%macro create_dummy(dataset=, var=);
%* Save Distinct Values and Dummy Variable Names;
proc sql noprint;
select distinct
&var,
tranwrd(tranwrd(trim(&var), " ", "_"), ".", "")
into
:value1-,
:name1-
from
&dataset
;
select
count(distinct(&var))
into
:total
from
&dataset
;
quit;
%* Create Dummy Variables;
data &dataset;
set &dataset;
%do i=1 %to &total;
if &var = "&&value&i" then &&name&i = 1; else &&name&i = 0;
%end;
run;
%mend create_dummy;
You can add a loop to the Macro if you want to call the Macro only once. Add a do loop to the top like:
%macro create_dummy(dataset=, var=);
%do l %to %sysfunc(countw(&var));
%let var1 = %scan(&var, &l);
%* Save Distinct Values and Dummy Variable Names;
proc sql noprint;
select distinct
&var1,
tranwrd(tranwrd(trim(&var1), " ", "_"), ".", "")
into
:value1-,
:name1-
from
&dataset
;
select
count(distinct(&var1))
into
:total
from
&dataset
;
quit;
%* Create Dummy Variables;
data &dataset;
set &dataset;
%do i=1 %to &total;
if &var1 = "&&value&i" then &&name&i = 1; else &&name&i = 0;
%end;
run;
%end;
%mend create_dummy;

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;

How to scan a numeric variable

I have a table like this:
Lista_ID 1 4 7 10 ...
in total there are 100 numbers.
I want to call each one of these numbers to a macro i created. I was trying to use 'scan' but read that it's just for character variables.
the error when i runned the following code was
there's the code:
proc sql;
select ID INTO: LISTA_ID SEPARATED BY '*' from
WORK.AMOSTRA;
run;
PROC SQL;
SELECT COUNT(*) INTO: NR SEPARATED BY '*' FROM
WORK.AMOSTRA;
RUN;
%MACRO CICLO_teste();
%LET LIM_MSISDN = %EVAL(NR);
%LET I = %EVAL(1);
%DO %WHILE (&I<= &LIM_MSISDN);
%LET REF = %SCAN(LISTA_ID,&I,,'*');
DATA WORK.UP&REF;
SET WORK.BASE&REF;
FORMAT PERC_ACUM 9.3;
IF FIRST.ID_CLIENTE THEN PERC_ACUM=0;
PERC_ACUM+PERC;
RUN;
%LET I = %EVAL(&I+1);
%END;
%MEND;
%CICLO_TESTE;
the error was that:
VARIABLE PERC IS UNITIALIZED and
VARIABLE FIRST.ID_CLIENTE IS UNITIALIZED.
What I want is to run this macro for each one of the Id's in the List I showed before, and that are referenced in work.base&ref and work.up&ref.
How can I do it? What I'm doing wrong?
thanks!
Here's the CALL EXECUTE version.
%MACRO CICLO_teste(REF);
DATA WORK.UP&REF;
SET WORK.BASE&REF;
BY ID_CLIENTE;
FORMAT PERC_ACUM 9.3;
IF FIRST.ID_CLIENTE THEN PERC_ACUM=0;
PERC_ACUM+PERC;
RUN;
%CICLO_TESTE;
DATA _NULL_;
SET amostra;
*CREATE YOUR MACRO CALL;
STR = CATT('%CLIO_TESTE(', ID, ')');
CALL EXECUTE(STR);
RUN;
First you should note that SAS macro variable resolve is intrinsically a "text-based" copy-paste action. That is, all the user-defined macro variables are texts. Therefore, %eval is unnecessary in this case.
Other miscellaneous corrections include:
Check the %scan() function for correct usage. The first argument should be a text string WITHOUT QUOTES.
run is redundant in proc sql since each sql statement is run as soon as they are sent. Use quit; to exit proc sql.
A semicolon is not required for macro call (causes unexpected problems sometimes).
use %do %to for loops
The code below should work.
data work.amostra;
input id;
cards;
1
4
7
10
;
run;
proc sql noprint;
select id into :lista_id separated by ' ' from work.amostra;
select count(*) into :nr separated by ' ' from work.amostra;
quit;
* check;
%put lista_id=&lista_id nr=&nr;
%macro ciclo_teste();
%local ref;
%do i = 1 %to &nr;
%let ref = %scan(&lista_id, &i);
%*check;
%put ref = &ref;
/* your task below */
/* data work.up&ref;*/
/* set work.base&ref;*/
/* format perc_acum 9.3;*/
/* if first.id_cliente then perc_acum=0;*/
/* perc_acum + perc;*/
/* run; */
%end;
%mend;
%ciclo_teste()
tested on SAS 9.4 win7 x64
Edited:
In fact I would recommend doing this to avoid scanning a long string which is inefficient.
%macro tester();
/* get the number of obs (a more efficient way) */
%local NN;
proc sql noprint;
select nobs into :NN
from dictionary.tables
where upcase(libname) = 'WORK'
and upcase(memname) = 'AMOSTRA';
quit;
/* assign &ref by random access */
%do i = 1 %to &NN;
data _null_;
a = &i;
set work.amostra point=a;
call symputx('ref',id,'L');
stop;
run;
%*check;
%put ref = &ref;
/* your task below */
%end;
%mend;
%tester()
Please let me know if you have further questions.
Wow that seems like a lot of work. Why not just do the following:
data work.amostra;
input id;
cards;
1
4
7
10
;
run;
%macro test001;
proc sql noprint;
select count(*) into: cnt
from amostra;
quit;
%let cnt = &cnt;
proc sql noprint;
select id into: x1 - :x&cnt
from amostra;
quit;
%do i = 1 %to &cnt;
%let x&i = &&x&i;
%put &&x&i;
%end;
%mend test001;
%test001;
now in variables &x1 - &&x&cnt you have your values and you can process them however you like.
In general if your list is small enough (macro variables are limited to 64K characters) then you are better off passing the list in a single delimited macro variable instead of multiple macro variables.Remember that PROC SQL will automatically set the count into the macro variable SQLOBS so there is no need to run the query twice. Or you can use %sysfunc(countw()) to count the number of entries in your delimited list.
proc sql noprint ;
select id into :idlist separated by '|' from .... ;
%let nr=&sqlobs;
quit;
...
%do i=1 %to &nr ;
%let id=%scan(&idlist,&i,|);
data up&id ;
...
%end;
If you do generate multiple macro variables there is no need to set the upper bound in advance as SAS will only create the number of macro variables it needs based on the number of observations returned by the query.
select id into :idval1 - from ... ;
%let nr=&sqlobs;
If you are using an older version of SAS the you need set an upper bound on the macro variable range.
select id into :idval1 - :idval99999 from ... ;