Comparing date columns using sas macros - sas

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;

Related

How to get number of rows equal to zero instead of nothing if table is empty is SAS

I have the following code, which stores the number of rows in a table inside the macro-variable n_vars:
data cont_vars;
set var_list;
where flg_categorical = 0;
call symput('n_vars', _n_);
%put &n_vars;
run;
Currently if the resulting table is empty then n_vars resolves to nothing and I want it to be set equal to 0 so I can use it in a %do x = 1 %to &n_vars; loop later.
If the where statement indeed leads to no observation in the dataset, the following code should then set the n_vars macro-variable to 0:
data want;
if eof then call symputx('n_vars',_n_-1);
set have end=eof;
where flg_categorical = 0;
run;
Please consider creating a Minimal Reproducible Example.
data have;
infile datalines delimiter=',';
input x1 flg_categorical;
datalines;
1,1
6,2
3,2
;
run;
data want;
if eof then call symputx('n_vars',_n_-1);
set have end=eof;
where flg_categorical = 0;
run;
%put &=n_vars;
Result:
Anyway I don't know see why you would use a %do loop that would be incremented from 1 to 0.
Maybe you just want to run another kind of steps when the data set is empty. If that is the case, I would consider using:
%if &n_vars. > 0 %then %do; /* data set is not empty */
...
%end;
%else %do; /* data set is empty */
...
%end;

SAS MACRO error: Nesting of %IF statements in open code is not supported. %IF ignored

When I execute this code, it came up with an error: Nesting of %IF statements in open code is not supported. %IF ignored.
The code is as follows:
%let Vis_Perform_Filter = VISYN^=:"Y";
data cp_ref_patient_visits;
length CP_VISIT_PERFORMED 8.;
set cp_ref_patient_visits;
/* Are all visit identifiers related to actual performed visits? */
/* if select 'Yes' */
%IF %superQ(Vis_Perform_Filter) eq %THEN %DO;
CP_VISIT_PERFORMED = 1; /* 1 OR 0 */
%END;
/* if select 'No' then apply filter condition */
%ELSE %IF %superQ(Vis_Perform_Filter) ne %THEN %DO;
if &Vis_Perform_Filter. then CP_VISIT_PERFORMED = 0;
else CP_VISIT_PERFORMED = 1;
%END;
label CP_VISIT_ID = "Internal Visit Identifier"
CP_VISIT_PERFORMED = "Visit Performed Flag";
proc sort; by SUBJID CP_PATIENT CP_VISIT_ID;
run;
And the Error is:
31 %ELSE %IF %superQ(Vis_Perform_Filter) ne %THEN %DO;
ERROR: Nesting of %IF statements in open code is not supported. %IF ignored.
ERROR: Skipping to next %END statement.
I want to know why this error happens? How to solve this error?
Thank you for your help~
If you have a lot of filters that are specified as 'data step source code snippets' in possible macro variables, you may want to code a separate macro (CODEGEN_flag_logic) to examine the filters by name and determine whether or not that source code should be emitted as part of the data step.
Example:
%MACRO CODEGEN_flag_logic (FILTER, DEFAULT=1);
%* FILTER is name of macro variable containing source code that is data step logical evaluation snippet;
%if %length(&FILTER) %then
%if %symexist (&FILTER) %then
%if %length (&&&FILTER) %then
&&&FILTER;
%else
&DEFAULT;
%else
&DEFAULT;
%else
&DEFAULT;
%MEND;
%let name_flag_logic = name =: "J"; /* extant source code */
data want;
set sashelp.class;
name_flag = %codegen_flag_logic (name_flag_logic);
name_flag_inverse = %codegen_flag_logic (name_flag_logic, DEFAULT=0);
run;
%let name_flag_logic = ; /* extant macro variable with no source code */
data want;
set sashelp.class;
name_flag = %codegen_flag_logic (name_flag_logic);
run;
%SYMDEL name_flag_logic; /* macro variable not present in session or scope */
data want;
set sashelp.class;
name_flag = %codegen_flag_logic (name_flag_logic);
run;
There will still be problems if said filters contain invalid SAS code or deal with variables expected in the PDV that are not present.
Such as
/* snippet uses homeroom (which will be noted in LOG as uninitialized */
%let name_flag_logic = homeroom = 'sunny';
/* snippet is invalid sas code and will show ERROR in the LOG */
%let name_flag_logic = busroute one of (1,2,3);
You should probably move your logic about the value of the macro variable outside of the logic of the data step to make things clearer. Looks like when the macro variable is empty you want to execute the ELSE clause of the IF statement. So just set the value to something that is false.
%let Vis_Perform_Filter = VISYN^=:"Y";
%IF %superQ(Vis_Perform_Filter) eq %THEN %DO;
%let Vis_Perform_Filter=0;
%END;
Then generating your data step code does not require any macro logic, much less any nested macro logic. You just expand the value of the macro variable where the IF statement is expecting the code to use to evaluate the condition.
data cp_ref_patient_visits;
length CP_VISIT_PERFORMED 8.;
set cp_ref_patient_visits;
/* Are all visit identifiers related to actual performed visits? */
if &Vis_Perform_Filter. then CP_VISIT_PERFORMED = 0;
else CP_VISIT_PERFORMED = 1;
label CP_VISIT_ID = "Internal Visit Identifier"
CP_VISIT_PERFORMED = "Visit Performed Flag"
;
run;
proc sort; by SUBJID CP_PATIENT CP_VISIT_ID;
run;
As this blog post reveals
%if %then %else statements are not supported in open code until SAS 9.4M5. And there are restrictions to its use.
My suggestion is to get rid of the macro functions when you are inside a data step. For example, replace
%IF %superQ(Vis_Perform_Filter) eq %THEN %DO;
CP_VISIT_PERFORMED = 1; /* 1 OR 0 */
%END;
with
if VISYN ne "Y" then CP_VISIT_PERFORMED = 1;

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;

Creating datasets within Macro SAS

I have the following code:
%macro MSA (Data=, Code=, MSAName=);
data &Data;
set NoDup;
%if MSA = &Code %then %do;
MSA_name = "&MSAName";
output &data;
%end;
run;
%mend MSA;
%MSA (Data=Bakersfield, Code=12540, MSAName=Bakersfield);
%MSA (Data=Chico, Code=17020, MSAName=Chico);
So I get two datasets as I want with one names Bakersfield and another Chico. However, the MSA column does not display the correct value (i.e. a column of 12540 for Bakersfield and 17020 for Chico for MSA), nor do I get a variable named MSA_Name that gives me the correct values (Bakersfield for all of the MSA_Name column, and Chico). What am I doing wrong?
The issues you have with your code is to mingle macro syntax with data step. Please try the following:
%macro MSA (Data=, Code=, MSAName=);
data &Data;
set NoDup;
if MSA = &Code /*if MSA is char, you will need quote "&code"*/ then do;
MSA_name = "&MSAName"; output; end;
run;
%mend MSA;
%MSA (Data=Bakersfield, Code=12540, MSAName=Bakersfield);
%MSA (Data=Chico, Code=17020, MSAName=Chico);

Unable to match macro variable with dataset variable

The character variable in dataset never matches with the macro variable. The %IF loop never comes true. Kindly advice.
I am trying to match by months and accordingly trying to create array and put counts only for specific months. Not working because the month macro variable never matches with dataset variable having month.
/*create dummy data*/
data datefile;
input tran_date date9. cnt 3.;
datalines;
13feb2015 5
10feb2015 4
11feb2015 3
05feb2015 8
08feb2015 5
01jan2015 1
20dec2014 1
31jan2015 2
23dec2014 2
12jan2015 1
;
/*calculate month*/
data datefile11;
set datefile;
tran_mon=year(tran_date)*100+month(tran_date);
run;
/*select distinct month*/
proc sql;
create table datefile12 as select distinct(tran_mon)
from datefile11 order by tran_mon;
quit;
/*convert month from numeric to character*/
data datefile11(drop=tran_mon);
informat tran_mon2 $6.;
set datefile11;
tran_mon2=tran_mon;
run;
/*create macro variables through datastep*/
data datefile13;
set datefile12;
monum = cat('mnth',_N_);
run;
data _null_;
set datefile13;
call symput(monum,trim(left(tran_mon)));
run;
/*use array to make separate column for each month and
put split count for each month to each colunms*/
%macro c;
proc sql noprint;
select count(1) into :nrow from datefile13;
quit;
%let nrow = &nrow;
data datefile14;
set datefile11;
array mon{*} mon_1 - mon_&nrow;
%do i=1 %to &nrow;
%if tran_mon2 = &&mnth&i %then %do; %put tran_mon2;
mon_&i = cnt; %end;
%else %do; mon_&i = 0 ; %end;
%end;
run;
%mend c;
%c
Your macro %if %then %do check executes while the data step is still being compiled - by the time the data step has begun to execute, there is no further opportunity to use macro logic like that.
Try doing it the other way round - write your loop using if then do data step logic instead.