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

Hi everyone,

I have a Macro that performs a Do loop over a list of elements (numbers).
This do loop executes good for the first element. 

But strangerly, it ignores the rest of the list, and there is no error message displayed.

My question is the following : How do I rewrite my macro so that the Do loop performs for all elements of the list, not just the first one ?

I could not transpose solutions from the post : https://communities.sas.com/t5/SAS-Programming/Macro-loop-stuck-in-the-first-element/td-p/507723  My case is probably slighty different.


The situation : creating a table
with N deleted observations


I have the following situation :

- a dataset  "table_name" with 2 columns : "Time" and "Quantities"
- I want to create a new table "table_name_N" in which I delete the last N observations of "Quantities

- To make it easier, I created a 3rd column named "Back" that takes "Time" from the end (see below for utility)

For example, if N=4, we can compare my initial data and "table_name_4" :


Initial Data :

 

Time

Back

Quantities

1

50

100

2

49

100.4

...

...

 

46

5

108

47

4

108.3

48

3

108.5

49

2

108.9

50

1

109.4

 

 

New table :  "table_name_4"

 

 

Time

Back

Quantities

1

50

100

2

49

100.4

...

...

 

46

5

108

47

4

.

48

3

.

49

2

.

50

1

.

 

The goal :
dynamic creation of tables, whatever the number of tables

 

I would like a macro that performs this automatically, e.g. :
A) for any value of N
B) for any number of tables I want to create.

That is, if my macro takes as argument the list  "4 8 12", I should obtain 3 tables :

- a table             "table_name_4"             with 4 deleted observations

- a table             "table_name_8"             with 8 deleted observations

- a table             "table_name_12"             with 12 deleted observations

 

 


My code : what works

I have the code that performs A) and produces the table "table_name_4" or any kind of "table_name_N" :

 

            data WORK.Table_Name_4.;
            set WORK.Table_Name ;

/* We use the "Back" column to control how many observations we delete */


                        DO i = 1 TO 4 ; 
                              if Back=i then Quantities =. ;
                        END ; 

            run  ; 

 

 


My code : what does not work

 


The following code is what should generalize A)  so that I can perfom B)

Strangely enough, it does work for the first element of the list, and it does not produce error messages. But other elements of the list are ignored. I get the table  "table_name_4", but not the tables for 8 and 12 periods.

Code that does not work (1) : the Macro Definition

 

%macro Dynamic_Table_Creation (periods = ); 


/* periods is a list of numbers : 4 8 12 or 1 2 3 4, etc...) */
              


/* local macro-variables for the outer loop */ 

%local i next_element ;



/* outer loop : controls how many tables we create */

/* %sysfunc(countw(&periods.)) is the upper bound of the loop, it indicates how many distinct tables are created */

              %do i=1 %to %sysfunc(countw(&periods.));



              /* next_element contains the running number index */


              /* for the value taken by "next_element", we create a table whose suffix is "next_element" */

              %let next_element = %scan(&periods., &i.); 

                            data WORK.table_name_&next_element.; 
                            set WORK.table_name ;


                            /* inner loop */

                            /* We use the "Back" column to control how many observations we delete */

                            %DO i = 1 %TO %eval(&next_element.) ; 
                                          if Back=&i. then Quantities=.;
                            %END ; 
                            run ; 
              %end ; 



%mend ;



Code that does not work (2) : the Macro execution

 

 %Dynamic_table_creation (periods = 4 8 12) ;

/* 4 8 12  can be replaced with any other list of numbers, like 1 2 3 4 5 */



Now if I execute my macro it does good for the first element of my list.
That means "table_name_4" is created.

But strangely, the loop ignores the rest of the list (numbers 8 and 12).

How should I modify my code so that my loop perform for every element of the list ?
That is, so that my loop creates 3 tables ?


I work under SAS Enterprise Guide, and my version of SAS is SAS 9.1.3


I'm clearly a beginner for Data Management in SAS, so I'm sorry if this trivial... but I've been stuck on this single issue for days now and can't find what I did wrong.

Thanks in advance, any help would be greatly appreciated.

 

 

 

1 ACCEPTED SOLUTION

Accepted Solutions
Quentin
Super User

Hi, 

 

You have this loop:

%do i=1 %to %sysfunc(countw(&periods.));

And inside that loop you have another loop:

%DO i = 1 %TO %eval(&next_element.) ; 

You can't use the same variable, i, for both loops.  When you do, the inner loop will increment the value of i, so the outer loop only executes once.

 

You could change the inner loop to :

%DO j = 1 %TO %eval(&next_element.) ; 

And make related changes to create a local variable j, and use j in references on the inner loop.

 

 

The Boston Area SAS Users Group (BASUG) is hosting our in person SAS Blowout on Oct 18!
This full-day event in Cambridge, Mass features four presenters from SAS, presenting on a range of SAS 9 programming topics. Pre-registration by Oct 15 is required.
Full details and registration info at https://www.basug.org/events.

View solution in original post

6 REPLIES 6
Quentin
Super User

Hi, 

 

You have this loop:

%do i=1 %to %sysfunc(countw(&periods.));

And inside that loop you have another loop:

%DO i = 1 %TO %eval(&next_element.) ; 

You can't use the same variable, i, for both loops.  When you do, the inner loop will increment the value of i, so the outer loop only executes once.

 

You could change the inner loop to :

%DO j = 1 %TO %eval(&next_element.) ; 

And make related changes to create a local variable j, and use j in references on the inner loop.

 

 

The Boston Area SAS Users Group (BASUG) is hosting our in person SAS Blowout on Oct 18!
This full-day event in Cambridge, Mass features four presenters from SAS, presenting on a range of SAS 9 programming topics. Pre-registration by Oct 15 is required.
Full details and registration info at https://www.basug.org/events.
makemo
Obsidian | Level 7

It works !!!

Thank you very much !!!

Quick and good reply, perfect solution to my headaches.

yabwon
Onyx | Level 15

Hi,

 

won't something like this be ok?

data table_name;
do time = 1 to 50;
  back = 51-time;
  Quantities = 100 + ranuni(1);
  output;
end;
run;

%macro Dynamic_Table_Creation (periods = ); 
%local i next_element cntw;
%let cntw = %sysfunc(countw(&periods.));
  %do i=1 %to &CNTW.;
    %let next_element = %scan(&periods., &i.); 

    data WORK.table_name_&next_element.; 
    set WORK.table_name ;
     
      if Back <= &next_element. then Quantities=.;
    run ; 
  %end ; 
%mend ;

%Dynamic_Table_Creation(periods=1 2 3 7 13)

 

All the best

Bart

_______________
Polish SAS Users Group: www.polsug.com and communities.sas.com/polsug

"SAS Packages: the way to share" at SGF2020 Proceedings (the latest version), GitHub Repository, and YouTube Video.
Hands-on-Workshop: "Share your code with SAS Packages"
"My First SAS Package: A How-To" at SGF2021 Proceedings

SAS Ballot Ideas: one: SPF in SAS, two, and three
SAS Documentation



makemo
Obsidian | Level 7

Hi Yabwon,

Yes, I've tried it, and it does the work too !

It seems actually a cleaner and more efficient code, and less prone to errors, because the inner loop is not needed anymore. I will certainly adopt this code, seems just better overall.

Thanks a lot for the input, very well crafted.

Reeza
Super User
Your loop is fine, you forgot to end a comment before the data step is the primary issue.

This is all one comment in your code:

/* for the value taken by "next_element", we create a table whose suffix is "next_element"

%let next_element = %scan(&periods., &i.);

data WORK.table_name_&next_element.;
set WORK.table_name ;


/* inner loop */
makemo
Obsidian | Level 7

@Reeza 

Yes you're right, I forgot some "*/" . But this is only a mistake I made here when reproducing my code for communities.sas.  In my own code the commentaries are fine.

Thank you anyway for the input, I will be more careful next time.

SAS Innovate 2025: Call for Content

Are you ready for the spotlight? We're accepting content ideas for SAS Innovate 2025 to be held May 6-9 in Orlando, FL. The call is open until September 25. Read more here about why you should contribute and what is in it for you!

Submit your idea!

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.

Click image to register for webinarClick image to register for webinar

Classroom Training Available!

Select SAS Training centers are offering in-person courses. View upcoming courses for:

View all other training opportunities.

Discussion stats
  • 6 replies
  • 1200 views
  • 2 likes
  • 4 in conversation