Need something like %EVAL for date functions - sas

I'm trying to have a SAS data set automatically limit the results based on date but don't want to manually have to manually change the date through a %Let statement.
If I try
%let BeginDate = %EVAL(MDY(MONTH(TODAY()), 1, YEAR(TODAY()));
I get a "Open code statement recursion detected"... I've tried &SYSFUNC and &SYSEVALF but no luck either. It seems like this should be much simpler... any suggestions would surely be appreciated.
Thanks!

#Joe's method is the most straightforward. Additionally, if you wanted to do this in a datastep with similar syntax you could do:
data _null_;
call symputx('BeginDate_ds',mdy(month(today()),1,year(today())));
run;
%put &BeginDate_ds.;

Depending on what you're doing, you either don't need anything, or you need %SYSFUNC.
If you want to have &begindate evaluate to an actual date value, you would use %SYSFUNC.
However, you have five functions there - that's going to require a bunch of sysfuncs, though I think we can do two not five.
%let begindate = %sysfunc(intnx(MONTH,%sysfunc(today()),0,b));
%put &begindate;
We use INTNX with the MONTH and B(eginning) options to tell SAS to go ahead 0 months (so current month) and to go to the Beginning of that month. A second SYSFUNC grabs TODAY(). You could simplify this more:
%let begindate = %sysfunc(intnx(MONTH,"&sysdate."d,0,b));
%put &begindate;
&SYSDATE is a macro variable that stores the system date when SAS was started up; so only use that if you're okay with that (i.e., if SAS likely/definitely started up today).
With SYSFUNC don't forget that you need to drop quotation marks, with the one big exception of the date constant above - that is okay to use them - but note "MONTH" and "b" are not quoted.

Related

How to use call symput on a specific observation in SAS

I'm trying to convert a SAS dataset column to a list of macro variables but am unsure of how indexing works in this language.
DATA _Null_;
do I = 1 to &num_or;
set CondensedOverrides4 nobs = num_or;
call symputx("Item" !! left(put(I,8.))
,"Rule", "G");
end;
run;
Right now this code creates a list of macro variables Item1,Item2,..ItemN etc. and assigns the entire column called "Rule" to each new variable. My goal is to put the first observation of "Rule" in Item1, the second observation in that column in Item2, etc.
I'm pretty new to SAS and understand you can't brute force logic in the same way as other languages but if there's a way to do this I would appreciate the guidance.
Much easier to create a series of macro variables using PROC SQL's INTO clause. You can save the number of items into a macro variable.
proc sql noprint;
select rule into :Item1-
from CondensedOverrides4
;
%let num_or=&sqlobs;
quit;
If you want to use a data step there is no need for a DO loop. The data step iterates over the inputs automatically. Put the code to save the number of observations into a macro variable BEFORE the set statement in case the input dataset is empty.
data _null_;
if eof then call symputx('num_or',_n_-1);
set CondensedOverrides4 end=eof ;
call symputx(cats('Item',_n_),rule,'g');
run;
SAS does not need loops to access each row, it does it automatically. So your code is really close. Instead of I, use the automatic variable _n_ which can function as a row counter though it's actually a step counter.
DATA _Null_;
set CondensedOverrides4;
call symputx("Item" || put(_n_,8. -l) , Rule, "G");
run;
To be honest though, if you're new to SAS using macro variables to start isn't recommended, there are usually multiple ways to avoid it anyways and I only use it if there's no other choice. It's incredibly powerful, but easy to get wrong and harder to debug.
EDIT: I modified the code to remove the LEFT() function since you can use the -l option on the PUT statement to left align the results directly.
EDIT2: Removing the quotes around RULE since I suspect it's a variable you want to store the value of, not the text string 'RULE'. If you want the macro variables to resolve to a string you would add back the quotes but that seems incorrect based on your question.

Include macro in a file name

In SAS I like to make a dynamic filename since I am browsing my data on a daily tabel.
I have tried to include a macro in the filename like this:
%let date=input(put(today()-3,ddmmyy6.),6.); *This is equal to todays date-3 (format = 190317)
filename nlp "DailyB.DG%date";
It does not work can you help me?
To get a intuition of what I like to do I have posted a example below
I want to have a date minus 3 days from today in this format:DDMMYY (190317)
So if i run the code the day after it would be 200317.
The variable should then be put into the code so I get the following:
filename nlp 'DailyB.DG190317';
If you want a macro variable to resolve a function, you need %sysfunc. Here's one way to do that.
%let date=%sysfunc(putn(%eval(%sysfunc(today())-3),ddmmyyn6.)); *This is equal to todays date-3 (format = 190317);
%put &=date;
The first %sysfunc asks for the result of today(), the second asks for the result to be formatted. %eval is needed to subtract 3 from the value, as #Quentin points out in comments.
Alternately, call symputx would work here if you're more comfortable in the data step.
data _null_;
call symputx('date',put(today()-3,ddmmyyn6.));
run;
%put &=date;
So what you currently tried is going to end up with a filename statement like
filename nlp "DailyB.DGinput(put(today()-3,ddmmyy6.),6.)";
To run functions in macro code you need to use %SYSFUNC(). You will also need to use the PUTN() function since the PUT() function doesn't work with %SYSFUNC().
%let date=%sysfunc(putn(%sysfunc(today())-3,ddmmyyn6));
filename nlp "DailyB.DG&date";
If you can get the process changed I would recommend using 8 characters for the date part of the name so that you can include the century. Also if you use Year,Month,Day order for your date string your generated filenames will sort in proper date order and also prevent users confusing Columbus Day for Dewey Decimal System Day.
You need to use the %sysfunc() and %eval() macro functions to evaluate the Data Step functions outside the Data Step. As Joe says:
%let date=%sysfunc(putn(%eval(%sysfunc(today())-3),ddmmyyn6.));
Then you need to change your % to a &
filename nlp "DailyB.DG&date";

I want to automate the sas code for every month

I am calculating around 12 metrics (Say Sales for each month individually for latest 12 months). Every month I need to go manually and change the month everywhere. If there is any way to automate it, it would be very helpful. My code is
proc sql;
create table inter.calls as
select a.district_name,
sum(01JAN2016,01FEB2016,01MAR2016)/terr_count as q1_workingdays,
sum(01APR2016,01JUN2016,01MAY2016)/terr_count as q2_workingdays,
sum(01AUG2016,01SEP2016,01JUL2016)/terr_count as q3_workingdays,
sum(01NOV2016,01DEC2016,01OCT2016)/terr_count as q4_workingdays
from inter.calls_made_bymon_reg3 a left join inter.territory_count b
on a.district_name=b.district_name;
quit;
Now when I refresh for JAN2017, I need to change from FEB2016 to JAN2017 for latest 12months. Every time it is difficult to change the code manually.
I will be very thankful if I get any help!!
It's difficult to understand your problem completely, because as Reeza mentioned the arguments in your sum() functions are invalid (the names begin with a number).
However, I do understand the desire not to manually change dates all over the place. You might find a macro like the below helpful:
%macro prev_month(n);
%let latest_date = %sysfunc(intnx(month,%sysfunc(inputn(&latest_month.,monyy7.)),-&n.));
days_%sysfunc(putn(&latest_date.,monyy7.))
%mend prev_month;
And you would then use it in your query like so:
%let latest_month = JAN2017;
proc sql;
create table inter.calls as
select a.district_name,
sum(%prev_month(11),%prev_month(10),%prev_month(9))/terr_count as q1_workingdays,
sum(%prev_month(8) ,%prev_month(7) ,%prev_month(6))/terr_count as q2_workingdays,
sum(%prev_month(5) ,%prev_month(4) ,%prev_month(3))/terr_count as q3_workingdays,
sum(%prev_month(2) ,%prev_month(1) ,%prev_month(0))/terr_count as q4_workingdays
from inter.calls_made_bymon_reg3 a left join inter.territory_count b
on a.district_name=b.district_name;
quit;
Hope it helps.

Simplifying a Macro Definition

So I have created a macro, which works perfectly fine. Within the macro, I set where the observation will begin reading, and then how many observations it will read.
But, in my proc print call, I am not able to simply do:
(firstobs=&start obs=&obs)
Because although firstobs will correctly start where I want it, obs does not cooperate, as it must be a higher number than firstobs. For example,
%testmacro(start=5, obs=3)
Does not work, because it is reading in the first 3 observations, but trying to start at observation 5. What I want the macro to do is, start at observation 5, and then read the next 3. So what I did is this:
(firstobs=&start obs=%eval((&obs-1)+&start))
This works perfectly fine when I use it. But I am just wondering if there is a simpler way to do this, rather than having to use the whole %eval... call. Is there one simple call, something like numberofobservations=...?
I don't think there is. You can only simplify your macro a little, within the %eval(). .
%let start=5;
%let obs=3;
data want;
set sashelp.class (firstobs=&start obs=%eval(&obs-1+&start));
run;
Data set options are listed here:
http://support.sas.com/documentation/cdl/en/ledsoptsref/68025/HTML/default/viewer.htm#p0h5nwbig8mobbn1u0dwtdo0c0a0.htm
You could count the obs inside the data step using a counter and only outputting the records desired, but that won't work on something like proc print and isn't efficient for larger data steps.
You could try the point= option, but I'm not familiar with that method, and again I don't think it will work with proc print.
As #Reeza said - there is not a dataset option that will do what you are looking for. You need to calculate the ending observation unfortunately, and %eval() is about as good a way to do it as any.
On a side-note, I would recommend making your macro parameter more flexible. Rather than this:
%testmacro(start=5, obs=3)
Change it to take a single parameter which will be the list of data-set options to apply:
%macro testmacro(iDsOptions);
data want;
set sashelp.class (&iDsOptions);
run;
%mend;
%testmacro(firstobs=3 obs=7);
This provides more flexibility if you need to add in additional options later, which means fewer future code changes, and it's simpler to call the macro. You also defer figuring out the observation counts in this case to the calling program which is a good thing.

How to rename variables dynamically in sas?

I have a sas data set. In it i have some variables following a pattern
-W 51 Sales
-W 52 Sales
-W 53 Sales
and so on.
Now i want to rename all of these variables dynamically such that W 51 is replaced by starting date of that week and the new name becomes - 5/2/2013 Sales?
The reason i want to rename them is that i have sales data of all the 53 weeks in an year and the data set would be eassier for me to understand if i had the starting date of a week instead of W(week_no) Sales as a variable name
Is there any way i can do that in sas?
You really don't want to rename your variables. You may think you do, but it'll just bite you eventually.
What you can do instead is give them descriptive labels. This can be done via proc datasets.
proc datasets library=<lib>;
modify <dataset>;
label <variable> '5/2/2013 sales';
run;
Just for fun lets assume you want to do this anyway -- Safest thing to do is just create a copy of the dataset for your output...
this code assumes your variable names are named like w1_sales and output names are going to be renamed to 03JAN2013_sale or something like that.
data newDataSet;
set oldDataSet;
%MACRO rename_vars(mdataset,year);
data &mdataset.;
set &mdataset.;
%do i = 1 %to 53;
%let weekStartDate = %sysfunc(intnx('week&i','01jan&year.'d,0)); %*returns the starting day of week(i) uses sunday as starting date. If you want monday use 0.1 as last param;
%let weekstartDateFormatted = %sysfunc(putn(&weekStartDate.,DATE.)) %*formats into ddMONyyy. substitute whatever format you want;
rename w&i._Sale = &weekstartDateFormatted ._SALES;
%end;
run;
%MEND rename_vars;
%rename_vars(newDataSet,2013);
I don't have time to test this right now, so sommebody let me know if I screwed it up somewhere. This should at least get you going though. Or you can send me or post some code to read a small sample dataset (obviously if this is possible without having to share some proprietary info. You might have to genericize it a bit) with those vars like that and I'll debug it.