SAS year function not working inside macro - sas
hello am trying access columns from library with specific date format and using year function on the columns in my macro code but it produces duplicate values... but the year function displays duplicate values and does not provide desired results. my code should return only the year from the input dates.
%macro dteyear(lib=,outdsn=);
proc sql noprint;
select distinct catx(".",libname,memname), name
into :dsns separated by " ", :varname separated by " "
from dictionary.columns
where libname = upcase("&lib") and format=('YYMMDD10.')
order by 1;
quit;
%put &dsns;
%put &varname;
%local olddsn curdsn curvbl i;
data &outdsn.;
set
%let olddsn=;
%do i=1 %to &sqlobs;
%let curdsn=%scan(&dsns,&i,%str( ));
%let curvbl=%scan(&varname,&i,%str( ));
%if &curdsn NE &olddsn
%then %do;
%if &olddsn NE
%then %do;
)
%end;
%let olddsn=&curdsn.;
&curdsn (keep=&curvbl
%end;
%else %do;
&curvbl
%end;
%end;
);
%do i=1 %to &sqlobs;
%scan(&varname,&i,%str( ))=year(&varname.);
%end;
run;
proc print data=&outdsn;run;
%MEND;
%dteyear(lib=dte3,outdsn=dtetst);
the input data is as follows
1975-12-04
1977-11-03
1989-09-15
1998-06-17
1999-05-31
2000-08-14
2001-03-11
2007-03-11
2007-12-28
2008-10-07
2009-12-03
duplicate output from my code is-->
Obs RFDTC
1 1965-05-19
2 1965-05-19
3 1965-05-19
4 1965-05-19
5 1965-05-19
6 1965-05-19
7 1965-05-19
8 1965-05-19
9 1965-05-19
10 1965-05-19
11 1965-05-19
12 1965-05-19
13 1965-05-19
The basic problem is that the YEAR() function returns a 4-digit number, and the variable's format is YYMMDD10., so the result is formatted as a SAS date very close to 1960 (SAS's beginning of all time).
What I did in the code below was change the format to 4.0, so it displays as a 4-digit number.
If you want to have access to the original date variable, you'll have to create a new variable for the year. I'll leave that to you.
There was an additional problem--that is, YEAR(&varname.) inserts the entire list of variables, not just the one you're working with. It works if there is only one date variable, but not if there are more than one. I fixed this, too.
%macro dteyear(lib=,outdsn=);
proc sql noprint;
select distinct catx(".",libname,memname), name
into :dsns separated by " ", :varname separated by " "
from dictionary.columns
where libname = upcase("&lib") and format=('YYMMDD10.')
order by 1;
quit;
%put &dsns;
%put &varname;
%local olddsn curdsn curvbl i;
data &outdsn.;
set
%let olddsn=;
%do i=1 %to &sqlobs;
%let curdsn=%scan(&dsns,&i,%str( ));
%let curvbl=%scan(&varname,&i,%str( ));
%if &curdsn NE &olddsn
%then %do;
%if &olddsn NE
%then %do;
)
%end;
%let olddsn=&curdsn.;
&curdsn (keep=&curvbl
%end;
%else %do;
&curvbl
%end;
%end;
);
%do i=1 %to &sqlobs;
%let curvbl=%scan(&varname,&i,%str( ));
&curvbl=year(&curvbl.);
format &curvbl 4.0;
%end;
run;
proc print data=&outdsn;run;
%MEND;
data have;
input datevar yymmdd10.;
format datevar yymmdd10.;
cards;
1975-12-04
1977-11-03
1989-09-15
1998-06-17
1999-05-31
2000-08-14
2001-03-11
2007-03-11
2007-12-28
2008-10-07
2009-12-03
run;
options mprint;
%dteyear(lib=work,outdsn=want)
The result, then, is:
Obs datevar
1 1975
2 1977
3 1989
4 1998
5 1999
6 2000
7 2001
8 2007
9 2007
10 2008
11 2009
To convert a date value to just a year you can use the YEAR() function, but you also need to change the format attached to the variable since you will have essentially divided the value stored in it by 365 to convert it from the number of days to the number of years.
rfdtc = year(rfdtc);
format rfdtc 4. ;
Your macro is attempting to read many variables from many datasets and generate a single output dataset. I am not sure the resulting dataset will be of much value to you since it will look like a checker board of missing values. Also if the same variable name appears in more than one input dataset you will get corrupted values because of applying the YEAR() function to value that has already been converted from a date value to a year value.
For example you could end up generating a data step like this:
data WANT ;
set ds1 (keep=datevar1)
ds1 (keep=datevar2)
ds2 (keep=datevar3)
ds3 (keep=datevar3)
;
datevar1=year(datevar1);
datevar2=year(datevar2);
datevar3=year(datevar3);
datevar3=year(datevar3);
format datevar1 datevar2 datevar3 datevar3 4.;
run;
Since both input datasets DS2 and DS3 have a variable named DATEVAR3 you will be applying the YEAR() function to the value twice. That will convert everything to the year 1965.
To eliminate the problem with running the YEAR() function on the same value multiple times and losing the actual year perhaps you just want to apply the YEAR. format instead of converting the stored value.
format datevar1 datevar2 datevar3 datevar4 year. ;
That would still leave the underlying different date values. If you really need to values to be identical perhaps you could convert the value to the first day of the year? You could use INTNX() function
datevar1 = intnx('year',datevar1,0,'b');
or the MDY() function
datevar1 = mdy(1,1,year(datevar1));
Related
Iterate date in loop in SAS
need help on one query , I have to iterate date in do loop that is in format of yymmd6.(202112) so that once the month reach to 12 then its automatically change to next year first month. ///// code//////// %let startmo=202010 ; %let endmo= 202102; %macro test; %do month= &startmo %to &endmo; Data ABC_&month; Set test&month; X=&month ; %end; Run; %mend; %test; ////////// Output should be 5 dataset as ABC_202010 ABC_202011 ABC_202012 ABC_202101 ABC_20210 I need macro variable month to be resolved 202101 once it reached to 202012
Those are not actual DATE values. Just strings that you have imposed your own interpretation on so that they LOOK like dates to you. Use date values instead and then it is easy to generate strings in the style you need by using a FORMAt. %macro test(startmo,endmo); %local offset month month_string; %do offset = 0 to %sysfunc(intck(month,&startmo,&endmo)); %let month=%sysfunc(intnx(month,&startmo,&offset)); %let month_string=%sysfunc(putn(&month,yymmn6.)); data ABC_&month_string; set test&month_string; X=&month ; format X monyy7.; run; %end; %mend; %test(startmo='01OCT2020'd , endmo='01FEB2021'd) And if you need to convert one of those strings into a date value use an INFORMAT. %let date=%sysfunc(inputn(202010,yymmn6.));
I would prefer to use a do while loop. check whether the last 2 characters are 12, if so, change the month part to 01. code %let startmo=202010 ; %let endmo= 202102; %macro test; %do %while(&startmo <= &endmo); Data ABC_&startmo; Set test&startmo; X=&startmo ; Run; %end; %let mon = %substr(&startmo, 5, 2); %let yr = %substr(&startmo, 1, 4); %if &mon = 12 %then %do; %let m = 01; %let startmo = %sysfunc(cat(%eval(&yr + 1), &m)); %end; %else %do; %let startmo = %eval(&startmo + 1); %end; %mend; %test;
how can I build a loop for macro in SAS?
I want to do a simulation based on macro in SAS. I can build a function named 'fine()', the code is as follows DATA CLASS; INPUT NAME $ SEX $ AGE HEIGHT WEIGHT; CARDS; ALFRED M 14 69.0 112.5 ALICE F 13 56.5 84.0 BARBARA F 13 65.3 98.0 CAROL F 14 62.8 102.5 HENRY M 14 63.5 102.5 RUN; PROC PRINT; TITLE 'DATA'; RUN; proc print data=CLASS;run; PROC FCMP OUTLIB = work.functions.func; function populationCalc(HEIGHT,WEIGHT,thres); pop=HEIGHT-WEIGHT-thres; return (pop); ENDSUB; options cmplib=(work.functions); %macro fine(i); data ex; set CLASS; thres=&i; pop = populationCalc(HEIGHT,WEIGHT,thres); if (pop>50) then score=1; else score=0; run; proc iml; USE ex; READ all var _ALL_ into ma[colname=varNames]; CLOSE ex; nn=nrow(ma); total_score=sum(ma[,'thres']); avg_score=sum(ma[,'thres'])/nn; print total_score avg_score; %mend fine; %fine(10); %fine(100); %fine(150); I want to build a loop for function 'fine()' ans also use macro, but the result is not as I expect. How can I fix this? %macro ct(n); data data_want; %do i=1 %to &n; x=%fine(&i); output x; %end; run; %macro ct; %ct(10);
%fine does not generate any text that can be used in the context of a right hand side (RHS) of a DATA Step variable assignment statement. You seem to perhaps want this data set as a result of invoking %ct i total_score average_score - ----------- ------------- 1 5 1 2 10 2 3 15 3 etc... Step 1. Save IML result Add this to the bottom of IML code in %fine, replacing the print create fine_out var {total_score avg_score}; append; close fine_out; quit; Step 2. Rewrite ct macro Invoke %fine outside a DATA step context so the DATA and IML steps can run. Append the IML output to a results data set. %macro ct(n,out=result); %local i; %do i=1 %to &n; %fine(&i) %if &i = 1 %then %do; data &out; set fine_out; run; %end; %else %do; proc append base=&out data=fine_out; run; %end; %end; %mend; options mprint; %ct(10) This should be the output WORK.RESULT based on your data
How to receive and filter array data coming from a report to a stored process is SAS
I've a parameter of named _ID who accepts multiple values from a list and send them to my stored process, lets say I've sent four values (1,2,3,4) in it, I'll receive them in my store process as, _ID0 = 4 _ID1 = 1 _ID2 = 2 _ID3 = 3 _ID4 = 4 _ID_COUNT = 4 I'm receiving and filtering them as following. %let ID = "&_ID"; %let Count = "&_ID_COUNT"; %macro IDs; %global _ID0; /* If more than one ID value was selected then cycle through the values */ %if %eval(&_ID0 ge 2) %then %do; %do i=1 %to &_ID0; &&_ID&i %end; %end; /* If only one ID value was selected */ %else &_ID %mend; ****************************; %macro filter; %if &Count ne "0" %then %do; %stpbegin; proc sql noprint; create table users as select * from work.users where id in(%IDs); run; %stpend; %end; %mend; %filter; there is not any error in log above one is my code but It's not filtering anything. if user table has values 1-10 in id column user should be update with only 1,2,3,4 user id 1 2 3 4 5 6 7 8 9 10 after filter i want user id 1 2 3 4 I don't know What's wrong and did I miss any better approach.
Your code should work. The %IDS macro could be more concise and might need more logic to deal with the inconsistency of how macro variables are created when count is less than 2. So this macro will make sure that the 0 and 1 variables are populated (at least while the macro is running) before trying to loop over them. %macro IDs; %local i ; %let _id0 = &_id_count ; %if &_id0 = 1 %then %let _id1 = &_id ; %if &_id0 = 0 %then -99999 ; %do i=1 %to &_id0; &&_ID&i %end; %mend IDs; Based on your example data it should work like this: 1071 %put (%ids); (1 2 3 4) What value do you want to emit if they don't select any values? I have set this example up to generate -9999, but your other macro should already be skipping the call in that case so it shouldn't matter.
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;
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.