BookmarkSubscribeRSS Feed
🔒 This topic is solved and locked. Need further help from the community? Please sign in and ask a new question.
Ubai
Quartz | Level 8

Hi everyone,

 

I'm not experienced with macros and I have two questions about them. I have a macro that runs Proc PHReg in a certain setting, and I would like to optimize how it works, so:

 

Question 1:

Can I add a macro variable as text, which would then run as a WHERE statement in the proc phreg. Example:

%MyPHReg (datain, where, time, censor, var);

Proc PHReg data=&datain;
&where.;
model &time. * &censor.(1) = &var.;
run;
%MEnd;

/* and to use the macro*/

%MyPHReg (mydata, Where X = 1, Survival, Censor, Smoking);

I have tried this but it didn't work. I know that macro variables should be variables, but can I integrate text?

 

Question 2:

I'm running a similar functioning macro for several data sets (here as &datain.) and several influence variables (here as &var.). Can I identify a matrix of data sets and variables and request the macro to run all possible combinations. Example:

 

/* something faster and more practicable instead of running the following */ 

%MyPHReg (dataset1, Survival, Censor, Age);
%MyPHReg (dataset1, Survival, Censor, Smoking);
%MyPHReg (dataset1, Survival, Censor, Alcohol);

%MyPHReg (dataset2, Survival, Censor, Age);
%MyPHReg (dataset2, Survival, Censor, Smoking);
%MyPHReg (dataset2, Survival, Censor, Alcohol);

%MyPHReg (dataset3, Survival, Censor, Age);
%MyPHReg (dataset3, Survival, Censor, Smoking);
%MyPHReg (dataset3, Survival, Censor, Alcohol);

Thanks

Ubai

 

 

1 ACCEPTED SOLUTION

Accepted Solutions
ballardw
Super User

You can use variables in a data step coupled with call execute to call your macro.

The example below shows using 3 dataset names and 3 variables to create calls as you indicate. If you are looping through more values for your "survival" and "censor" positioned parameters add loops similar to the ones I show and modify the Parmstr assignment code to reference those variables instead of the string literals. Define the lengths for those to be as long as needed. The length of Parmstr should be long enough to hold the macro call needed. Make it bigger if the test put shows it too short.

 

If the code is working creating the parmstr as shown in the log (option for the interested reader would be to Put the value to a text file and then %include that if a record of the code used is needed), then uncommenting the line with call execute should do what your are looking for.

 

data _null_;
   length dataset $ 43 vname $ 32 parmstr $ 200;
   do dataset= 'Dataset1','Dataset2','Dataset3';
         do vname= 'Age','Smoking','Alcohol';
         parmstr = catx(' ','%MyPHReg (', catx(',',dataset,'Survival','Censor',vname),');');
         /* to test if this is building the correct calling strings*/
         put parmstr; 
         /* when it is doing as needed uncomment the line below*/
         /* call execute(parmstr);*/
      end;
   end;
run;
      

DO NOT change the ' ' around the %Myphreg portion of the parmstr assignment code to double quotes, "   ", or the macro processor will attempt to run the macro and generate lots of errors.

 

View solution in original post

10 REPLIES 10
art297
Opal | Level 21

Answer to your first question: Yes! However, you have to start a macro definition with %macro whatevername; e.g.:

%macro MyPHReg (datain, where, var);

proc print data=&datain;
&where.;
var &var;
run;
%MEnd;

/* and to use the macro*/

%MyPHReg (sashelp.class, Where age = 14, name sex);

Art, CEO, AnalystFinder.com

art297
Opal | Level 21

As for the 2nd part of your question, you can use a combination of counters combined with %scan to incorporate any combination of multiple runs desired. e.g.:

%macro MyPHReg (datain, where, var);
  %let i=1;
  %do %while (%scan(&datain,&i,' ') ne );
    proc print data=%scan(&datain,&i,' ');
      &where.;
      var &var;
    run;
    %let i=&i+1;
  %end;
%MEnd;

/* and to use the macro*/

%MyPHReg (sashelp.class sashelp.class sashelp.class, Where age = 14, name sex);

Art, CEO, AnalystFinder.com

ballardw
Super User

You can use variables in a data step coupled with call execute to call your macro.

The example below shows using 3 dataset names and 3 variables to create calls as you indicate. If you are looping through more values for your "survival" and "censor" positioned parameters add loops similar to the ones I show and modify the Parmstr assignment code to reference those variables instead of the string literals. Define the lengths for those to be as long as needed. The length of Parmstr should be long enough to hold the macro call needed. Make it bigger if the test put shows it too short.

 

If the code is working creating the parmstr as shown in the log (option for the interested reader would be to Put the value to a text file and then %include that if a record of the code used is needed), then uncommenting the line with call execute should do what your are looking for.

 

data _null_;
   length dataset $ 43 vname $ 32 parmstr $ 200;
   do dataset= 'Dataset1','Dataset2','Dataset3';
         do vname= 'Age','Smoking','Alcohol';
         parmstr = catx(' ','%MyPHReg (', catx(',',dataset,'Survival','Censor',vname),');');
         /* to test if this is building the correct calling strings*/
         put parmstr; 
         /* when it is doing as needed uncomment the line below*/
         /* call execute(parmstr);*/
      end;
   end;
run;
      

DO NOT change the ' ' around the %Myphreg portion of the parmstr assignment code to double quotes, "   ", or the macro processor will attempt to run the macro and generate lots of errors.

 

Ubai
Quartz | Level 8

Thanks @art297 and @ballardw for your quick answers.

 

In the first question I did actually try the method However, for the first question I am still receiving errors.

ERROR: The keyword parameter VAR was not defined with the macro.

 

These only occur when I try to use a code that integrates the WHERE statement instead of writing it in the macro. Example:

 

%macro b (where);
proc phreg data=datain;
class sex;
where &where;
model time*censor(0) = sex;
run;
%mend;

%b (var=1);

***** instead of this one *****

%macro a (where);
proc phreg data=datain;
class sex;
&where;
model time*censor(0) = sex;
run;
%mend;

%a (where var=1);

 

 Any ideas?

 

For the second question the solution ballardw used is great, mainly because it allows me to use the method for variables and data names that don't end with numbers, or -as actually in my case- completely different. Thank you guys.

 

 

Tom
Super User Tom
Super User

If you call a macro with this syntax

%b(var=1)

You are telling it you want the parameter VAR to have the value 1.

If instead you want to set a value for the WHERE parameter then tell SAS that.

%b(where=var=1)

Note that you can always use named parameters in the macro call, even for a parameter that is defined as positional in the macro definition. You just can't try to pass parameters by position, ie without a name, if they are not defined as positional.

 

You could also add parentheses so that the value does not look like name= .

%b((var=1))

Having the extra parentheses in the value of WHERE should not cause any issue. 

art297
Opal | Level 21

Both methods will work but, for macro b, the compiler is confused upon seeing the = sign. Both of the following should work:

 

%macro b (where);
proc print data=sashelp.class;
where &where;
run;
%mend;

%b (age eq 14);


%macro a (where);
proc print data=sashelp.class;
&where;
run;
%mend;

%a (where age eq 14);

Art, CEO, AnalystFinder.com

 

Ubai
Quartz | Level 8

@ballardw

Actually I am looping more values for the "survival" and "censor" variables along with different predicting variables, but of course I would like a survival variable to go along with certain censoring variable. How can I make that possible within the data step you showed?

 

So to make it clear I have several data sets of the same structure, several predicting variables, several survival times for different outcomes and along with the survival times their corresponding censoring variables. I would like each survival time and censoring variables to go along together in such loop. How would you do that?

ballardw
Super User

@Ubai wrote:

@ballardw

Actually I am looping more values for the "survival" and "censor" variables along with different predicting variables, but of course I would like a survival variable to go along with certain censoring variable. How can I make that possible within the data step you showed?

 

So to make it clear I have several data sets of the same structure, several predicting variables, several survival times for different outcomes and along with the survival times their corresponding censoring variables. I would like each survival time and censoring variables to go along together in such loop. How would you do that?


You will have to show some examples of what you mean. If "survival" always has the same "censor" then a different loop structure would be needed such as loop Survival and then assign censor based on that value inside the loop.

If you have a complete list of survivals that have to cross with the censors such as in the previous example then add two more loop similar to the existing ones with the macro call in the middle of the whole mess.

You could have a dataset with variables holding value pairs for survival and censor and the first line of the code after data would SET that data set. Then the entire sett of loops get called for each record in the data set.

 

You would add the VARIABLES to the catx code instead of the quoted strings I used but that should be a simple change.

Ubai
Quartz | Level 8

Yes, each "survival" has only its own censor variable, so the variable TimeDeath has TimeDeathCens only and TimeMeta (for metastasis) has TimeMetaCens only. I would be meaningless to have TimeDeath and a censoring variable for other event. What loop structure would you suggest for this problem? How would you do it?

 

I actually came to this question because I wanted two additional loops to the code you provided and then I figured out that it would loop time-to-event with unrelated censoring variables.

ballardw
Super User

@Ubai wrote:

Yes, each "survival" has only its own censor variable, so the variable TimeDeath has TimeDeathCens only and TimeMeta (for metastasis) has TimeMetaCens only. I would be meaningless to have TimeDeath and a censoring variable for other event. What loop structure would you suggest for this problem? How would you do it?

 

I actually came to this question because I wanted two additional loops to the code you provided and then I figured out that it would loop time-to-event with unrelated censoring variables.


Again following the previous code example set lengths for the survival and censor variable names before use.

as an added loop using your examples above I would start with something like inside the VNAME loop.

Do survival = 'TimeDeath','TimeMeta';

   Censor= cats(survival,'Cens'); /* adds Cens to the end of the survival variable name*/

   /* change parmstr to:*/

     parmstr = catx(' ','%MyPHReg (', catx(',',dataset,Survival,Censor,vname),');');
     

 

End;

 

If your survival  and censor variable are not as easily built as the example above then make a data set with the two values needed and add a SET statement pointing to that data set immediately after the data _null_;

SAS Innovate 2025: Save the Date

 SAS Innovate 2025 is scheduled for May 6-9 in Orlando, FL. Sign up to be first to learn about the agenda and registration!

Save the date!

How to Concatenate Values

Learn how use the CAT functions in SAS to join values from multiple variables into a single value.

Find more tutorials on the SAS Users YouTube channel.

SAS Training: Just a Click Away

 Ready to level-up your skills? Choose your own adventure.

Browse our catalog!

Discussion stats
  • 10 replies
  • 1511 views
  • 6 likes
  • 4 in conversation