BookmarkSubscribeRSS Feed
vishal_prof_gmail_com
Obsidian | Level 7

I have written a macro program in which Iam getting an error : Macro keyword appears as a text. I am getting same error for all macro keywords: DO, Let.

Can any body suggest what could be the reason for this error?

8 REPLIES 8
Amir
PROC Star

Hi,

Can you post the code to help diagnose the issue, please.

Or better still the log showing the code and the full error message.

There could be an issue with a missing semi colon or unbalanced quotes.

Regards,

Amir.

Message was edited by: Amir Malik - Added last two sentences.

vishal_prof_gmail_com
Obsidian | Level 7

Hi Amir,

I started a new session on EG. Now the macro compiles correctly.  I am attaching the code of the macro which splits a dataset in to user defined no. of datasets vertically. Can you suggest why I was getting this error initially.

%macro SplitDataset(dataset ,n);

proc contents data= &dataset varnum noprint out = temp1;

run;

%local variables;

%let ctr = 1;

data _null_;

set temp1 end = last;

if last then;

call symput('variables',_n_);

run;

%do k = 1 %to %eval(&variables);

%local TempVar&k.;

%end;

%if %sysfunc(mod(%eval(%eval(&variables)/%eval(&n)))) = 0

  %then %let Splitpoint = %eval(%eval(&variables)/%eval(&n));

  %else %let Splitpoint = %eval(%eval(&variables)/%eval(&n)+1);

%do i = 1 %to %eval(&n.);

proc sql;

select name into :Tempvar&i. - :Tempvar&&splitpoint&i. from temp1

where varnum >  %eval(&i.);

quit;

data Split&i.;

set &dataset. ;

keep %do j = %eval(&ctr.) %to %eval(&&splitpoint&i.);

  &&Tempvar&j

  %end;;

%let ctr = &j;

run;

%end;

%mend SplitDataset;

Thanks,

Vishal

data_null__
Jade | Level 19

I copied you macro and tried it but it does not work.  This is probably something like I might do.

%macro SplitDataset(dataset,n,out=split);
  
   proc contents data= &dataset varnum noprint out = temp1;
      run;
   proc sort data=temp1;
      by varnum;
      run;
   data temp1;
      set temp1;
      if mod(_n_,&n) eq
1 then group + 1;
      run;
   filename FT35F001 temp;
   data _null_;
      file FT35F001;
      put
'Data ';
      do until(eof);
         do until(last.group);
            set temp1 end=eof;
            by group;
            if first.group then put +
6 "&out" group '(keep=' @;
            put name @;
            end;
         put
')';
         end;
      put +
6 ';';
      stop;
      run;
  
%inc FT35F001 / nosource2;
      set &dataset;
      run;
   filename FT35F001 clear;
  
%mend SplitDataset;
options mprint=1;
%
splitdataset(sashelp.heart,5);
Astounding
PROC Star

When your own macro is too complicated for you to figure out how it is working, it is time to simplify.  Here are a few suggestions.

Start by adding:

%let n = %eval(&n);

In most cases, that is overkill.  It would only be necessary if the macro call gave &n a computed value such as 3+4.

Remove the extra semicolon and switch to SYMPUTX to remove extra blanks:

if last then call symputx('variables', _n_);

There are faster ways to get the number of observations in a data set, but you probably have a small enough data set that it doesn't matter.

Change your formula for &SplitPoint into a formula that a human can understand:

%let SplitPoint = %sysevalf(&variables/&n, ceil);

Add spacing and indentation that show where %DO groups begin and end.

These changes aren't necessarily going to solve the problem.  But they will put you in a better position to be able to solve it.

Good luck.

Vince28_Statcan
Quartz | Level 8

I see a few concerning issues in the code but it might just be that I don't fully seize your implementation logic.

The first one is that the MOD function requires 2 arguments and you are actually using the divisor instead of the comma to separate your 2 arguments.

the next one is that you have many %eval wraps around macro referencing. There might be some cases where this is useful but it often isn't and it makes the code far more dense to read and more difficult to catch things like the above. As Astounding mentionned, if you have spacing issues, you can use symputx or the proc sql ino statement (read doc, either var range always auto strip or single macro var name does I can't remember which) or you can use usual tricks such as

%let var=&var;

to strip blank spaces. %eval(&var) is typically only if you expect &var to contain an arithmetic equation.

you also double ref &&splitpoint&i yet you haven't created any macro variables by the names of splitpoint1, splitpoint2, ... etc¸.

your macro code only assigns a value to splitpoint so this will eventually try to ref &splitpoint1, &splitpoint2 etc after the 2nd pass of the macro processor on the line and cause issues.

you are also using

%let ctr = &j;

outside the loop over j. In a data step it works because loop doesn't reset the PDV and iterator variables are added to the pdv but I am not 100% sure that the macro variables defined for and by loops are scoped beyond the loop. They probably are to remain consistent with data step so this shouldn't cause an issue but it bugs me logically as really the last value of j is %eval(&&splitpoint&i.) which you should've used to increment your outer counter so-to-speak for better logic readability.

If you can state how exactly you want to "split" your data according to some existing variable in your data, it would help us greatly help you if you could word it out. It looks like you want a succession of a subset of the previous sets but since your MOD call fails in syntax and we have no clue of the values you are looking to MOD over, it's difficult to really grasp what you want to achieve.

Vince

vishal_prof_gmail_com
Obsidian | Level 7

Thanks for your comments. I have re -written the macro and now its working fine.. I think there is scope of optimizing the code and making it simpler.  I am attaching the code with the message if someone has any suggestions please let me know.

This macro takes as input name of data set and no. of data sets in which it should be split , then it calculates what should be the no. of variables in each data set and creates them accordingly.

%macro SplitDataset(dataset ,n);

proc contents data= &dataset varnum noprint out = temp1;

run;

%local variables; /* Macro variable to store no. of variables*/

%let ctr1 =1; /*Counter variable to split variables in different datasets*/

%local ctr2; /*Counter variable to split variables in different datasets*/

%local splitpoints; /* Counter to store no. of variables in each data set*/

%local current_splitpoint; /* Counter to last variable in a data set*/

/* Finding no. of variables*/

proc sql;

select count(*) into :variables from temp1;

quit;

/* Temporary variables to store variables in a data set*/

%do k = 1 %to %eval(&variables);

  %local TempVar&k.;

%end;

/* Determining the no. of variables in each data set*/

%let remainder =  %sysfunc(mod(%eval(&variables),%eval(&n)));

%if &remainder = 0

  %then %do;

       %let Splitpoints = %sysevalf(&variables/&n,floor);

       %let current_splitpoint = &splitpoints;

  %end;

  %else %do;

            %let Splitpoints= %eval(%sysevalf(&variables/&n,floor)+1);

            %let current_splitpoint = &splitpoints;

  %end;

%let ctr2 = &splitpoints;

/* Creating different Dats sets*/

%do i = 1 %to %eval(&n.);

       proc sql;

       select name into :Tempvar&ctr1. - :Tempvar&&ctr2. from temp1

       where varnum in (%do k= %eval(&ctr1) %to %eval(&ctr2-1);

                                &k,

                                %end;&ctr2) ;

      quit;

  data Split&i.(keep = %do j = %eval(&ctr1.) %to %eval(&ctr2.);

                                &&Tempvar&j

                                %end; ) ;

  set &dataset. ;

   run;

  %let ctr1 = %eval(&ctr2+1);

  %let current_splitpoint  = %eval(&current_splitpoint+&splitpoints);

  %if &i=%eval(&n-1)

  %then %do;

            %if &remainder = 0

            %then %let ctr2 = &current_splitpoint;

            %else %let ctr2 = &variables.;

            %end;

  %else %do;

                 %let ctr2 = &current_splitpoint;

            %end;

%end;

%mend SplitDataset;

data dataset;

input a b c d e f;

datalines;

1 2 3 4 3 5

2 3 4 5 5 6

6 7 8 9 6 7

;

%SplitDataset(dataset,2);

Vince28_Statcan
Quartz | Level 8

Ok, now looking at your test example,

I would suggest that you look into

sashelp.vcolumn (column dictionary)

using a proc sql select into statement on the vcolumn dictionary would allow you to read all the var names into variables directly (save proc content).

You could then use the &SQLOBS automatic macro variables to replace your select count(*) into statement, generated from the proc sql on sashelp.vcolumn to get the total number of variables and then use that to calculate how many datasets you ought to create.

finally, if you want to save tons of processing time, with the above done, you can write a single data step within your macro and use a combination of macro loops and dataset options (keep=) to create all the split sets within a single pass on your original data

something like

%macro SplitDataset(libname, dataset ,n);

proc sql;

     select name

     into :varname1-:varname999999 /* generic max, only &SQLOBS such macro vars will be created as needed */

     from sashelp.vcolumn

     where memname=upcase(&dataset.)

         %if &libname. NE  %then %do;

           and libname=upcase(&libname.)

          %end;

     ;

quit;

data

     %do i=1 %to &n.;

          split&i.(keep=(%do j=i %to &SQLOBS %by &n.

                                   &&varname&j..

                                   %end;

                    ))

     %end;

     ;

     set &dataset.;

run;

%mend;

this is untested but the logic is there. You read your dataset only once through the set statement and create all of the datasets using keep= dataset options like you already were. It's likely possible to rewrite the proc sql to use %scan for conditions with the &dataset name including both a libname and a dataset name. I simply broke it down for simplicity.

I did not extensively look at exactly how you broke your DS down but looking back you may dislike the way I'm breaking it down. It shouldn't be too difficult to change the inner loop such that it goes from

(&i.-1)*(ceil(&sqlobs./&n.))+1

to

min((&i.)*((ceil(&sqlobs./&n.)), &sqlobs.)

Obviously with a couple %sysfunc, %eval wraps that I am sparing myself here -_-

It should result in a good efficiency gain as you will loop through your original dataset only once instead of &n. times to achieve the same desired results.

Hope this is useful

Vince

Tom
Super User Tom
Super User

So is the idea to perform an horizontal split (left most variables --> right most variables) on a dataset with &N representing the maximum number of variables per output dataset?  So if I had dataset HAVE with variables VAR1 to VAR500 I could split this into three files with max of 200 variables each by writing code like this:

data split1(keep=var1-var200)

       split2(keep=var201-var400)

       split3(keep=var401-var500)

;

set have ;

run;

If the names are instead arbitrary instead of a simple numbered range of names you can use the -- range specification instead. So if you wanted to split SASHELP.CLASS with a max of three variables per the resulting split would be NAME--AGE and HEIGHT--WEIGHT.

So what is a reasonable way to generate that code. I find that this is easier with a data step as you can take advantage of the FIRST./LAST. processing to generate the code.

%macro hsplit(inds,nvars,prefix=split);

proc contents data=&inds out=varlist(keep=name varnum) noprint ;

run;

proc sql noprint ;

  create table varlist as

  select int((varnum-1)/&nvars)+1 as group

       , varnum

       , name

  from varlist

  order by 1,2

  ;

quit;

filename code temp;

data _null_;

  set varlist end=eof;

  by group ;

  file code ;

  if _n_=1 then put 'data ';

  if first.group then put "  &prefix" group ' (keep= ' name '-- ' @ ;

  if last.group then put name ')' ;

  if eof then put ';' / "  set &inds;" / 'run;' ;

run;

%inc code/source2 ;

%mend hsplit;

%hsplit(sashelp.class,3)

%hsplit(sashelp.cars,7)

sas-innovate-2024.png

Join us for SAS Innovate April 16-19 at the Aria in Las Vegas. Bring the team and save big with our group pricing for a limited time only.

Pre-conference courses and tutorials are filling up fast and are always a sellout. Register today to reserve your seat.

 

Register now!

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
  • 8 replies
  • 2380 views
  • 13 likes
  • 6 in conversation