data freq;;
input placebo ;
cards;
3
;
Run;
data freqt;
set freq;
%macro freq1 (arm=,pct=,result=);
%if &arm ne . %then &pct=&arm*100;
%if &pct ne . %then %do;
%if &pct le 10 %then &result = "test";
%end;
%mend freq1;
%freq1(arm=placebo,pct=pct_pla,result=placebo_);
run;
Above data step macro If then conditions are not working but normal condition is working but don't need normal condition.
Would like use only macro condition. Please help me.
Thank you...
Hard to tell what you want to do. But to develop a macro you need to first start with working SAS code. Perhaps something like:
data freqt;
set freq;
if placebo ne . then pct_placebo=placebo*100;
if . <= pct_placebo <= 10 then placebo_= "test";
run;
Then replace the parts that vary with macro variable references.
%let arm=placebo;
...
if &arm ne . then pct_&arm=&arm*100;
if . <= pct_&arm <= 10 then &arm._= "test";
Then define a macro.
%macro freq(arm);
if &arm ne . then pct_&arm=&arm*100;
if . <= pct_&arm <= 10 then &arm._= "test";
%mend freq;
Then use the macro. You probably will want to use it multiple times otherwise it wasn't worth the trouble of making a macro to begin with.
data freqt;
set freq;
%freq(placebo)
%freq(drug1)
%freq(drug2)
run;
You're confusing macro syntax with data step syntax. In macro syntax, you can refer only to the contents of the macro variable - not the data step variable. So if &arm contains the variable name placebo, then your condition
%if &arm ne . %then ...
tests whether the text placebo equals . . Of course it does not - so that's always false.
However, this works:
if &arm ne . then ...
Because now you're writing data step syntax, which allows you to access the data step variable placebo. So now you compare 3 to . (or whatever is in it).
You have some other issues; the macro definition shouldn't be inside the data step (it's possible to put it there, but it's sort of nonsense to do so) and of course your pct logic has the same issues.
Related
Could someone please explain why it is happening?
I'm trying to find objects dependencies tree in a DB.
Let's say view5 is a view sits on top view4 which sits on top view1.
Also,
view3 sits on top view2 sits on top view1.
So,
the when I query the macro for view1, I should get back view4, view5, view2 and view3.
This is the macro:
%macro dependencies(obj=);
%let dependent_objectname =;
proc sql noprint;
select "'"||trim(dependent_objectname)||"'"
into :dependent_objectname separated by ", "
from &_input.
where src_objectname in (&obj.);
quit;
%put &dependent_objectname.;
%let dependent_objectname = (&dependent_objectname.);
%put &dependent_objectname.;
%if %length("&dependent_objectname")>0 %then
%dependencies(obj = &dependent_objectname.);
%mend dependencies;
%let source = 'ditemp.depend_test1';
%put &source.;
%dependencies(obj = &source.);
First iteration works well,
I get the objects sit on top depend_test1
in a form of "('ditemp.depend_test2','ditemp.depend_test3')"
then I'm checking for the length of variable dependent_objectname (greater than zero)
and calling the macro again,
only it never stops...
I see a couple problems.
The statement:
%if %length("&dependent_objectname")>0 %then %do;
will always return true, even if the value of &dependent_objectname is null. Because the quotes are part of the value in the macro language. You probably want:
%if %length(&dependent_objectname)>0 %then %do;
That test for nullness usually works. Or see this paper for better methods. http://support.sas.com/resources/papers/proceedings09/022-2009.pdf
Before that, the statement:
%let dependent_objectname = (&dependent_objectname.);
is adding parentheses to your value. So again, even if &dependent_objectname were null, it would be () after this. It looks like you don't need these parentheses, so I would skip this statement.
I would also add:
%local dependent_objectname ;
to the top of the macro. That way each invocation of the macro will have its own local macro variable, rather than having them all use the macro variable created in the first iteration (or worse yet, all use a global macro variable).
You have sensibly added %PUT statements to help with debugging. I would expect they would show that the value of &dependent_objectname is always non-null as currently written. You could also add:
%put The length is: %length(&dependent_objectname.) ;
Since you are using an SQL query to generate the dependent list you can use the automatic variable SQLOBS in your test to break the recursion.
%if &sqlobs %then %do;
%dependencies(obj = &dependent_objectname.);
%end;
Also do NOT use a comma as the delimiter between the items listed in the OBJ parameter. The IN operator in SAS doesn't need them and they will cause trouble in the macro call.
select * from sashelp.class where name in ('Alfred' 'Alice') ;
So your macro could look like this:
%macro dependencies(object_list);
%local dependent_list ;
proc sql noprint;
select catq('1as',dependent_objectname)
into :dependent_list separated by ' '
from &_input.
where src_objectname in (&object_list)
and dependent_objectname is not null
;
quit;
%put Dependent Objects of (&object_list) = (&dependent_list);
%if &sqlobs %then %dependencies(&dependent_list);
%mend dependencies;
And here is a test case.
%let _input=sample;
data sample;
length src_objectname dependent_objectname $41 ;
input (_all_) (:) ;
cards;
object1 object2
object2 object3
object2 object4
;;;;
%dependencies('object1');
This is my first macro, so my apologies if I missed something simple.
I need to do the same data step six (or more) times and append each one to the first, so I tried a do-loop within a macro. Everything works with the loop removed, but once the do-loop is added, I get errors that either say I have an extra %end or an extraneous %mend. All ideas welcome. Thanks!
%macro freeze_samples(orig_file=, samples= , Start_Freeze_Incr=,
End_Freeze_Incr= );
%do i = 1 %to &samples;
data freeze_slice_&i;
set &orig_file;
(do stuff)
run;
* If we have more than one slice, append to previous slice(s).;
%if &i > 1 %then %do;
proc append base = temp_1 data = temp_&i;
run;
%end;
%end;
%mend;
I think you either have a problem you didn't include in the text (ie, in the 'do stuff' section) or you have a bad session (ie, you fixed the problem but there's something from a previous run messing up something now). This runs fine (given I don't know what you're doing):
%macro freeze_samples(orig_file=, samples= , Start_Freeze_Incr=,
End_Freeze_Incr= );
%do i = 1 %to &samples;
data freeze_slice_&i;
set &orig_file;
*(do stuff);
run;
* If we have more than one slice, append to previous slice(s).;
%if &i > 1 %then %do;
proc append base = freeze_slice_1 data = freeze_slice_&i;
run;
%end;
%end;
%mend;
%freeze_samples(orig_file=sashelp.class,samples=2,start_freeze_incr=1,end_freeze_incr=5);
I would note that you're probably better off not doing whatever you're doing this way; in SAS, there is usually a better way than splitting data off into multiple datasets. But since I don't know what you're doing I can't really suggest the better way beyond recommending reading this article and keeping it in mind (even if you're doing something different than bootstrapping, the concept applies to almost everything in 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.
I write a macro sentence in SAS :
%macro loop;
%let sigmav=0.1;
.....
%let sigmav_new=std(V);
%if abs(%eval(&sigmav-&sigmav_new))<0.001 %then %do;
......
%mend;
But there are always errors of abs( ) and std( ). So I wonder whether there are special rules to express the function of abs() and std() in a macro. Hope for your help!
If you want to call a SAS function in a macro (and outside of a data step), you need to enclose it in %sysfunc().
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;