Creating datasets within Macro SAS - sas

I have the following code:
%macro MSA (Data=, Code=, MSAName=);
data &Data;
set NoDup;
%if MSA = &Code %then %do;
MSA_name = "&MSAName";
output &data;
%end;
run;
%mend MSA;
%MSA (Data=Bakersfield, Code=12540, MSAName=Bakersfield);
%MSA (Data=Chico, Code=17020, MSAName=Chico);
So I get two datasets as I want with one names Bakersfield and another Chico. However, the MSA column does not display the correct value (i.e. a column of 12540 for Bakersfield and 17020 for Chico for MSA), nor do I get a variable named MSA_Name that gives me the correct values (Bakersfield for all of the MSA_Name column, and Chico). What am I doing wrong?

The issues you have with your code is to mingle macro syntax with data step. Please try the following:
%macro MSA (Data=, Code=, MSAName=);
data &Data;
set NoDup;
if MSA = &Code /*if MSA is char, you will need quote "&code"*/ then do;
MSA_name = "&MSAName"; output; end;
run;
%mend MSA;
%MSA (Data=Bakersfield, Code=12540, MSAName=Bakersfield);
%MSA (Data=Chico, Code=17020, MSAName=Chico);

Related

running sas program for multiple parameters in loop

Very new to sas, need to perform export procedure for many datasets called data1, data2, data3 ... data10.
Is there a way to operationalize this? I've tried the following without success
LIBNAME input '/home/.../input';
LIBNAME output '/home/.../output';
%macro anynumber(number);
proc export data=input.data&number file="/home/.../output/data&number..dta" dbms=dta replace;
run;
%mend;
do i = 1 to 10;
%anynumber(i)
end;
run;
CALL EXECUTE is the recommended option but a macro loop is also a good option here.
You could loop it within the same macro as well. All options are illustrated below.
Usually when I see this type of coding though, my first thought is that someone isn't familiar with BY group processing.
data demo;
do i=1 to 10;
*make sure this string matches macro call;
str = catt('%anynumber(', i, ');');
*write records to log to check;
put str;
call execute(str);
end;
run;
Another option is a macro loop itself.
%macro loop_export(numLoops=10);
%do i=1 %to &numLoops;
%anywhere(&i);
%end;
%mend;
%loop_export(numLoops=10);
Putting them together:
%macro anynumber(number);
%do i=1 %to &number;
proc export data=input.data&number file="/home/.../output/data&i..dta" dbms=dta
replace;
run;
%end;
%mend;
*will export all 10;
%anyNumber(10);

Checking for variable type in SAS-Macro

I am trying to summarize my variables using proc sql and proc freq procedures in a macro.
Here is the code:
%macro des_freq(input= ,vars= );
%let n=%sysfunc(countw(&vars));
%let binary=NO;
%do i = 1 %to &n;
%let values = %scan(&vars, &i);
%if %datatyp(&values)=NUMERIC %then %do;
proc summary data = &input;
output out=x min(&values)=minx max(&values)=maxx;
run;
data _null_;
set x;
if minx = 0 and maxx = 1 then call symputx('binary','YES');
run;
%if &binary = YES %then %do;
proc sql;
select segment_final,
(sum(case when &values = 1 then 1 else 0 end)/ count(*)) * 100 as &values._percent
from &input
group by segment_final;
quit;
%end;
%else %do;
proc freq data =&input;
tables segment_final*&values/nofreq nopercent nocol;
run;
%end;
%end;
%else %do;
proc freq data =&input;
tables segment_final*&values/nofreq nopercent nocol;
run;
%end;
%end;
%mend;
My variables can be numeric or character. If it's numeric, it can 2 more distinct values.
I want % of 1's in a binary variable by segments(hence proc sql) and % of all distinct variables for each segment(hence proc freq).
My first if statement is checking whether the variable if numeric or not and then if its numeric, next few steps is checking if its binary or not. If its binary then execute the proc sql else execute proc freq.
If the variable is character then just execute the proc freq.
I am not able to figure out how to check if my variable is numeric or not. I tried %SYSFUNC(Vartype), %isnum and %DATATYP. None of them seem to work. Please help!!
First you can look into sashelp.vcolumn table to check variables types:
data want(keep=libname memname name type);
set sashelp.vcolumn( where= (libname='SASHELP' and memname='CLASS'));
run;
If you don't want to use vcolumn table, you can use vtype() data step function as #Tom suggest:
data _NULL_;
set &input (obs=1);
call symput('binary',ifc(vtype(&values)='N','YES','NO' ));
run;

How to scan a numeric variable

I have a table like this:
Lista_ID 1 4 7 10 ...
in total there are 100 numbers.
I want to call each one of these numbers to a macro i created. I was trying to use 'scan' but read that it's just for character variables.
the error when i runned the following code was
there's the code:
proc sql;
select ID INTO: LISTA_ID SEPARATED BY '*' from
WORK.AMOSTRA;
run;
PROC SQL;
SELECT COUNT(*) INTO: NR SEPARATED BY '*' FROM
WORK.AMOSTRA;
RUN;
%MACRO CICLO_teste();
%LET LIM_MSISDN = %EVAL(NR);
%LET I = %EVAL(1);
%DO %WHILE (&I<= &LIM_MSISDN);
%LET REF = %SCAN(LISTA_ID,&I,,'*');
DATA WORK.UP&REF;
SET WORK.BASE&REF;
FORMAT PERC_ACUM 9.3;
IF FIRST.ID_CLIENTE THEN PERC_ACUM=0;
PERC_ACUM+PERC;
RUN;
%LET I = %EVAL(&I+1);
%END;
%MEND;
%CICLO_TESTE;
the error was that:
VARIABLE PERC IS UNITIALIZED and
VARIABLE FIRST.ID_CLIENTE IS UNITIALIZED.
What I want is to run this macro for each one of the Id's in the List I showed before, and that are referenced in work.base&ref and work.up&ref.
How can I do it? What I'm doing wrong?
thanks!
Here's the CALL EXECUTE version.
%MACRO CICLO_teste(REF);
DATA WORK.UP&REF;
SET WORK.BASE&REF;
BY ID_CLIENTE;
FORMAT PERC_ACUM 9.3;
IF FIRST.ID_CLIENTE THEN PERC_ACUM=0;
PERC_ACUM+PERC;
RUN;
%CICLO_TESTE;
DATA _NULL_;
SET amostra;
*CREATE YOUR MACRO CALL;
STR = CATT('%CLIO_TESTE(', ID, ')');
CALL EXECUTE(STR);
RUN;
First you should note that SAS macro variable resolve is intrinsically a "text-based" copy-paste action. That is, all the user-defined macro variables are texts. Therefore, %eval is unnecessary in this case.
Other miscellaneous corrections include:
Check the %scan() function for correct usage. The first argument should be a text string WITHOUT QUOTES.
run is redundant in proc sql since each sql statement is run as soon as they are sent. Use quit; to exit proc sql.
A semicolon is not required for macro call (causes unexpected problems sometimes).
use %do %to for loops
The code below should work.
data work.amostra;
input id;
cards;
1
4
7
10
;
run;
proc sql noprint;
select id into :lista_id separated by ' ' from work.amostra;
select count(*) into :nr separated by ' ' from work.amostra;
quit;
* check;
%put lista_id=&lista_id nr=&nr;
%macro ciclo_teste();
%local ref;
%do i = 1 %to &nr;
%let ref = %scan(&lista_id, &i);
%*check;
%put ref = &ref;
/* your task below */
/* data work.up&ref;*/
/* set work.base&ref;*/
/* format perc_acum 9.3;*/
/* if first.id_cliente then perc_acum=0;*/
/* perc_acum + perc;*/
/* run; */
%end;
%mend;
%ciclo_teste()
tested on SAS 9.4 win7 x64
Edited:
In fact I would recommend doing this to avoid scanning a long string which is inefficient.
%macro tester();
/* get the number of obs (a more efficient way) */
%local NN;
proc sql noprint;
select nobs into :NN
from dictionary.tables
where upcase(libname) = 'WORK'
and upcase(memname) = 'AMOSTRA';
quit;
/* assign &ref by random access */
%do i = 1 %to &NN;
data _null_;
a = &i;
set work.amostra point=a;
call symputx('ref',id,'L');
stop;
run;
%*check;
%put ref = &ref;
/* your task below */
%end;
%mend;
%tester()
Please let me know if you have further questions.
Wow that seems like a lot of work. Why not just do the following:
data work.amostra;
input id;
cards;
1
4
7
10
;
run;
%macro test001;
proc sql noprint;
select count(*) into: cnt
from amostra;
quit;
%let cnt = &cnt;
proc sql noprint;
select id into: x1 - :x&cnt
from amostra;
quit;
%do i = 1 %to &cnt;
%let x&i = &&x&i;
%put &&x&i;
%end;
%mend test001;
%test001;
now in variables &x1 - &&x&cnt you have your values and you can process them however you like.
In general if your list is small enough (macro variables are limited to 64K characters) then you are better off passing the list in a single delimited macro variable instead of multiple macro variables.Remember that PROC SQL will automatically set the count into the macro variable SQLOBS so there is no need to run the query twice. Or you can use %sysfunc(countw()) to count the number of entries in your delimited list.
proc sql noprint ;
select id into :idlist separated by '|' from .... ;
%let nr=&sqlobs;
quit;
...
%do i=1 %to &nr ;
%let id=%scan(&idlist,&i,|);
data up&id ;
...
%end;
If you do generate multiple macro variables there is no need to set the upper bound in advance as SAS will only create the number of macro variables it needs based on the number of observations returned by the query.
select id into :idval1 - from ... ;
%let nr=&sqlobs;
If you are using an older version of SAS the you need set an upper bound on the macro variable range.
select id into :idval1 - :idval99999 from ... ;

Setting Macro Variable equal to value in table

I have a table with one row and 4 columns. I would like to create 4 macro variables named after each column where the value is set to the value in the 1 row.
If this were R I could access the values directly with something like:
newvar1=tablename[1,1]
newvar2=tablename[1,2]...
Is there anyway for me to select values from a table and set macrovariables equal to that value?
So something like:
%macrovar1=tablename[1,1]...
Except obviously the right side of the equals sign is R code not SAS.
Thanks
You can use proc sql to do it like so:
proc sql noprint inobs=1;
select name into :my_val from sashelp.class;
quit;
%put &my_val;
Or you can use call symput from the datastep like this:
data _null_;
set sashelp.class(obs=1);
call symput('my_val',name);
run;
For something more flexibile, we use a utility macro that allows us to check values from anywhere in our code. I've modified it slightly to accommodate your request but the usage will be like this:
%let my_val = %get_val(iDs=sashelp.class, iField=name);
You could also use it in the middle of a proc or a datastep like so:
data _null_;
my_value = "%get_val(iDs=sashelp.class, iField=name)";
run;
Or even:
proc sql noprint;
create table want as
select * from sashelp.class
where name = "%get_val(iDs=sashelp.class, iField=name)"
;
quit;
Here is the macro definition:
%macro get_val(iDs=, iField=);
%local dsid pos rc result cnt value;
%let result=;
%let cnt=0;
/*
** ENSURE ALL THE REQUIRED PARAMETERS WERE PASSED IN.
*/
%if "&iDs" ne "" and "&iField" ne "" %then %do;
%let dsid=%sysfunc(open(&iDs,i));
%if &dsid %then %do;
%let pos=%sysfunc(varnum(&dsid,&iField));
%if &pos %then %do;
%let rc=%sysfunc(fetch(&dsid));
%if "%sysfunc(vartype(&dsid,&pos))" = "C" %then %do;
%let value = %qsysfunc(getvarc(&dsid,&pos));
%if "%trim(&value)" ne "" %then %do;
%let value = %qtrim(&value);
%end;
%end;
%else %do;
%let value = %sysfunc(getvarn(&dsid,&pos));
%end;
&value
%end;
%else %do;
%put ERROR: MACRO.GET_VAL.SAS: FIELD &iField NOT FOUND IN DATASET %upcase(&iDs).;
%end;
%end;
%else %do;
%put ERROR: MACRO.GET_VAL.SAS: DATASET %upcase(&iDs) COULD NOT BE OPENED.;
%end;
%let rc=%sysfunc(close(&dsid));
%end;
%else %do;
%put ERROR: MACRO.GET_VAL.SAS: YOU MUST SPECIFY BOTH THE IDS AND IFIELD PARAMETERS TO CALL THIS MACRO.;
%end;
%mend;
The above macro is an abbreviated version of the %ds2list macro found here.
There are several ways, but the simplest is:
data _null_;
set have;
if _n_=1 then do;
call symputx('macrovar1',var1);
*more of these;
end;
stop;
run;
That opens the dataset, then if on first row (_n_ is iteration of data step loop, which in most cases is the first row), use call symputx to assign its value to a macro variable.
I would note that you should remember that SAS macro variables are not data variables, do not have a data type (always are text), and usually are not used to store data the way you would use vectors in R.

How to loop through a macro variable in SAS

I have an example like this:
proc sql;
select dealno into :deal_no
from deal_table;
Now I want to traverse the variable deal_no now containing all dealno in table deal_table but I don't know how to do it.
Another option is add 'separated by' to the sql code, which will add a delimiter to the values. You can then use the SCAN function in a data step or %SCAN in a macro to loop through the values and perform whatever task you want. Example below.
proc sql noprint;
select age into :age separated by ','
from sashelp.class;
quit;
%put &age.;
data test;
do i=1 by 1 while(scan("&age.",i) ne '');
age=scan("&age.",i);
output;
end;
drop i;
run;
If you do
%put &deal_no;
you can see that it only contains the first value of dealno, not all of them.
To avoid that you can do something like this:
proc sql;
create table counter as select dealno from deal_table;
select dealno into :deal_no_1 - :deal_no_&sqlobs
from deal_table;
quit;
%let N = &sqlobs;
%macro loop;
%do i = 1 %to &N;
%put &&deal_no_&i;
%end;
%mend;
%loop; run;
Here's another solution.
proc sql noprint;
select age into :ageVals separated by ' '
from ageData;
quit;
%put &ageVals;
%macro loopAgeVals; %let i = 1; %let ageVal = %scan(&ageVals, &i);
%do %while("&ageVal" ~= "");
%put &ageVal;
%let i = %eval(&i + 1);
%let ageVal = %scan(&ageVals, &i);
%end;
%mend;
%loopAgeVals;