I have a list of company IDs and Dates and I want to run a macro on this list in such a way that for each date all the company IDs need to be considered as my macro filter.
For example, my list is -
DATA comp_date_list;
INPUT compno sdate;
DATALINES;
12490 20090120
87432 20090120
24643 20090120
87432 20090119
12490 20090105
24643 20090105
;
proc print data=comp_date_list;
run;
Now, I have a macro that is as follows -
%macro1(compno=,sdate=,threshold=,fdate=,edate=)
Now The macro has to run for every comp-date combination in my list. But since this is to run on a very large dataset, running it n times will take a long time. So to reduce the runtime, I plan to make a list of compnos for a given date and alter my macro to produce the results for a date.
Now my question is how to create a macro variable that has all the compnos for a given date and which alters as date changes? am new to macro writing and SAS. So please excuse my ignorance. Thanks!
A call execute statement in a datastep can run selective code (in this case, your macro) after the datastep in which it was called. For example, the following should work for you:-
proc sort data = comp_date_list;
by sdate compno;
data _null_;
set comp_date_list;
by sdate;
attrib all_comps_on_date length=$1000 label="All_comps_on_date: '|' separated company numbers for date";
retain all_comps_on_date "";
if first.sdate then all_comps_on_date = '';
all_comps_on_date = catx('|', all_comps_on_date, compno);
if last.sdate then call execute('%macro1(compno='||strip(all_comps_on_date)||',sdate=,threshold=,fdate=,edate=)');
run;
A note of caution though; call execute can play havoc with macros that themselves create macro variables (especially if they are using call execute statements!)
I can only echo #ChrisJ and add that while SAS macros can be useful, maintaining and debugging them is a pain and I only use them as a last resort. Not much help of course with legacy code!
Related
I have files that should be send out every week. These files changes names e.g. "filename_1" next week it will be "filename_2".
But it only takes the filename that I manually wrote which is filename_1. Is there a way to say that it should take the latest file with this name everyweek,instead of me doing it manually every week?
This is my code for the email (I manually wrote the filename):
filename outbox email from="test#test.dk" to="test#test.dk"
type='text/html' subject='test' attach=("F:\filename_1.png" ct='png')
ods html body=outbox rs=none style=Htmlblue;run; ods html close;
The SAS macro facility will help you solve this very problem. If your filenames always have a consistent pattern, you can assign a macro variable to automatically change it for you. For simplicity's sake, let's say your filename always ends with today's date. You can assign a macro variable to hold this value.
%let filename = filename_&sysdate9..png;
This will resolve to filename_14DEC2020.png. You can confirm it with %put &filename.
If your file is sent out weekly and increments in a pattern, some quick math will help us figure out the correct suffix. Let's set a base week to start. We can count the number of weeks from this base week to identify the suffix. In this case, let's say it's today: December 14th, 2020. intck() can count the number of weeks from then until today. Our logic is:
suffix = (Number of weeks from Dec. 14th 2020 to Today) + 1.
In data step language, this is:
suffix = intck('week', '14DEC2020'd, today() ) + 1;
Translated to SAS macro language:
%let suffix = %sysevalf(%sysfunc(intck(week, %sysfunc(inputn(14DEC2020, date9.)), %sysfunc(today()) )) + 1);
%let filename = filename_&suffix..png;
Because we're pulling from data step functions, we need to enclose nearly everything in %sysfunc() to call them. This is one of the functions available that connect the SAS macro facility with the data step language.
Note that we also cannot use date literals directly in the SAS macro facility. We must use inputn() or putn() to convert a human-readable date into a SAS date format.
Simply call this macro variable within your code and it will resolve automatically (except within single quotes).
filename outbox email
from="test#test.dk" to="test#test.dk"
type='text/html'
subject='test'
attach=("F:\&filename" ct='png')
;
I would like to conditionally process blocks of syntax where the condition is based on the active data set.
Within an SPSS macro, you can conditionally process a block of syntax using the !IF/!IFEND macro command. However, as far as I can tell, the user is required to explicitly give a value to the flag by either using the !LET command (!LET !FLAG = 1), or by using a Macro input variable. This is wildly different from my experience with other languages, where I can write code that has branching logic based on the data I'm working with.
Say that there is a block of syntax that I only want to run if there are at least 2 records in the active data set. I can create a variable in the data set which is equal to the number of records using the AGGREGATE function, but I can't find a way to make a macro variable equal to that value in a way that is usable as a !IF condition. Below is a very simple version of what I'd like to do.
COMPUTE DUMMY=1.
AGGREGATE
/OUTFILE = * MODE = ADDVARIABLES
/BREAK DUMMY
/NUMBER_OF_CASES = N.
!LET !N_CASES = NUMBER_OF_CASES.
!IF (!N_CASES > 1) !THEN
MEANS TABLES = VAR1 VAR2 VAR3.
!IFEND
Is what I'm attempting possible? Thanks in advance for your time and consideration.
Following is a way to put a value from the dataset into a macro, which you can then use wherever you need - including in another macro.
First we'll make a little dataset to recreate your example:
data list free/var1 var2 var3.
begin data
1 1 1 2 2 2 3 3 3
end data.
* this will create the number of cases value:
AGGREGATE /OUTFILE = * MODE = ADDVARIABLES /BREAK /NUMBER_OF_CASES = N.
Now we can send the value into a macro - by writing a separate syntax file with the macro definition.
do if $casenum=1.
write out='SomePath\N_CASES.sps' /"define !N_CASES() ", NUMBER_OF_CASES, " !enddefine.".
end if.
exe.
insert file='SomePath\N_CASES.sps'.
The macro is now defined and you can use the value in calculations (e.g if you want to use it for analysis of a different dataset, or later in your syntax when the current data is not available).
for example:
compute just_checking= !N_CASES .
You can also use it in your macro as in your example - you'll see that the new macro can't read the !N_CASES macro as is, that's why you need the !eval() function:
define !cond_means ()
!IF (!eval(!N_CASES) > 1) !THEN
MEANS TABLES = VAR1 VAR2 VAR3.
!IFEND
!enddefine.
Now running the macro will produce nothing if there is just one line in your data, and will run means if there was more than one line:
!cond_means.
I need to create sum of 4 variables multiple times each time with new set of variables. For e.g. A1=sum(a1,a2,a3,a4),B1=sum(b1,b2,b3,b4) & so on. So , I am trying to write a macro that will help me do it easily. Following is the code:
%macro SUM2(VAR1,var2,var3,VAR4);
data Subs_60_new;
set Subs_60;
substr(&var1,1,10)=sum(&var1,&var2,&var3,&var4);
run;
%mend sum2;
options mprint mlogic;sum2(ADDITIONAL_INFO_Q1,ADDITIONAL_INFO_Q2,ADDITIONAL_INFO_Q3,ADDITIONAL_INFO_Q4);
I am using SAS EG for the same & when I run the macro I get the following note:
NOTE: Writing TAGSETS.SASREPORT13(EGSR) Body file: EGSR
& obviously when I try to execute the macro it throws an error.
Can some one help me out?
when calling a macro, you need to precede the macro name with a % symbol, eg as follows:
%macro SUM2(VAR1,var2,var3,VAR4);
data Subs_60_new;
set Subs_60;
substr(&var1,1,10)=sum(&var1,&var2,&var3,&var4);
run;
%mend sum2;
options mprint mlogic;
%sum2(ADDITIONAL_INFO_Q1,ADDITIONAL_INFO_Q2,ADDITIONAL_INFO_Q3,ADDITIONAL_INFO_Q4);
The NOTE is harmless. It is ERRORs and WARNINGs in general that you should be concerned with.
I'd point out that this will probably still throw an error, as you are trying to replace characters in a variable (&var1) that appears as though it should contain a numeric field (being part of a sum function). Given your description of what you are trying to achieve, I'd suggest adding the new variable name as another macro parameter - as follows:
%macro SUM2(VAR1,var2,var3,VAR4,varname);
data Subs_60_new;
set Subs_60;
&varname=sum(&var1,&var2,&var3,&var4);
run;
%mend sum2;
options mprint mlogic;
%sum2(ADDITIONAL_INFO_Q1,ADDITIONAL_INFO_Q2
,ADDITIONAL_INFO_Q3,ADDITIONAL_INFO_Q4
,MyNewVariable);
I have a problem with SAS 9.2.
I'm writing a simple macro which creates dataset and name it according to the variables submitted and some other words/letters/signs, for example
%macro example(var1,var2);
data &var1 || '_word_' || &var2;
a=1;
run;
%mend;
Can anyone help?
Pipes are only for strings within SAS, not within SAS macro. So don't use them here.
SAS Macro does not interpret quotes as indicating a string, it will just read them, so leave out the quotes.
If you want to concatenate elements in macro, you just need to write them appended to each other.
To make clear where the macro variable name ends, append a dot.
This should work:
%macro example(var1,var2);
data &var1._word_&var2.;
a=1;
run;
%mend;
I am calling a macro inside a data step and assigning the macro variable to a data step variable as below.
The input for the macro goes from the input dataset which has some 500 records.
%macro test(inp_var);
%global macro_var;
--- using inp_var variable here---
%if --some condition-- %then call symput('macro_var',-- some value--);
%mend;
data output;
set input;
%test(inp_var);
new_data_step_var = symget('macro_var');
run;
But it's showing the error message pointing the variable new_data_step_var - ERROR 180-322: Statement is not valid or it is used out of proper order.
No SAS macro actually executes "inside" a data step. The macro language processor and data step compiler as two different subsystems that share the code input stream. They hand off to one another as they "eat" chunks of SAS code. In the case of the original program, the language processor in SAS sees the "data" statement and hands off to the data step compiler. The embedded %test macro call is detected and the code input stream is handed to the macro processor FIRST! The macro processor expands all of the code and macro logic inside of the %test macro and then the whole stream of code is handed back to the SAS data step compiler to compile.
So %test is going to run to completion BEFORE the data step even compiles.
If you are looking to make your own subroutines in data step try proc fcmp. Otherwise, just implement your conditional logic inside of the data step as was suggested.
Re-write it using datastep if/then, not macro if/then, and don't create a macro variable, simply use a datastep variable.
%MACRO TEST(var) ;
call missing(tempvar) ;
if --some condition-- then tempvar = --some value-- ;
%MEND ;
data output ;
set input ;
%TEST(inp_var) ;
new_var = tempvar ;
drop tempvar ;
run ;
You cannot use a macro variable in the same data step where you set it with call symput.
The result of your call symput statement is only available after the data step.
So at the time the symget statement is being processed, the macro variable does not exist yet.
Also, it seems rather pointless, why don't you use a retain statement to save the value you want?
e.g.:
data output;
set input;
retain new_data_step_var;
if --some condition -- then new_data_step_var = --some value--;
run;
Macros that contain a proc or a data step are not executable inside of a data step. Macros are not functions or subroutines; they are text, just as if you'd typed it out (just saving some time with loops and conditionals). So the contents of your macro need to either be text that could be executed inside a data step:
%macro mymacro(numiters);
*this macro would be easier to do in an array, but it is an example;
%local t;
%do t = 1 to &numiters.;
x&t. = mean(y&t.,z&t.);
%end;
%mend mymacro;
data output;
set input;
%mymacro(5);
run;
In that case, it is easier (and more stylistically correct) to not store a value in a macro variable. Simply contain the result in a data step variable, and if needed pass that variable's name as one of your arguments.
There are also function-style macros, that actually return a value to the data step (or in this case, return text that equates to a value). They can be used on the right side of an equal sign.
%macro xtothey(in,power);
%local t;
&in.
%do t = 1 to &power-1;
*&in.
%end;
;
%mend myfunctionmacro;
data output;
set input;
y = %xtothey(x,4);
run;
That would actually be more easily done in PROC FCMP (which compiles functions and subroutines), but sometimes macros are better for this (or you might not know FCMP well).
Finally, some macros require procs or data steps of their own. In those cases, unless you're using some FCMP elements such as DOSUBL, you will need to store the value somewhere, whether it is in a dataset or a macro. In those cases, you must run the macro prior to the datastep where you want the value - but you only get one (or a finite number of) return values. You don't get one per row unless you go to some extreme lengths, which usually can be done better without using macro variables. I would argue the below is bad form, as you almost always can do it better without using macro variables - but this is how you would do it if you needed to. FCMP with DOSUBL would probably be the superior choice.
%macro findmode(dset,var,outvar);
proc means data=&dset;
var &var.;
output out=_tempset mode(&var.)=&var._mode;
run;
data _null_;
set _tempset;
call symputx("&outvar.",&var._mode);
run;
%mend findmode;
%findmode(sashelp.class,weight,wtmode);
data output;
set input;
mode=&wtmode;
run;