"For in" loop equivalent in SAS 9.3 - sas

I'm searching for a while an equivalent of the for in loop (like in Python or in R) in SAS 9.3 macro language. The DO loop seem's to be the solution but did't work exactly as I want.
I founded a way to do it in a data step with a DO loop but it don't work with the macro language.
For example, in a data step, this code is working :
DATA _NULL_;
DO i = 1,3,5,9;
PUT i;
END;
RUN;
And then the log prompt as expected :
1
3
5
9
When I try to do the same with an %DO loop in a Macro, I have an error.
%MACRO test();
%DO i = 1,2,4,9 ;
%PUT i = &i;
%END;
%MEND;
%test();
The log promp these messages :
ERROR: Expected %TO not found in %DO statement.
ERROR: A dummy macro will be compiled
I'm quite new in SAS and stackoverflow so I hope my question is no too stupid. It's so simple to do this in Python and R then it must have a simple way to do it in SAS.
Thank's for help - J. Muller

The closest I've ever come across to this pattern in SAS macro language is this:
%MACRO test();
%let j=1;
%let vals=1 2 4 9;
%do %while(%scan(&vals,&j) ne );
%let i=%scan(&vals, &j);
%put &i;
%let j=%eval(&j+1);
%end;
%MEND;
%test();
(Warning: untested, as I no longer have a SAS installation I can test this out on.)

You can certainly get around it this way:
options mindelimiter=,;
options minoperator;
%MACRO test();
%DO i = 1 %to 9 ;
%if &i in (1,2,4,9) %then %do;
%PUT i = &i;
%END;
%end;
%MEND;
%test();
However, I think you can usually avoid this sort of call by executing your macro multiple times rather than attempting to control the loop inside the macro. For example, imagine a dataset and a macro:
data have;
input x;
datalines;
1
2
4
9
;;;;
run;
%macro test(x);
%put &x;
%mend test;
Now you want to call %test() once for each value in that list. Okay, easy to do.
proc sql;
select cats('%test(',x,')') into :testcall separated by ' ' from have;
quit;
&testcall;
That works just as well as your %do in loop, except it's data driven, meaning if you want to change the calls you just change the dataset (or if your data changes, the call automatically changes!). In general, SAS is more effective when designed as data driven programming rather than as entirely written code.

Related

Rename a external file in a SAS macro

I am getting a generic 'Statement not valid or out of order' message with the below:
%macro test;
data _null_;
%if %sysfunc(fileexist("C:\report_201809.xlsx")) = 1 %then %do;
rc=%sysfunc(rename("C:\report_201809.xlsx",
"C:\report_201809.xlsx"_old.xlsx",'file'));
%end;
%mend;
%test;
The code below should get you what you need. While you can use %if statements in a data step you generally won't need to. I'm guessing the error is coming from the %sysfunc function around the fileexist and rename functions. %sysfunc allows you to call data step functions outside of a data step so it is not needed here.
%macro test;
data _null_;
if fileexist("C:\file.txt") then do;
rc = rename("C:\file.txt", "C:\file2.txt", 'file');
end;
run;
%mend;
Alternatively, you could use an X Command that allows you to execute Windows commands. You could replace the rename function with the following statement.
x move C:\file.txt C:\file2.txt;
Remove the DATA _NULL_ or proceed per #J_Lard.
Macro arguments used in %sysfunc invoked function calls are implicitly quoted and do not need additional ' or "
%macro test;
%local rc;
%if %sysfunc(fileexist(C:\report_201809.xlsx)) = 1 %then %do;
%let rc = %sysfunc(rename(C:\report_201809.xlsx,C:\report_201809_old.xlsx,file));
%end;
%test;
You original code may have worked (by way of non-obvious side effect) if the filename "C:\report_201809.xlsx"_old.xlsx" (having an extraneous ") was corrected to "C:\report_201809_old.xlsx"

SAS Macros for plotting

I'm trying to make some plots. I need to make 19 scatter plots with the names in verf_typ_nmes substituted into the data and proc steps one at a time then executed. I've researched Google and thought I copied correctly but no good.
Errors I'm getting:
"Expected %TO not found in %DO statement"
ERROR: A dummy macro will be compiled.
%macro Multiplot();
%do verf_typ_nmes = BDPREP, BRCODE, CONTNT, COPAL, CURSRY,
DFTEST,DOCPST,DSMS,LABEL,MAILING,
MAILPC,MERLIN,MMS,MPCV,PRESRT,PVDS,
SHPAID,TAP,WEIGHT;
data &verf_typ_nmes;
set vol_verfns_prime;
where verif_type="&verf_typ_nmes";
RUN;
proc sgscatter data=&verf_typ_nmes;
plot verif_pstg*(t_v tfev mlgs fsp avpm);
run;
%end;
%mend;
The %DO statement is not as powerful as the DO statement.
Why not just use a BY statement?
proc sgscatter data=vol_verfns_prime;
by verif_type ;
where verif_type in ('BDPREP' 'BRCODE' ..... );
plot verif_pstg*(t_v tfev mlgs fsp avpm);
run;
If you do need to loop over a list of names then put the list in a macro variable and use %scan() to pull out the values one by one.
%let list=
BDPREP BRCODE CONTNT COPAL CURSRY DFTEST DOCPST DSMS LABEL
MAILING MAILPC MERLIN MMS MPCV PRESRT PVDS SHPAID TAP WEIGHT
;
%do i=1 %to %sysfunc(countw(&list));
...
where verif_type="%scan(&list,&i)";
...
%end;
Don't use comma as your delimiter in lists if you can avoid it. Commas will make it harder to use your list in function and macro calls.

sas macro loop to rename variable

Hi I am trying to rename variables using SAS Macro loop.
%Let t1=12Mth;
%Let t2=20;
%Let t3=30;
%Let t4=40;
%Let t5=50;
%Let t6=60;
%macro Re(time);
%Do I = 1 %to &time.;
data MilkNew;
set Milk;
rename MT&&t&I..Sp=MTSp&&t&I.;
run;
%end;
%mend Re;
%Re(6)
This loop is mean to rename MT...Sp to MTSp.... Eg:MT20SP to MTSp20.
When I run my loop, there was no error but the variable names were not changed in MilkNew at all.
Where does the problem come? Thanks!
If the only purpose of the macro is to rename the variables in the data set, then why read the data with a set statement. Your data set is probably really small so you don't even realize the inefficiency of doing that. Instead use the modify statement in proc datasets to accomplish the same thing, but more efficiently. Here's an alternative macro for you.
%macro renamevar(dsname, time);
%local lib ds i;
%let lib = %sysfunc(coalescec(%scan(&dsname, -2, %str(.)), work));
%let ds = %scan(&dsname, -1, %str(.));
proc datasets lib=&lib nolist;
modify &ds;
rename
%do i = 1 %to &time;
mt&&t&i..Sp=MTSp&&t&i.
%end;
;
quit;
%mend;
%renamevar(milk, 6);
Here's the log after the macro call:
NOTE: Renaming variable mt12MthSp to MTSp12Mth.
NOTE: Renaming variable mt20Sp to MTSp20.
NOTE: Renaming variable mt30Sp to MTSp30.
NOTE: Renaming variable mt40Sp to MTSp40.
NOTE: Renaming variable mt50Sp to MTSp50.
NOTE: Renaming variable mt60Sp to MTSp60.
NOTE: MODIFY was successful for WORK.MILK.DATA.
NOTE: PROCEDURE DATASETS used (Total process time):
real time 0.00 seconds
cpu time 0.01 seconds
You should move the loop so that it only generates just the RENAME statement (or even just the old=new name pairs). What is happening now is that you keep overwriting MilkNew so only the last RENAME has any effect.
%macro Re(time);
data MilkNew;
set Milk;
%do I = 1 %to &time.;
rename MT&&t&I..Sp=MTSp&&t&I.;
%end;
run;
%mend Re;
%Re(6)
You should have seen the last variable name in the loop (so the 6th) changed. That's because you repeated the same data step with the same source dataset but a different destination - so each time you 'forgot' the changes made in the earlier step.
So, this would've worked, though I'll get in a minute to why this isn't a good way to do this.
%Let t1=12Mth;
%Let t2=20;
%Let t3=30;
%Let t4=40;
%Let t5=50;
%Let t6=60;
%macro Re(time);
%Do I = 1 %to &time.;
data Milk;
set Milk;
rename MT&&t&I..Sp=MTSp&&t&I.;
run;
%end;
%mend Re;
data milk;
input
MT12mthSP
MT20SP
MT30SP
MT40SP
MT50SP
MT60SP
;
datalines;
12 20 30 40 50 60
;;;;
run;
%Re(6)
Here I had it make all changes to Milk and save them back in that dataset. If you want to preserve Milk then first make Milk_New then have that in both set and data statements.
Second, you should not do a new data step for each change. Macros don't have to have a data step in them; they can be run inside the datastep.
So for example:
%macro Re(time);
%Do I = 1 %to &time.;
rename MT&&t&I..Sp=MTSp&&t&I.;
%end;
%mend Re;
data milk_new;
set milk;
%Re(6);
run;
Even better would be generating this list outside of a macro entirely - look up "generating code SAS" for suggestions on that.
If you didn't see any renames at all, you also may have an issue where a label is present on the column(s). That won't affect your usage of the variable name, but it will make it confusing. Use
label _all_;
Or include a label-clearing statement (label <varname>; where you pop in the same variable name as the original variable name before rename) inside your macro loop to fix that.

Unmatched quotation mark SAS

%macro name_modal();
/*Create macro variables which contain the modalities of variables*/
%do i=1 %to &num_Var;
data _null_;
set &lib..Table_variable_modal_&i.;
call symputx('num_Mod'||compress(put(&i,4.)),_N_,"G");
call symputx('table'||compress(put(&i,4.))||'modal'||compress(put(_N_,4.)),compress(&&name_Var&i.),"G");
run;
%end;
/*Display modalities by variable*/
%do i=1 %to &num_Var;
%put &&name_Var&i. has &&num_Mod&i. modalities ;
%do j=1 %to &&num_Mod&i.;
%put %nrstr(&&tableb&i.modal&j.);
%end;
%end;
%mend name_modal;
%name_modal();
I hope the code is self-documenting.
I'll explain the problems here.
Everything is working fine until I pass to the second of the program which serves to display the modalities by variables.
For example when the name of modalities being stocked in the macro variables are like
$100% BLO,
100% COLOR,
AVON & RAGOBERT,
BALLANTINE'S,
L'OREAL,
AT&T,
U-V-A
etc
I fail to use %put properly.
I've tried using %bquote and %nrstr, but the problem persists.
So far, the only solution I can see is to modify the name of the modalities but since the names come from a client, I don't have the possibility to make amendment on the data.
Thank you
After trying a few times, I find %superq could solve this problem. Handling special characters in macro is troublesome. This page provides some useful tips on macro quoting.
I simplified your code here to the following
UPDATE: make it double-loop case.
data test;
input name ~ & $15.;
datalines;
100% BLO
100% COLOR
AVON & RAGOBERT
BALLANTINE'S
L'OREAL
AT&T
U-V-A
;
run;
%macro name_modal();
/*Create macro variables which contain the modalities of variables*/
%do i=1 %to 4;
data _null_;
set test;
call symputx('num_Mod1',_N_,"G");
call symputx('tableb'||compress(put(&i,4.))||'modal'||compress(put(_N_,4.)),name,"G");
run;
%end;
%do i=1 %to 4;
%do j=1 %to 7;
%put %superq(tableb&i.modal&j);
%end;
%end;
%mend name_modal;
%name_modal();
The result will display correctly.
Note that it is %superq(tableb&i.modal&j) not %superq(&&tableb&i.modal&j) as superq accepts macro variable name without extra ampersand.
It is hard to answer the question for all your sample data because I can't recreate your code (missing global macros).
[UPDATE] It is more difficult to get macros to resolve which include characters that need to be masked. Post some code that can be executed, and more help can be provided.
Here is a good link for macro quoting.
This has a really good diagram detailing which macro masking functions to use for different situations.
%Str will work even if a % or & are included in the string, as long as they are followed by a space. %NRSTR if either could be interpreted as a macro variable or macro call.
You can precede a single quote (') or single double quote (") with a percent sign (%), or use BQUOTE or NRBQUOTE (if your character string includes & or %).
Here is a start [UPDATE]:
%Macro Test(var=);
%Put &var;
%Mend test;
%Test(Var=%str($100% BLO)) ;
%Test(Var=%str(100% COLOR)) ;
%Test(Var=%nrstr(100 %COLOR)) ;
%Test(Var=%str(AVON & RAGOBERT)) ;
%Test(Var=%nrstr(AVON &RAGOBERT)) ;
%Test(Var=%str(BALLANTINE%'S)) ;
%Test(Var=%bquote(BALLANTINE'S)) ;
%Test(Var=%str(L%'OREAL)) ;
%Test(Var=%bquote(L'OREAL)) ;
%Test(Var=%nrstr(AT&T)) ;
%Test(Var=%str(U-V-A)) ;

How to detect how many observations in a dataset (or if it is empty), in SAS?

I wonder if there is a way of detecting whether a data set is empty, i.e. it has no observations.
Or in another saying, how to get the number of observations in a specific data set.
So that I can write an If statement to set some conditions.
Thanks.
It's easy with PROC SQL. Do a count and put the results in a macro variable.
proc sql noprint;
select count(*) into :observations from library.dataset;
quit;
There are lots of different ways, I tend to use a macro function with open() and attrn(). Below is a simple example that works great most of the time. If you are going to be dealing with data views or more complex situations like having a data set with records marked for deletion or active where clauses, then you might need more robust logic.
%macro nobs(ds);
%let DSID=%sysfunc(OPEN(&ds.,IN));
%let NOBS=%sysfunc(ATTRN(&DSID,NOBS));
%let RC=%sysfunc(CLOSE(&DSID));
&NOBS
%mend;
/* Here is an example */
%put %nobs(sashelp.class);
Here's the more complete example that #cmjohns was talking about. It will return 0 if it is empty, -1 if it is missing, and has options to handle deleted observations and where clauses (note that using a where clause can make the macro take a long time on very large datasets).
Usage Notes:
This macro will return the number of observations in a dataset. If the dataset does not exist then -1 will be returned. I would not recommend this for use with ODBC libnames, use it only against SAS tables.
Parameters:
iDs - The libname.dataset that you want to check.
iWhereClause (Optional) - A where clause to apply
iNobsType (Optional) - Either NOBS OR NLOBSF. See SASV9 documentation for descriptions.
Macro definition:
%macro nobs(iDs=, iWhereClause=1, iNobsType=nlobsf, iVerbose=1);
%local dsid nObs rc;
%if "&iWhereClause" eq "1" %then %do;
%let dsID = %sysfunc(open(&iDs));
%end;
%else %do;
%let dsID = %sysfunc(open(&iDs(where=(&iWhereClause))));
%end;
%if &dsID %then %do;
%let nObs = %sysfunc(attrn(&dsID,nlobsf));
%let rc = %sysfunc(close(&dsID));
%end;
%else %do;
%if &iVerbose %then %do;
%put WARNING: MACRO.NOBS.SAS: %sysfunc(sysmsg());
%end;
%let nObs = -1;
%end;
&nObs
%mend;
Example Usage:
%put %nobs(iDs=sashelp.class);
%put %nobs(iDs=sashelp.class, iWhereClause=height gt 60);
%put %nobs(iDs=this_dataset_doesnt_exist);
Results
19
12
-1
Installation
I recommend setting up a SAS autocall library and placing this macro in your autocall location.
Proc sql is not efficient when we have large dataset. Though using ATTRN is good method but this can accomplish within base sas, here is the efficient solution that can give number of obs of even billions of rows just by reading one row:
data DS1;
set DS nobs=i;
if _N_ =2 then stop;
No_of_obs=i;
run;
The trick is producing an output even when the dataset is empty.
data CountObs;
i=1;
set Dataset_to_Evaluate point=i nobs=j; * 'point' avoids review of full dataset*;
No_of_obs=j;
output; * Produces a value before "stop" interrupts processing *;
stop; * Needed whenever 'point' is used *;
keep No_of_obs;
run;
proc print data=CountObs;
run;
The above code is the simplest way I've found to produce the number of observations even when the dataset is empty. I've heard NOBS can be tricky, but the above can work for simple applications.
A slightly different approach:
proc contents data=library.dataset out=nobs;
run;
proc summary data=nobs nway;
class nobs;
var delobs;
output out=nobs_summ sum=;
run;
This will give you a dataset with one observation; the variable nobs has the value of number of observations in the dataset, even if it is 0.
I guess I am trying to reinvent the wheel here with so many answers already. But I do see some other methods trying to count from the actual dataset - this might take a long time for huge datasets. Here is a more efficient method:
proc sql;
select nlobs from sashelp.vtable where libname = "library" and memname="dataset";
quit;