SAS 9.3 passing a variable with spaces to a macro - sas

I need to pass a variable that contains spaces to a macro. And use this variable to make some logic and to buid a new column inside the macro.
I've tried something like:
%MACRO func(var);
if first.id then &var = 0;
retain &var;
if descr = %unquote(%str(%'&var%')) then &var = 1;
%MEND;
proc sort data=work.table5a;
by id;
run;
data temp;
set work.table5a;
by id;
%func(PLUTO)
%func(PAPERINO)
%func(BANDA BASSOTTI)
if last.id;
run;
ERROR is:
NOTE: Line generated by the macro variable "VAR".
37 BANDA BASSOTTI
_____
180
ERROR 180-322: Statement is not valid or it is used out of proper order.
If i comment %prova(BANDA BASSOTTI) it works. Any suggestions ?
thanks

You're using &var to create a variable name, and if you want to have a variable name with spaces in it, you need to use the variable name literals, e.g. "BANDA BASSOTTI"n. I haven't done this myself, seems like it makes the code uglier and harder to write, but something like this seems to work:
options validvarname=any;
%MACRO func(var);
retain "&var"n;
if first.id then "&var"n = 0;
if descr = "&var" then "&var"n = 1;
%MEND;

Related

put values to a file using functions without creating new variables

I am processing a dataset, the contents of which I do not know in advance. My target SAS instance is 9.3, and I cannot use SQL as that has certain 'reserved' names (such as "user") that cannot be used as column names.
The puzzle looks like this:
data _null_;
set some.dataset; file somefile;
/* no problem can even apply formats */
put name age;
/* how to do this without making new vars? */
put somefunc(name) max(age);
run;
I can't put var1=somefunc(name); put var1; as that may clash with a source variable named var1.
I'm guessing the answer is to make some macro function that will read the dataset header and return me a "safe" (non-clashing) variable, or an fcmp function in a format, but I thought I'd check with the community to see - is there some "old school" way to outPUT directly from a function, in a data step?
Temporary array?
34 data _null_;
35 set sashelp.class;
36 array _n[*] _numeric_;
37 array _f[3] _temporary_;
38 put _n_ #;
39 do _n_ = 1 to dim(_f);
40 _f[_n_] = log(_n[_n_]);
41 put _f[_n_]= #;
42 end;
43 put ;
44 run;
1 _f[1]=2.6390573296 _f[2]=4.2341065046 _f[3]=4.7229532216
2 _f[1]=2.5649493575 _f[2]=4.0342406382 _f[3]=4.4308167988
3 _f[1]=2.5649493575 _f[2]=4.1789920363 _f[3]=4.5849674787
4 _f[1]=2.6390573296 _f[2]=4.1399550735 _f[3]=4.6298627986
5 _f[1]=2.6390573296 _f[2]=4.1510399059 _f[3]=4.6298627986
6 _f[1]=2.4849066498 _f[2]=4.0483006237 _f[3]=4.4188406078
7 _f[1]=2.4849066498 _f[2]=4.091005661 _f[3]=4.4367515344
8 _f[1]=2.7080502011 _f[2]=4.1351665567 _f[3]=4.7229532216
9 _f[1]=2.5649493575 _f[2]=4.1351665567 _f[3]=4.4308167988
The PUT statement does not accept a function invocation as a valid item for output.
A DATA step does not do columnar functions as you indicated with max(age) (so it would be even less likely to use such a function in PUT ;-)
Avoid name collisions
My recommendation is to use a variable name that is highly unlikely to collide.
_temp_001 = somefunc(<var>);
_temp_002 = somefunc2(<var2>);
put _temp_001 _temp_002;
drop _temp_:;
or
%let tempvar = _%sysfunc(rand(uniform, 1e15),z15.);
&tempvar = somefunc(<var>);
put &tempvar;
drop &tempvar;
%symdel tempvar;
Repurpose
You can re-purpose any automatic variable that is not important to the running step. Some omni-present candidates include:
numeric variables:
_n_
_iorc_
_threadid_
_nthreads_
first.<any-name> (only tweak after first. logic associated with BY statement)
last.<any-name>
character variables:
_infile_ (requires an empty datalines;)
_hostname_
avoid
_file_
_error_
I think you would be pretty safe choosing some unlikely to collide names. An easy way to generate these and still make the code somewhat readable would be to just hash a string to create a valid SAS varname and use a macro reference to make the code readable. Something like this:
%macro get_low_collision_varname(iSeed=);
%local try cnt result;
%let cnt = 0;
%let result = ;
%do %while ("&result" eq "");
%let try = %sysfunc(md5(&iSeed&cnt),hex32.);
%if %sysfunc(anyalpha(%substr(&try,1,1))) gt 0 %then %do;
%let result = &try;
%end;
%let cnt = %eval(&cnt + 1);
%end;
&result
%mend;
The above code takes a seed string and just adds a number to the end of it. It iterates the number until it gets a valid SAS varname as output from the md5() function. You could even then test the target dataset name to make sure the variable doesn't already exist. If it does build that logic into the above function.
Test it:
%let my_var = %get_low_collision_varname(iSeed=this shouldnt collide);
%put &my_var;
data _null_;
set sashelp.class;
&my_var = 1;
put _all_;
run;
Results:
Name=Alfred Sex=M Age=14 Height=69 Weight=112.5 C34FD80ED9E856160E59FCEBF37F00D2=1 _ERROR_=0 _N_=1
Name=Alice Sex=F Age=13 Height=56.5 Weight=84 C34FD80ED9E856160E59FCEBF37F00D2=1 _ERROR_=0 _N_=2
This doesn't specifically answer the question of how to achieve it without creating new varnames, but it does give a practical workaround.

appending a counter to a string obtained by dereferencing macro variable

how do I get so inside the loop I get: var1, var2? I know it does not work to dereference j but the meaning gets more clear to what I want to do (see below)
%let var1 = apple;
%let var2 = pear;
data _null_;
do j=1 to j=2;
put &var&j; //<---?
end;
run;
in the log:
apple
pear
As noted above, J is not a macro variable so you cannot use it as such. You can use the SYMGET function to retrieve the value though. Assuming you want data step logic for some reason:
data _null_;
do i=1 to 2;
x= symget(catt('var', i));
put x;
end;
run;
Sounds like you want to resolve a macro variable whose name you are creating by appending the value of another macro variable to some constant prefix.
If you try to use code like this:
%let var1 = apple;
%let var2 = pear;
%let j=1 ;
%put &var&j;
You will get an error message that the macro variable named VAR does not exist.
You need to signal to the macro processor that it needs to delay trying to evaluate &var until after the suffix has been appended. The way to do this is to double the first &.
%put &&var&j;
The presence of double &'s will cause the macro processor to replace them with a single & and set a reminder to itself the re-scan the result for more macro variable references.
So the first pass will replace && with & and replace &j with 1. Then the second pass will replace &var1 with apple.

load values from datasets into arrays and use them in a datastep

I have 5 separate datasets(actually many more but i want to shorten the code) named dk33,dk34,dk35,dk51,dk63, each dataset contains a numeric field: surv_probs. I would like to load the values into 5 arrays and then use the arrays in a datastep(result), however, I need advice what is the best way to do it.
I am getting error when I use the macro: setarrays: (code below)
WARNING: The quoted string currently being processed has become more than 262 characters long. You might have unbalanced quotation
marks.
WARNING: The quoted string currently being processed has become more than 262 characters long. You might have unbalanced quotation
marks.
ERROR: Illegal reference to the array dk33_arr.
Here is the main code.
%let var1 = dk33;
%let var2 = dk34;
%let var3 = dk35;
%let var4 = dk51;
%let var5 = dk63;
%let varN = 5;
/*put length of each column into macro variables */
%macro getlength;
%do i=1 %to &varN;
proc sql noprint;
select count(surv_probs)
into : &&var&i.._rows
from work.&&var&i;
quit;
%end;
%mend;
/*load values of column:surv_probs into macro variables*/
%macro readin;
%do i=1 %to &varN;
proc sql noprint;
select surv_probs
into: &&var&i.._list separated by ","
from &&var&i;
quit;
%end;
%mend;
data _null_;
call execute('%readin');
call execute('%getlength');
run;
/* create arrays*/
%macro setarrays;
%do i=1 %to 1;
j=1;
array &&var&i.._arr{&&&&&&var&i.._rows};
do while(scan("&&&&&&var&i.._list",j,",") ne "");
&&var&i.._arr = scan("&&&&&&var&i.._list",j,",");
j=j+1;
end;
%end;
%mend;
data result;
%setarrays
put dk33_arr(1);
* some other statements where I use the arrays*
run;
Answer to toms question:
*macro getlength(when executed) creates 5 macro variables named: dk33_rows,dk34_rows,dk35_rows,dk51_rows,dk63_rows
*the macro readin(when executed):creates 5 macro variables dk33_list,dk34_list,dk35_list,dk51_list,dk63_list. Each containing a string which is comma separates the values from the column: eg.: 0.99994,0.1999,0.1111
*the macro setarrays creates 5 arrays,when executed, dk33_arr,dk34_arr,... holding the parsed values from the macro variables created by readin
I find that "macro arrays" like VAR1,VAR2,.... are generally more trouble than they are worth. Either keep your list of dataset names in an actual dataset and generate code from that. Or if the list is short enough put the list into a single macro variable and use %SCAN() to pull out the items as you need them.
But either way it is also better to avoid trying to write macro code that needs more than three &'s. Build up the reference in multiple steps. Build a macro variable that has the name of the macro you want to reference and then pull the value of that into another macro variable. It might take more lines of code, but you can more easily understand what is happening.
%let i=1 ;
%let mvarname=var&i;
%let dataset_name=&&&mvarname;
Before you begin using macro code (or other code generation techniques) make sure you know what code you are trying to generate. If you want to load a variable into a temporary array you can just use a DO loop. There is no need to macro code, or copying values, or even counts, into macro variables. For example instead of getting the count of the observations you could just make your temporary array larger than you expect to ever need.
data test1 ;
if _n_=1 then do;
do i=1 to nobs_dk33;
array dk33 (1000) _temporary_;
set dk33 nobs=nobs_dk33 ;
dk33(i)=surv_probs;
end;
do i=1 to nobs_dk34;
array dk34 (1000) _temporary_;
set dk34 nobs=nobs_dk34 ;
dk34(i)=surv_probs;
end;
end;
* What ever you are planning to do with the DK33 and DK34 arrays ;
run;
Or you could transpose the dataset first.
proc transpose data=dk33 out=dk33_t prefix=dk33_ ;
var surv_probs ;
run;
Then your later step is easier since you can just use a SET statement to read in the one observation that has all of the values.
data test;
if _n_=1 then do;
set dk33_t ;
array dk33 dk33_: ;
end;
....
run;

SAS DO Loop with Macro

To begin, this is for a class, i dont like this language. Its simple Do loop to print the square root of numbers. The objective is to replace the value in the do loop with macro variables. Here is my source code:
%LET Start_Value = 1;
%LET Stop_Value = 5;
DATA sqrt_table;
DO &Start_Value. TO &Stop_Value.;
Sqrt_n = SQRT(&Start_Value.);
OUTPUT;
END;
RUN;
TITLE 'Square root table from 1 to 5';
PROC PRINT DATA = sqrt_table noobs;
RUN;
TITLE;
The Log says the error is in the DO &Start_Value. "Symbol is not recognized"
I followed the the source coude given, i have decalred the macros as they should be, and i am accessing them as i read to do so. What is the issue?
Macro code in general, and in this case specifically, is just used to replace constant text. First get a working DO loop without any macro variables and then replace the parts that you want to vary with the macro variable references.
So the basic syntax for an iterative DO loop is:
do VAR=START to END;
...
end;
Where VAR is a variable name and start and end are numerical expressions.
Compare that to the pattern of your attempt and you can see that you have left off the VAR= part.
Also the assignment statement is going to assign the same value to SQRT_N on every iteration of the DO loop. Because you have essentially written.
Sqrt_n = SQRT(1);
Remember macro variables are just ways to help you generate the program that you want SAS to actually run.
If you are begginer in SAS don't mix macro lanauge wit 4GL. Here is what you need.
%LET Start_Value = 1;
%LET Stop_Value = 5;
DATA sqrt_table;
DO i = &Start_Value. TO &Stop_Value.;
Sqrt_n = SQRT(i);
OUTPUT;
END;
RUN;

SAS:how to use index to pick out macro array variable

I create a marco array using:
proc sql;
select distinct variable into:numVarList separated by ' ' from Map_num;
I used:
%put &numVarList{1};
and it gave me all variables:var1 var2 var3{1}
how to use index to pick out macro array variable?
update 20180305
it is strange that
%put &numVarList.;
then I got:age agenc_non_ccbt_fnd_bal chmtpd_tmpnt_bal crnyr_cnter_tdnum
%put %sysnc(scan(&numVarList.,1,str( )));
I got:age agnc_non_ccb
why?and how to fix it?
You do not create an array with your select. The result is just a string: var1 var2 var3
However you can access each element with the scan-function:
%let first_ele = %scan(&numVarList.,1,%str( ));
The result is: var1
You can also loop your string like this:
%do i=1 %to %sysfunc(countw(&numVarList.,%str( )));
%put %scan(&numVarList.,&i.,%str( ));
%end;
Concatenation of values
proc sql;
select distinct variable into:numVarList separated by ' ' from Map_num;
populates a single macro variable with a value, that can be construed as a list, which is a concatenation of the distinct values in the column named "variable".
For such a list you would scan out the individual items as shown by #zuluk.
In your case when the original values are names of variables, the resolution of the concatenation can be used directly as part of a SAS statement that accepts variable name lists, such as Proc PRINT; VAR &numVarList or DATA _NULL_; ARRAY v &numVarList
Macro array
The concept macro-array is simply a set of macro variables (which can be thought of as 'symbols' when too many 'variable' ideas are colliding) with a common basename and increasing numeric suffix. Such a set of macro variables is created by using a slightly different syntax in Proc SQL.
select distinct variable
into :symbol1-:symbol9999
from Map_num
The 9999 represents a large number that you do not expect to exceed. If the data has N <= 9999 rows then only N macro variable will be created. If N > 9999 rows only 9999 macro variables will be created. Caution: Too many macro variables can fill the macro symbol table and cause errors in your SAS. For me, Macro arrays are more a programming concept than a programming construct.
For example
Proc SQL noprint;
select name into :name1-:name9999 from sashelp.class;
%let name_count = &sqlobs;
quit;
%put NOTE: &=name1;
%put NOTE: &=name2;
%put NOTE: name&name_count=%superq(name&name_count); * almost same as next;
%put NOTE: name&name_count=&&name&name_count; * almost same as prev;
When dealing with the 'name' of the macro array in 1-level abstraction way, complete resolution is achieved by coding the 'tricky triple-hat' &&&
%macro log_macroArray (basename);
%local i count_symbol value_symbol;
%let count_symbol = &basename._count;
%do i = 1 %to &&&count_symbol;
%let value_symbol = &basename.&i;
%put NOTE: &value_symbol=&&&value_symbol;
%end;
%mend;
%log_macroArray(name);
The SAS macro system 'loops' internally during its value resolution phase and collapses the presence to && to & at each step of it's internal evaluation.
Building on #zuluk's answer, you cannot use an operator (like { }) to access a macro "array" since it's not a part of the language and it's not possible to overload operators in SAS... mostly ... but you can do a function-style macro easily.
proc sql;
select name into :namelist separated by ' '
from sashelp.class;
quit;
%macro marray(list, n);
%scan(&list.,&n.)
%mend marray;
%put %marray(&namelist,2);
That is pretty close to what you're looking for, just not quite the same syntax. If you then wanted to build new variables/etc., you could do so through the macro as well, though it might be more complicated to write a general macro given there are lots of ways you might want to do that. Here's a non-function-style version.
%macro m_to_array(list, n);
*optionally - if you want to not specify n;
%let n = %sysfunc(countw(&&&list));
%do _i = 1 %to &n;
%global &list.&_i.;
%let &list.&_i. = %scan(&&&list.,&_i.);
%end;
%mend m_to_array;
%m_to_array(namelist);
%put _global_;