"Call symput" absolutely everything

Introduction

I write a lot of macros, as you will realize from spending some time on this web site. All the macros you can find on this web site accept values through parameter settings. Some of my macros have many, many parameters but in practice, only a few of these parameters get used. Suppose you had a macro with a lot of parameters and all of them had to be set for a person to be able to use it, then things get messy. I have two macros with more than 90 parameters. Imagine having to call a macro and specify all 90 plus parameters with their values!

Using parameters is not the only way to pass values to a macro. You can pass them using a sas dataset if you have designed you macro to accept them. You can make it work such that the variable names are the parameter names and the values in the variables are the parameter values. Then, in your macro, you use it just like you had parameters with parameter values. It makes sense to do it this way when you have a lot of parameters that have to be set. Using a dataset makes it is easier to document your settings. You can have a standard "set" of settings in a dataset that can be distributed. You can specify your "changing" settings at the top of your code, in a data step, where it is easy to notice, rather than specify them at the end of your code where you call a macro after preparing your input data.

An example of a macro parameter dataset

I had been working on a macro and at some point it became clear that it would be better to supply the macro parameters and their values in the form of a single observation dataset. The macro could then use the dataset to "symput" out the variable values using the variable name to name the macro variable inside the macro. If "symput"ing inside a macro then these macro variables should be local to that macro just like they were macro parameters (how to ensure the "call symput"s create local rather than global macro variables will be explained later).

I like to give real-life examples so here is the parameter dataset I set up. Actually, there is not just one parameter dataset -- there are TWO !!
 
data rgpp_style;
  *- do not change any of these variable names or their characteristics -;
  length cback cstripe cscale $ 8
         fitemtext fitemdesc fblockdesc fpatient $ 32
         citemdesc citemdesctrunc cblockdesc cpatient citemtext cfigure $ 8
         cblockbox $ 8
         cblockdescbg cpatientbg $ 8
         hitemdesc hblockdesc hpatient hitemtext hfigure hscaletext 8
         drawblockbox uniformscale usearrows 8
         ypixels xpixels vpos hpos 8
         hposdmin hposdmax hposdmean hposminfigwidth hposmingap 8
         maxucletters maxmcletters 8
         ;

  *- Size of html output with number of rows and columns. -;
  *- You need a knowledge of sas graphics to set these.   -;
  ypixels=2000;
  vpos=150;  /* number of rows */
  xpixels=1000;
  hpos=50;   /* number of columns */

  *- Start and end hpos positions for date area. The maximum -;
  *- value must not exceed what was specifed in hpos= above. -;
  hposdmin=20;
  hposdmax=50;
  *- mean position calculated automatically -;
  hposdmean=(hposdmin+hposdmax)/2;

  *- Colors of the background and stripes across the date area -;
  cback="white";
  cstripe="beige";

  *- Font, color and height of text displayed on the left of the date area  -;
  *- (note that non-sas fonts need to be enclosed in an extra set of single quotes) -;
  *- Note that hitemdesc must not be given a value greater than 1.0 -;
  fitemdesc="'Arial'";
  citemdesc="maroon";
  hitemdesc=0.8;

  *- Height, font and default color of the text to use when text values -;
  *- are displayed in the date area such as vital signs values.   -;
  *- (note that non-sas fonts need to be enclosed in an extra set of single quotes) -;
  *- Note that hitemtext must not be given a value greater than 1.0 -;
  fitemtext="'Arial'";
  hitemtext=0.7;
  citemtext="maroon";

  *- Maximum upper case and mixed case letters that you know will  -;
  *- fit on the left of the date area. You will have to do a trial -;
  *- run, inspect the outputs and adjust these values. -;
  *- upper case -;
  maxucletters=23;
  *- mixed case -;
  maxmcletters=29;
  *- Color to show this text when it gets truncated. You will be  -;
  *- able to mouse over this truncated text to see the full text. -;
  citemdesctrunc="blue";

  *- Font, color, height and background color of Patient top-of-page title -;
  *- (note that non-sas fonts need to be enclosed in an extra set of single quotes) -;
  fpatient="'Arial / Bold'";
  cpatient="black";
  cpatientbg="CXE0E0E0";
  hpatient=2;

  *- Font, color, height and background color of block titles -;
  *- (note that non-sas fonts need to be enclosed in an extra set of single quotes) -;
  fblockdesc="'Arial / Bold'";
  cblockdesc="black";
  cblockdescbg="CXE0E0E0";
  hblockdesc=1;

  *- Whether or not to draw a box around each block of information displayed -;
  *- and what color if so. Set to 0 or 1  0=no 1=yes -;
  drawblockbox=1;
  cblockbox="maroon";

  *- Height and color of the time scale to draw.   -;
  *- Note that block box color (cblockbox) overrides -;
  *- the scale color if drawblockbox=1  -;
  hscaletext=0.8;
  cscale="maroon";

  *- Whether the time ticks on the scale should be uniformly spaced. -;
  *- 0=no, 1=yes -;
  uniformscale=1;

  *- Whether to use arrow heads to point to start or end day values -;
  *- that have been imputed where their exact value is unknown.     -;
  *- This also applies to start or end values that fall off the     -;
  *- day scale at either end. 0=no, 1=yes.    -;
  usearrows=1;

  *- Height and color of the figures. Height is effectively the   -;
  *- thickness of the arrow or rod shafts used for showing period -;
  *- information such as the duration of an adverse event. Note   -;
  *- that if arrows are used then the value for hfigure must not  -;
  *- be greater than 0.5  -;
  hfigure=0.4;
  cfigure="BIYG";

  *- This is the minimum width in hpos values that you want for -;
  *- period data. Some adverse events might only last a day and -;
  *- so a rod or an arrow used to represent it might be too     -;
  *- short to see. This setting will ensure that these short    -;
  *- periods are visible. The position on the day scale will be -;
  *- the mid position of the start and end day. This will be a  -;
  *- box shape that will override an arrow shape. Set it to a   -;
  *- value that is slightly less than the hfigure value.        -;
  hposminfigwidth=0.3;

  *- hposmingap is a control used when you are displaying text   -;
  *- values. If two text values are very close to each other due -;
  *- to the dates being close then the two sets of text will     -;
  *- overlap and become unreadable. What the patient profiler    -;
  *- will do is to display a box for the second set of text      -;
  *- instead of the text if the values are too close. It is up   -;
  *- to you to decide how close you want to get before it makes  -;
  *- this substitution. This is given in hpos positions. Look at -;
  *- the range of hpos values you used for the scale (hposdmin   -;
  *- and hposdmax) and decide what value, based on this, you     -;
  *- require to ensure displayed text values stay separate.      -;
  hposmingap=1.3;

run;
 

data rgpp_global;
  *- do not change any of these variable names or their characteristics -;
  length patvar $ 32  webout scale $ 200;

  *- Name of the unique patient identifier variable present -;
  *- in the rgpp_patients and rgpp_data datasets. -;
  patvar="subjid";

  *- Destination folder for the html files and gif files -;
  *- created during a run of the patient profiler. Note  -;
  *- this destination folder must actually exist. For a  -;
  *- complete run of new reports you should ensure that  -;
  *- the destination folder is empty before you do the   -;
  *- full run.   -;
  webout="C:\webtemp";

  *- Set this to your scale you want to show on the time axis. They should be pairs -;
  *- of study day numbers (first day of study being Day 0) and labels separated     -;
  *- with a #. Pairs of study days and labels should be separated from others like  -;
  *- themselves with a single upright line that sas normally uses (in pairs) for    -;
  *- concatenantion.  An example is given below.  -;
  scale="-14#Screen | 0#Baseline | 7#WK1 | 14#WK2 | 28#WK4 | 56#WK8 | 112#WK16";

run;

Running the above code just creates a dataset. It does not create any macro variables. I ran that code and afterwards checked for what global macro variables were there and this is what I got. I got no global macro variables.
 
624  %put _global_;
 
 

I know that my parameter dataset contains both numeric variables and character variables. I also know it does not contain variables with the unlikely names of "__i",  "__y", "_num" or "_char". So here is my code for "symput"ing out all the variables and their values. Note that I have to use a merge statement, without any "by" variables, as I have two datasets but want only one collective observation.
 
data _null_;
  merge rgpp_style rgpp_global;
  array _num {*} _numeric_;
  array _char {*} _character_;
  length __y $ 32;
  do __i=1 to dim(_char);
    __y=vname(_char(__i));
    call symput(__y,trim(left(_char(__i))));
  end;
  do __i=1 to dim(_num);
    __y=vname(_num(__i));
    call symput(__y,trim(left(_num(__i))));
  end;
run;

%put _global_; 

....and here is what I got in the log.
 
688  data _null_;
689    merge rgpp_style rgpp_global;
690    array _num {*} _numeric_;
691    array _char {*} _character_;
692    length __y $ 32;
693    do __i=1 to dim(_char);
694      __y=vname(_char(__i));
695      call symput(__y,trim(left(_char(__i))));
696    end;
697    do __i=1 to dim(_num);
698      __y=vname(_num(__i));
699      call symput(__y,trim(left(_num(__i))));
700    end;
701  run;

NOTE: Numeric values have been converted to character values at the places given by:
      (Line):(Column).
      699:31
NOTE: There were 1 observations read from the data set WORK.RGPP_STYLE.
NOTE: There were 1 observations read from the data set WORK.RGPP_GLOBAL.
NOTE: DATA statement used (Total process time):
      real time           0.01 seconds
      cpu time            0.01 seconds
 

702
703  %put _global_;
GLOBAL FPATIENT 'Arial / Bold'
GLOBAL HPOS 50
GLOBAL HPOSMINGAP 1.3
GLOBAL FITEMTEXT 'Arial'
GLOBAL HPOSDMEAN 35
GLOBAL HPOSMINFIGWIDTH 0.3
GLOBAL HSCALETEXT 0.8
GLOBAL DRAWBLOCKBOX 1
GLOBAL SCALE -14#Screen | 0#Baseline | 7#WK1 | 14#WK2 | 28#WK4 | 56#WK8 | 112#WK16
GLOBAL HPATIENT 2
GLOBAL CITEMTEXT maroon
GLOBAL CFIGURE BIYG
GLOBAL HBLOCKDESC 1
GLOBAL CBLOCKBOX maroon
GLOBAL HITEMDESC 0.8
GLOBAL USEARROWS 1
GLOBAL MAXUCLETTERS 23
GLOBAL WEBOUT C:\webtemp
GLOBAL CSTRIPE beige
GLOBAL HPOSDMIN 20
GLOBAL CBLOCKDESC black
GLOBAL CBLOCKDESCBG CXE0E0E0
GLOBAL HFIGURE 0.4
GLOBAL UNIFORMSCALE 1
GLOBAL CSCALE maroon
GLOBAL FITEMDESC 'Arial'
GLOBAL CPATIENT black
GLOBAL CPATIENTBG CXE0E0E0
GLOBAL HITEMTEXT 0.7
GLOBAL CBACK white
GLOBAL MAXMCLETTERS 29
GLOBAL VPOS 150
GLOBAL FBLOCKDESC 'Arial / Bold'
GLOBAL CITEMDESC maroon
GLOBAL CITEMDESCTRUNC blue
GLOBAL PATVAR subjid
GLOBAL YPIXELS 2000
GLOBAL HPOSDMAX 50
GLOBAL XPIXELS 1000

You can see that all the dataset variables have been turned into global macro variables and their values are now the global macro values.

What I am going to do next is to show you how you can have a macro access these two parameter datasets using the code above and you will see that these macro variables are local to the macro. This is the ideal situation because it means these parameter datasets really are acting like parameters and their settings and their values will not affect anything outside the calling macro. Firstly, there is something I have to warn you about. The macro variables will only be local to the macro if the macro has at least ONE parameter. That's the way "call symput" works. You don't have to use this parameter - you just have to declare it. I know that what we are trying to do is to pass all parameters and their values in datasets, and not call the macro with any parameter settings, but you need one parameter declared with the macro (at least) for the scope of "call symput" to be local. I will be using a positional parameter named "dummy" for that that I won't refer to in my code. You need to remember that rule if you are doing  serious macro programming using parameter datasets. The way it is explained in the sas documentation is "SYMPUT puts the macro variable in the most local nonempty symbol table". So in our macro we need to make sure it has something in its "symbol table" so that the "call symput"s will scope the macro variables in the macro. There is more in the sas documentation on this. Also read up on "call symputx" when you have time.

One more time - note that I used a dummy parameter in the macro definition. I did this to keep the "call symput"s local. Without the dummy parameter, they would be global.
 
152  %macro mymacro(dummy);
153    data _null_;
154      merge rgpp_style rgpp_global;
155      array _num {*} _numeric_;
156      array _char {*} _character_;
157      length __y $ 32;
158      do __i=1 to dim(_char);
159        __y=vname(_char(__i));
160        call symput(__y,trim(left(_char(__i))));
161      end;
162      do __i=1 to dim(_num);
163        __y=vname(_num(__i));
164        call symput(__y,trim(left(_num(__i))));
165      end;
166    run;
167    %put >>>>>> inside "mymacro" these are the user macro variables >>>>>;
168    %put _user_;
169  %mend mymacro;
170  %mymacro
171
172  %put >>>>>>> ouside "mymacro" these are the global macro variables >>>>>;

NOTE: Numeric values have been converted to character values at the places given by:
      (Line):(Column).
      2:82
NOTE: There were 1 observations read from the data set WORK.RGPP_STYLE.
NOTE: There were 1 observations read from the data set WORK.RGPP_GLOBAL.
NOTE: DATA statement used (Total process time):
      real time           0.07 seconds
      cpu time            0.01 seconds
 

>>>>>> inside "mymacro" these are the user macro variables >>>>>
MYMACRO FPATIENT 'Arial / Bold'
MYMACRO HPOS 50
MYMACRO HPOSMINGAP 1.3
MYMACRO FITEMTEXT 'Arial'
MYMACRO HPOSDMEAN 35
MYMACRO HPOSMINFIGWIDTH 0.3
MYMACRO HSCALETEXT 0.8
MYMACRO DRAWBLOCKBOX 1
MYMACRO SCALE -14#Screen | 0#Baseline | 7#WK1 | 14#WK2 | 28#WK4 | 56#WK8 | 112#WK16
MYMACRO HPATIENT 2
MYMACRO CITEMTEXT maroon
MYMACRO CFIGURE BIYG
MYMACRO HBLOCKDESC 1
MYMACRO CBLOCKBOX maroon
MYMACRO HITEMDESC 0.8
MYMACRO USEARROWS 1
MYMACRO MAXUCLETTERS 23
MYMACRO DUMMY
MYMACRO WEBOUT C:\webtemp
MYMACRO CSTRIPE beige
MYMACRO HPOSDMIN 20
MYMACRO CBLOCKDESC black
MYMACRO CBLOCKDESCBG CXE0E0E0
MYMACRO HFIGURE 0.4
MYMACRO UNIFORMSCALE 1
MYMACRO CSCALE maroon
MYMACRO FITEMDESC 'Arial'
MYMACRO CPATIENT black
MYMACRO CPATIENTBG CXE0E0E0
MYMACRO HITEMTEXT 0.7
MYMACRO CBACK white
MYMACRO MAXMCLETTERS 29
MYMACRO VPOS 150
MYMACRO FBLOCKDESC 'Arial / Bold'
MYMACRO CITEMDESC maroon
MYMACRO CITEMDESCTRUNC blue
MYMACRO PATVAR subjid
MYMACRO YPIXELS 2000
MYMACRO HPOSDMAX 50
MYMACRO XPIXELS 1000
>>>>>>> ouside "mymacro" these are the global macro variables >>>>>
173  %put _global_;

 

Conclusion

You have been introduced to the concept of a macro parameter dataset and how the variables and its values in this single observation dataset can easily be converted into macro variables local to the macro that are essentially the same as macro parameters and their values. Using the technique described on this page, you can design macros that accept parameter datasets, when the need arises, rather than rely on macro parameters.
 


 
 

Go back to the home page.

E-mail the macro and web site author.