BookmarkSubscribeRSS Feed
trevand
Obsidian | Level 7

To create indicators that equal to 1 I have to check if 20 variables have certain strings. So for instance if the variables VAR_1 through VAR_2 have "A1" "A2" "A3" I want to create testA=1. I am trying the following code but it doesn't work.

 

%let testA=("A1" "A2" "A3");
%let testB=("B1" "B2" "B3");


data NEED;
set HAVE;

%let var_list = TESTA TESTB;
%local i var;
%do i=1 %to %sysfunc(countw(&var_list.));
%let var = %scan(&var_list., &i.);

array cond {20} VAR_1-VAR_20;
do i=1 to dim (cond);
do j=1 to countw('&&&var..', ',');
if (cond {i} in &&&var..) the do;
&var.=1;
end;
end;
end;

%end;
%mend;
%var_list;

run;
23 REPLIES 23
PaigeMiller
Diamond | Level 26

First, you have not shown us a macro, because there is no %MACRO statement. So that needs to be corrected.

 

When code doesn't work, saying "it doesn't work" and not providing more details is never helpful. We need to see the evidence that it doesn't work, that evidence is likely in the SAS log.

 

When you are trying to debug a macro, turn on the macro debugging command by running this line of code 

 

options mprint;

 

and then run your macro again, and look at the log. See if you can figure out from the log where the problem is and how to fix it. If you still need help, show us the ENTIRE log by copying the log as text and pasting it into the window that appears when you click on the </> icon. DO NOT SKIP THIS STEP.

PaigeMiller_0-1699900743276.png

 

 Why is the title "macro within macro" when your code doesn't show anything like that?

--
Paige Miller
Astounding
PROC Star

A general principle:  get your code working without macro language.  Once that is complete, convert your code to a macro.  Looking at your program, a couple of things would stand out.  

The letter "n" is missing from the word "then":  "the do"

The IN operator is being used incorrectly.  A single value can precede the word IN, with a list of values following the word IN.  

It is unlikely you want to include parentheses within your list of values, as would happen in your program.

Also note (this could change the way you structure your program), following the IN operator you are allowed to use an array reference

Good luck.

trevand
Obsidian | Level 7

@PaigeMiller  @Astounding I can't copy code of the environment. So typed up the code and had typos here is the correct code:

 

%let testA=("A1" "A2" "A3");
%let testB=("B1" "B2" "B3");


data NEED;
set HAVE;

%macro var_list;

%let var_list = TESTA TESTB;
%local i var;
%do i=1 %to %sysfunc(countw(&var_list.));
%let var = %scan(&var_list., &i.);

array cond {20} VAR_1-VAR_20;
do i=1 to dim (cond);
do j=1 to countw('&&&var..', ',');
if (cond {i} in &&&var..) then do;
&var.=1;
end;
end;
end;

%end;
%mend;
%var_list;

run;

I get the following error: ERROR 124-185: The variable cond has already been defined

PaigeMiller
Diamond | Level 26

Okay, problem #1: Do not define a macro beginning with %MACRO and ending with %MEND inside a data step. Define the macro outside (before) the data step and then call it within the data step.

 

Problem #2, and agreeing with @Astounding , you absolutely (as in MUST, not optional) get the code to work properly in one situation, without macros and without macro variables. If the code doesn't do what you want without macros and without macro variables, then you will NEVER be able to get it to work with macros and with macro variables.  So do that, if you are having trouble doing this, show us the code you tried that doesn't work properly WITHOUT macros and WITHOUT macro variables. And if you get it to work without macros and without macro variables, then we can help you turn it into a macro.

--
Paige Miller
trevand
Obsidian | Level 7

@PaigeMiller why do you have to start and end the macro outside of data step?You mean this:

 

%macro var_list;

data need;
set have;
run;

%mend
&var_list;
PaigeMiller
Diamond | Level 26

I said  "Define the macro outside (before) the data step and then call it within the data step." So, %MACRO all the way down to %MEND is before the data step.

 

Why? Because you have to define a macro (starting with %MACRO and then some code and ending with %MEND) before you can call it. Then you can do this:

 

data need;
    set have;
    %var_list
run;

 

In this way, the macro code and the data step code will work together properly, if the macro %VAR_LIST is written properly. You can't combine SAS tools (like macros and data steps) willy-nilly without regards to how they work, and expect them to work. What does happen when you execute macro code? It replaces the macro with the code inside the macro, and you CANNOT do things that are not allowed in a data step, here is an example of something that is not allowed in a data step and so won't work, even if it is called by a macro.

 

data want;
    set have;
    twas brillig = and the slithy toves;
run;

 

 

This won't work because you cannot have spaces between variables named TWAS BRILLIG on the left side of the equal sign, you can't even have two variable names on the left side; and similarly the space on the right side will cause an error.

 

But again, I consider it mandatory that you write code that works and does what you want without macros and without macro variables for one instance of this problem you are trying to solve. MANDATORY. You will never make any progress otherwise. Now three people have told you to do this, I expect you will give that a try rather than continuing to attempt to write macros without that step.

--
Paige Miller
Kurt_Bremser
Super User

Your definition of cond is inside the %DO loop, so the ARRAY statement will be repeatedly created, causing the ERROR.

But that's just the obvious thing. You MUST start your macro development with working non-macro code for a single instance. Then decide what must be repeated and therefore created dynamically, and create the macro for that.

trevand
Obsidian | Level 7

I figured it out. Below it the code that works. 

 

%let testA=("A1" "A2" "A3");
%let testB=("B1" "B2" "B3");


data NEED;
set HAVE;

%macro var_list;

%let var_list = TESTA TESTB;
%local i var;
%do i=1 %to %sysfunc(countw(&var_list.));
%let var = %scan(&var_list., &i.);

array cond_&var. {20} VAR_1-VAR_20;
do i=1 to dim (cond_&var.);
do j=1 to countw('&&&var..', ',');
if (cond_&var.{i} in &&&var..) then do;
&var.=1;
end;
end;
end;

%end;
%mend;
%var_list;

run;

The code works even if you start the %macro inside the data step. So I'm not sure about your point @PaigeMiller. Could you please provide an example when it matters when you start and end the macro and maybe also the code. I'm still confused where you would put 

%macro var_list;

%end;
%mend;
%var_list;

 

PaigeMiller
Diamond | Level 26

Off the top of my head, I don't have an example of why you shouldn't begin a macro definition inside a data step. If I think of one I will let you know, or maybe the others can jump in with an example. And I never said it can't work, I said (or implied) that it is considered a poor practice and should be avoided.

 

It seems that you have learned all the wrong lessons here. You appear to have learned that either through brute force or sheer luck or both, you can make this macro work. This is not a good lesson to carry forward, and we have tried to steer you into a better approach, which requires neither brute force nor sheer luck. There's a reason why people like @Kurt_Bremser and @Astounding (and me) recommend a different approach, and this is because it is less effort and more likely to succeed. 

 

So, it's not that your way will never work; it's that it is inefficient and has a higher probability of failure.

--
Paige Miller
trevand
Obsidian | Level 7

@PaigeMiller oh I did use the advice of you, @Kurt_Bremser, and @Astounding gave me. I removed the macro and wrote the code for two cases without the macro. This is how I realized what I was missing.

Tom
Super User Tom
Super User

The main reason to define the macro BEFORE the data step is to avoid confusing yourself.

 

The macro processor interprets the text of your program and the results are passed onto to the actual SAS language processor to operate.  By placing the macro definition inside the DATE step it gives the programmer the false impression that the macro is compiled while the data step is running.  

 

The other reason is it make the code easier to read.

 

So first let's move the macro definition to the first thing in your program.

%macro var_list;
%local i var varlist;
%let var_list = TESTA TESTB;
%do i=1 %to %sysfunc(countw(&var_list.));
  %let var = %scan(&var_list., &i.);
array cond_&var. {20} VAR_1-VAR_20;
do i=1 to dim (cond_&var.);
  do j=1 to countw('&&&var..', ',');
    if (cond_&var.{i} in &&&var..) then do;
      &var.=1;
    end;
  end;
end;
%end;
%mend ;

%let testA=("A1" "A2" "A3");
%let testB=("B1" "B2" "B3");

data NEED;
  set HAVE;
%var_list;
run;

Notice how I added VAR_LIST to your list of LOCAL macro variables that the macro is defining and using while it executes.

 

Now let's try to understand what the macro code is doing.  One thing that looks wrong is the use of macro variable references inside of a string quoted with single quotes.  Such strings are ignored by the macro processor. So this DO loop 

do j=1 to countw('&&&var..', ',');

will iterate only one time since there are no commas in the string passed as the first argument to the COUNTW() function.

 

And why are you defining multiple arrays that contain the same set of variables?

MPRINT(VAR_LIST): array cond_TESTA {20} VAR_1-VAR_20;
...
MPRINT(VAR_LIST): array cond_TESTB {20} VAR_1-VAR_20;

 

Can you explain in words what you are trying to do?

trevand
Obsidian | Level 7

@Tom I have 20 variables that contain different strings. I need to create a lot of different indicator variables depending on combinations of strings in those 20 variables. For instance if any of the 20 variables contain the strings A1, A2, and A3 the indicator testA should equal to 1 and 0 otherwise. If any of the 20 variables contain the strings B1, B2, and B3 the indicator testB should equal to 1 and 0 otherwise.  

Tom
Super User Tom
Super User

So you want to do something like this?

data have;
  row+1;
  input (var_1-var_4) ($);
cards;
A1 C1 .  .
D1 C2 B3 .
. . . .
;

data want;
  set have;
  array var_[4];
  do i=1 to dim(var_) until(testA);
    testA = var_[i] in ('A1' 'A2' 'A3');
  end;
  do i=1 to dim(var_) until(testB);
    testB = var_[i] in ('B1' 'B2' 'B3');
  end;
  drop i;
run;

Where the only part that varies are the DO loops at the end.

You could make a macro that just generates the DO loops.

%macro dx_test(names,codelists);
%local i name codelist ;
%do i=1 %to %sysfunc(countw(&names,%str( )));
  %let name=%scan(&names,&i,%str( ));
  %let codelist=%scan(&codelist,&i,|);
do i=1 to dim(var_) until(&name);
  &name = var_[i] in (&codelist);
end;
%end;
%mend;

Then call it like this:

data want;
  set have;
  array var_[4];
%dx_test(names=testA testB,codelists='A1' 'A2' 'A3'|'B1' 'B2' 'B3')
  drop i;
run;

But does that really save you any typing or make the code easier to follow?

Tom
Super User Tom
Super User

Probably even better to make a really simple macro:

%macro dx_test(name,codelist);
do i=1 to dim(var_) until(&name);
  &name = var_[i] in (&codelist);
end;
%mend;

And just call it multiple times.

data want;
  set have;
  array var_[4];
%dx_test(testA,'A1' 'A2' 'A3')
%dx_test(testB,'B1' 'B2' 'B3')
  drop i;
run;

hackathon24-white-horiz.png

The 2025 SAS Hackathon Kicks Off on June 11!

Watch the live Hackathon Kickoff to get all the essential information about the SAS Hackathon—including how to join, how to participate, and expert tips for success.

YouTube LinkedIn

Mastering the WHERE Clause in PROC SQL

SAS' Charu Shankar shares her PROC SQL expertise by showing you how to master the WHERE clause using real winter weather data.

Find more tutorials on the SAS Users YouTube channel.

Discussion stats
  • 23 replies
  • 4541 views
  • 3 likes
  • 5 in conversation