Simply adding two macro variables in SAS - macros

I'm currently trying to simply add two macros being created. But I'm struggling quite badly with this. It seemed very straight forward at the beginning but..
So here is what I'm trying to do..
%let A = 5;
%let B = 10;
%let AB = &A + &B;
%put &AB;
Rather than giving me 15 which is what I want, SAS spits out 5 + 10
Help anyone..??
Many thanks in advance.

If the values are floating point then use %sysevalf():
%let AB = %sysevalf(&a + &b);

Related

recode and add prefix to sas variables

Lets's say I have a bunch of variables named the same way and I'd like to recode them and add a prefix to each (the variables are all numeric).
In Stata I would do something like (let's say the variables start with eq)
foreach var of varlist eq* {
recode var (1/4=1) (else=0), pre(r_)
}
How can I do this in SAS? I'd like to use the %DO macros, but I'm not familiar with them (I want to avoid SQL). I'd appreciate if you could include comments explaining each step!
SAS syntax for this would be easier if your variables are named using numeric suffix. That is, if you had ten variables with names of eq1, eq2, .... , eq10, then you could just use variable lists to define both sets of variables.
There are a number of ways to translate your recode logic. If we assume you have clean variables then we can just use a boolean expression to generate a 0/1 result. So if 4 and 5 map to 1 and the rest map to 0 you could use x in (4,5) or x > 3 as the boolean expresson.
data want;
set have;
array old eq1-eq10 ;
array new r_eq1-r_eq10 ;
do i=1 to dim(old);
new(i) = old(i) in (4,5);
end;
run;
If you have missing values or other complications you might want to use IF/THEN logic or a SELECT statement or you could define a format you could use to convert the values.
If your list of names is more random then you might need to use some code generation, such as macro code, to generate the new variable names.
Here is one method that use the eq: variable list syntax in SAS that is similar to the syntax of your variable selection before. Use PROC TRANSPOSE on an empty (obs=0) version of your source dataset to get a dataset with the variable names that match your name pattern.
proc transpose data=have(obs=0) out=names;
var eq: ;
run;
Then generate two macro variables with the list of old and new names.
proc sql noprint ;
select _name_
, cats('r_',_name_)
into :old_list separated by ' '
, :new_list separated by ' '
from names
;
quit;
You can then use the two macro variables in your ARRAY statements.
array old &old_list ;
array new &new_list ;
You can do this with rename and a dash indicating which variables you want to rename. Note the following only renames the col variables, and not the other one:
data have;
col1=1;
col2=2;
col3=3;
col5=5;
other=99;
col12=12;
run;
%macro recoder(dsn = , varname = , prefix = );
/*select all variables that include the string "varname"*/
/*(you can change this if you want to be more specific on the conditions that need to be met to be renamed)*/
proc sql noprint;
select distinct name into: varnames
separated by " "
from dictionary.columns where memname = upcase("&dsn.") and index(name, "&varname.") > 0;
quit;
data want;
set have;
/*loop through that list of variables to recode*/
%do i = 1 %to %sysfunc(countw(&varnames.));
%let this_varname = %scan(&varnames., &i.);
/*create a new variable with desired prefix based on value of old variable*/
if &this_varname. in (1 2 3) then &prefix.&this_varname. = 0;
else if &this_varname. in (4 5) then &prefix.&this_varname. = 1;
%end;
run;
%mend recoder;
%recoder(dsn = have, varname = col, prefix = r_);
PROC TRANSPOSE will give you good flexibility with regards to the way your variables are named.
proc transpose data=have(obs=0) out=vars;
var col1-numeric-col12;
copy col1;
run;
proc transpose data=vars out=revars(drop=_:) prefix=RE_;
id _name_;
run;
data recode;
set have;
if 0 then set revars;
array c[*] col1-numeric-col12;
array r[*] re_:;
call missing(of r[*]);
do _n_ = 1 to dim(c);
if c[_n_] in(1 2 3) then r[_n_] = 0;
else if c[_n_] in(4 5) then r[_n_] = 1;
else r[_n_] = c[_n_];
end;
run;
proc print;
run;
It would be nearly trivial to write a macro to parse almost that exact syntax.
I wouldn't necessarily use this - I like both the transpose and the array methods better, both are more 'SASsy' (think 'pythonic' but for SAS) - but this is more or less exactly what you're doing above.
First set up a dataset:
data class;
set sashelp.class;
age_ly = age-1;
age_ny = age+1;
run;
Then the macro:
%macro do_count(data=, out=, prefix=, condition=, recode=, else=, var_start=);
%local dsid varcount varname rc; *declare local for safety;
%let dsid = %sysfunc(open(&data.,i)); *open the dataset;
%let varcount = %sysfunc(attrn(&dsid,nvars)); *get the count of variables to access;
data &out.; *now start the main data step;
set &data.; *set the original data set;
%do i = 1 %to &varcount; *iterate over the variables;
%let varname= %sysfunc(varname(&dsid.,&i.)); *determine the variable name;
%if %upcase(%substr(&varname.,1,%length(&var_start.))) = %upcase(&var_start.) %then %do; *if it matches your pattern then recode it;
&prefix.&varname. = ifn(&varname. &condition., &recode., &else.); *this uses IFN - only recodes numerics. More complicated code would work if this could be character.;
%end;
%end;
%let rc = %sysfunc(close(&dsid)); *clean up after yourself;
run;
%mend do_count;
%do_count(data=class, out=class_r, var_start=age, condition= > 14, recode=1, else=0, prefix=p_);
The expression (1/4=1) means values {1,2,3,4} should be recoded into
1.
Perhaps you do not need to make new variables at all? If have variables with values 1,2,3,4,5 and you want to treat them as if they have only two groups you could do it with a format.
First define your grouping using a format.
proc format ;
value newgrp 1-4='Group 1' 5='Group 2' ;
run;
Then you can just use a FORMAT statement in your analysis step to have SAS treat your five level variable as it if had only two levels.
proc freq ;
tables eq: ;
format eq: NEWGRP. ;
run;

SAS, date manipulation

%let enddt = intnx('month',today(),0);
%let data = DataName_%sysfunc(intnx(month,%sysfunc(date()),-1),yymmn6);
%let intdt = intnx('month',today(),-7);
%let start = intnx('month',today(),-25);
How can I write the other 3 macro variables based on the first one (enddt)
For example, As of today, intdt=enddt-7, start=enddt-25. Data=DataName_201604.
If I change the enddt to intnx('month',today(),-1), then the other three will automatically changed. intdt=enddt-7, start=enddt-25. Data=DataName_201603.
Now if I want the date go back to 2 month, I have to do it manually like this:
%let enddt = intnx('month',today(),-2);
%let data = DataName_%sysfunc(intnx(month,%sysfunc(date()),-3),yymmn6);
%let intdt = intnx('month',today(),-9);
%let start = intnx('month',today(),-27);
If I understand you correctly, you want to loop over a date range with your 4 variables fixed time distances apart from each other. You need to create a macro function that redefines the 4 variables each iteration, something like this should get you on the right track:
%macro dateloop();
%do i=0 %to 10;
%let enddt = %sysfunc(intnx(month,%sysfunc(date()),-&i),yymmn6);
%let data = DataName_%sysfunc(intnx(month,%sysfunc(date()),-&i-1),yymmn6);
%let intdt = %sysfunc(intnx(month,%sysfunc(date()),-&i-7),yymmn6);
%let start = %sysfunc(intnx(month,%sysfunc(date()),-&i-25),yymmn6);
%put enddt = &enddt, data = &data, intdt = &intdt, start = &start;
/**** do your work here ****/
%end;
%mend;
%dateloop();
Your issue is that you're not setting intdt and start to have anything to do with enddt. Try replacing "today()" with "&enddt."
For example:
%let intdt = intnx('month',&enddt.,-7);

Can we add a statement in between MATLAB codes?

Is it possible to add statements in between the codes.
For example: If I have a code like this,
r(:,1) = a(:,1) - a(:,2);
Then can I write it as,
r(:,1) = a(:,1)("this is a constant") - a(:,2)("this is a variable");
You need to comment those statements like this
r(:,1) = a(:,1) ... % this is a constant
- a(:,2); % this is a variable
for more information read this
Just as a sideremark to the very correct answer of sudomakeinstall2
In other programming languages, as e.g. Java you can do something like
myvar = 1 + /* some comment here */ 5;
Eventhough MATLAB knows something similar with %{ ending with %}, they only work for multiline comments, so in the end, sudomakeinstall2s answer is the best I can think of.

Get the ith word in a macro variable list

%let TableList = TableA TableH TableB TableG;
Words in &TableList are separated by ' '.
How can I retrieve certain word to do the following?
I do not know the number of words in the tablelist and would like to get the nth word from the list.
Given i = 4,
data &&table&i.; /* &&table&i. will resolve to TableG */
set have;
[..];
run;
I would have done the same %sysfunc(scan) trick as #mjsqu and as to answer your remaining question - of getting the last word because you don't know the number of words in the list, the easiest way I can think of is using array like below
%let all=word1 word2 word3 word4 word5;
%macro test;
data _NULL_;
array x[*] &all.;
Num=dim(x);
call symput("Num_of_words",num);
run;
%mend;
%test;
Now you know the total number of words so can find out the last word as well.
The short answer is to use the %scan function:
%put %scan(&tablelist,4,%str( ));
The third argument specifies that %scan should count only spaces as delimiters. Otherwise, it will also treat all of the following characters as delimiters by default:
. < ( + & ! $ * ) ; ^ - / , % |
Given the list you have, you can use a %do loop to add the macro variables to a list:
/* initialise a counter macro variable */
%let k = 1;
/* iterate through tablelist until a value is not found */
%do %until (%scan(&tablelist,&k,%str( )) = );
%let table&k = %scan(&tablelist,&k,%str( ));
%let k = &k + 1;
%end;
%let i = 4;
%put &&table&i;
N.B. this code only works inside a macro definition (that is a block of code delimited by %macro and %mend statements.
If you're doing this for the purpose of selecting on the fly one word from the list, you should just make a macro, not try to set up macro variables. Too much extra work to do all that business to make the various macro variables versus a one-line macro.
%let tableList=TableA TableB TableC TableD;
%macro selectTable(k=);
%scan(&tablelist,&k)
%mend selectTable;
data %selectTable(k=4);
set sashelp.class;
run;

Number formatting in SAS : How to create a number list with 01, 02,

I am working with sas, and I have a program I have made I want to run on a series of databases which are indexed by 01, 02 until 95 in characters ! (they correspond to different geographical areas).
I have created my program and would like to use a %lanc macro, but I would like to know if there is a better way thant
%lanc(area=01)
%lanc(area=02)
...
%lanc(area=95)
My problem is therefore
How to add a zero before a number with the DO TO proc (or is there a better way ?)
How to convert these numbers in characters
Thanks
Have a macro loop, create a copy of the loop counter but formatted to z2., then pass that into your macro call...
%MACRO LOOPER ;
%DO LN = 1 %TO 95 ;
%LET Z2 = %SYSFUNC(putn(&LN,z2.)) ; /* format &LN in z2. */
%LANC(AREA=&Z2) ;
%END ;
%MEND ;
%LOOPER ;