ODS Graphics Template Modification


%Marginal Macro

You can use the %Marginal macro to create marginal model plots. The macro begins by initializing some macro variables and by checking the options. The macro gathers variable names, checks them, and stores them in the order that you specify. The macro determines the number of rows and columns in the panel along with the size. Next, it determines the endpoints for the predicted values plot. The macro constructs a custom graph template, and then one or more graphs are made using the template. The macro code follows.

                       /*----------------------------------------------*/
%macro marginal(       /*-------------Marginal Model Plots-------------*/
                       /*----------------------------------------------*/
       data=_last_,    /* Input SAS data set.                          */
       dependent=,     /* Dependent variable.                          */
       independents=,  /* Continuous independent variables.            */
       predicted=,     /* Predicted values variable.                   */
       smooth=loess,   /* LOESS, PBSPLINE, or REGRESSION.              */
       smoothopts=,    /* GTL options for smoothing.  They are added   */
                       /* to the GTL LOESSPLOT, PBSPLINEPLOT, or       */
                       /* REGRESSIONPLOT statement.                    */
       panel=,         /* Number of rows and columns in the panel.     */
                       /* The default depends on the number of         */
                       /* independent variables.  Specify two values   */
                       /* (rows columns) or one when rows equal        */
                       /* columns.  For example: PANEL=2 3.            */
       gopts=,         /* GTL BEGINGRAPH statement graph size options. */
                       /* The default depends on the number of         */
                       /* rows and columns in the panel.               */
                       /* Example: DESIGNHEIGHT=800px.                 */
       );;             /*----------------------------------------------*/

%local abort holdlast nvars i ncells savenote time tmpl paneled rows cols ettl;

%let time     = %sysfunc(datetime());
%let abort    = 0;
%let tmpl     = 0;
%let holdlast = &syslast;
%let savenote = %sysfunc(getoption(notes));

options nonotes;

data _null_; /* basic option processing and checking */
   length l $ 67;
   call symputx('ettl', quote('Marginal Models for ' || symget('dependent')));
   l = symget('data');
   if l = ' ' or lowcase(l) = '_last_' then  /* default, last data set */
      call symputx('data', symget('syslast'));

   l = symget('panel');
   r = input(scan(l, 1, ' '), best12.);
   c = input(scan(l, 2, ' '), best12.);
   if nmiss(c) and n(r) then c = r;
   call symputx('rows', r);
   call symputx('cols', c);

   if n(r,c) eq 2 and
      not (r = round(r) and c = round(c) and r ge 1 and c ge 1) then do;
      put "ERROR: PANEL=&panel must specify positive integers.";
      call symputx('abort', 1);
   end;

   l = lowcase(symget('smooth'));
   if not (trim(l) in ('regression', 'loess', 'pbspline')) then do;
      put 'ERROR: Invalid SMOOTH= option.  Specify: '
          'REGRESSION, LOESS, or PBSPLINE.';
      call symputx('abort', 1);
   end;

   if symget('predicted') eq ' ' or symget('dependent') eq ' ' or
      symget('independents') eq ' ' then do;
      put 'ERROR: The PREDICTED=, DEPENDENT=, and INDEPENDENTS= '
         'options ' 'must all ' 'be specified.';
      call symputx('abort', 1);
   end;

   if _error_ then call symputx('abort', 1);
run;

%if &syserr > 4 %then %let abort = 1; %if &abort %then %goto endit;

data _null_;  /* check the variable names,                           */
   set &data; /* some problematic specifications generate SAS errors */
   &predicted = mean(of &predicted &dependent &independents);
   &dependent = mean(of &predicted &dependent &independents);
   if _error_ then do;
      put 'ERROR: All variables must be numeric.';
      _error_ = 0;
      call symputx('abort', 1);
   end;
   stop;
run;

%if &syserr > 4 %then %let abort = 1; %if &abort %then %goto endit;

data __tmpcon; /* reorder vars if necessary */
   length &independents 8;
   set &data(obs=1 keep=&independents);
   if _error_ then call symputx('abort', 1);
run;

%if &syserr > 4 %then %let abort = 1; %if &abort %then %goto endit;

proc contents data=__tmpcon noprint out=__tmpcon(keep=name varnum);
run;

%if &syserr > 4 %then %let abort = 1; %if &abort %then %goto endit;

proc sort data=__tmpcon out=__tmpcon(drop=varnum);
   by varnum;
run;

%if &syserr > 4 %then %let abort = 1; %if &abort %then %goto endit;

data __tmpcon(keep=name); /* augment name list, determine panel size */
   length gopts $ 32;
   set __tmpcon nobs=n;

   output;

   if n = _n_ then do;
      call symputx('nvars', n + 1);

      r = input(symget('rows'), best12.);
      c = input(symget('cols'), best12.);
      if n(r,c) eq 0 then do;
         %let i = 8,9,10,16,17,18,19,20,21,30,31,32;
         select;
            when(n le 2)          do; r = 2; c = 2; end;
            when(n in (3,4))      do; r = 2; c = 3; end;
            when(n in (5,6,7,15)) do; r = 3; c = 3; end;
            when(n in (&i))       do; r = 3; c = 4; end;
            otherwise             do; r = 4; c = 4; end;
         end;
         call symputx('rows', r);
         call symputx('cols', c);
      end;
      call symputx('paneled', r * c gt 1);
      if symget('gopts') eq ' ' then do;
         select;
            when(r eq 1 and c eq 1) gopts = 'designwidth=defaultdesignheight';
            when(r eq 1 and c eq 2) gopts = 'designheight=360px';
            when(r eq 2 and c eq 2) gopts = 'designwidth=defaultdesignheight';
            when(r eq 2 and c eq 3) gopts = ' ';
            when(r eq 3 and c eq 4) gopts = 'designheight=520px';
            otherwise               gopts = 'designheight=defaultdesignwidth';
         end;
         call symputx('gopts', gopts);
      end;
      name = ' '; output;
   end;

   if _error_ then call symputx('abort', 1);
run;

%if &syserr > 4 %then %let abort = 1; %if &abort %then %goto endit;

data __tmpdat(keep=_x _y); /* get extremes for yhat series plot */
   set &data end=eof;
   retain _minx _maxx _miny _maxy .;
   if nmiss(_minx) then _minx = &predicted;
   if nmiss(_maxx) then _maxx = &predicted;
   if n(&predicted,_minx,&dependent) = 3 and &predicted lt _minx then do;
      _minx = &predicted; _miny = &dependent;
   end;
   if n(&predicted,_maxx,&dependent) = 3 and &predicted gt _maxx then do;
      _maxx = &predicted; _maxy = &dependent;
   end;
   if eof then do;
      _x = _minx; _y = _miny; output;
      _x = _maxx; _y = _maxy; output;
   end;
   if _error_ then call symputx('abort', 1);
run;

%if &syserr > 4 %then %let abort = 1; %if &abort %then %goto endit;

data __tmpdat;
   merge &data __tmpdat;
   if _error_ then call symputx('abort', 1);
run;

%if &syserr > 4 %then %let abort = 1; %if &abort %then %goto endit;

%let tmpl = 1;
proc template;
   define statgraph __marginal;
      dynamic %do i = 1 %to &rows*&cols - &paneled; _ivar&i %end; ncells pplot;
      begingraph / &gopts;
         entrytitle &ettl;
         legenditem type=line name='a' / lineattrs=GraphFit  label='Data';
         legenditem type=line name='b' / lineattrs=GraphFit2 label='Model';
         layout lattice / columns=&cols rows=&rows
                rowdatarange=unionall rowgutter=10 columngutter=10;
            %do i = 1 %to &rows * &cols - &paneled; /* ordinary cells */
               if(&i le ncells)
                  layout overlay / yaxisopts=(display=none)
                                   xaxisopts=(display=(label));
                     scatterplot  y=&dependent  x=_ivar&i;
                     &smooth.plot y=&dependent  x=_ivar&i / &smoothopts;
                     &smooth.plot y=&predicted  x=_ivar&i / &smoothopts
                                                            lineattrs=graphfit2;
                     %if not &paneled %then %do; /* not paneled?           */
                        layout gridded /         /* then put legend inside */
                           autoalign=(topright topleft bottomright bottomleft);
                           discretelegend 'a' 'b' / location=inside across=1;
                        endlayout;
                     %end;
                  endlayout;
               endif;
            %end;
            if(pplot) /* predicted values plot is handled differently */
               layout overlay / yaxisopts=(display=none)
                      xaxisopts=(display=(label) label='Predicted Values');
                  scatterplot  y=&dependent  x=&predicted;
                  &smooth.plot y=&dependent  x=&predicted / &smoothopts;
                  seriesplot   y=_y x=_x / lineattrs=graphfit2;
                  %if not &paneled %then %do;
                     layout gridded /
                            autoalign=(topright topleft bottomright bottomleft);
                        discretelegend 'a' 'b' / location=inside across=1;
                     endlayout;
                  %end;
               endlayout;
            endif;
            %if &paneled %then %do; /* legend in a cell by itself if paneled */
               layout overlay / yaxisopts=(display=none)
                                xaxisopts=(display=none);
                  discretelegend 'a' 'b' / location=inside across=1
                                           border=false;
               endlayout;
            %end;
         endlayout;
      endgraph;
   end;
run; quit;

%if &syserr > 4 %then %let abort = 1; %if &abort %then %goto endit;

%let i = 1;
%do %while(&i le &nvars); /* create as many panels as is necessary */
   data _null_;
      set __tmpcon(firstobs=&i);
      length l $ 2000;
      retain l 'dynamic';
      if name ne ' ' then /* construct independent var list */
         l = catx(' ', l, '_ivar' || put(_n_, 3. -L), '=', quote(trim(name)));
      if name eq ' ' or _n_ eq (&rows * &cols - &paneled) then do;
         call symputx('pplot', name eq ' '); /* yhat plot in this panel? */
         call symputx('dynamics', l);
         call symputx('i', &i + _n_);
         call symputx('ncells', _n_ - (name = ' '));
         if _error_ then call symputx('abort', 1);
         stop;
      end;
      if _error_ then call symputx('abort', 1);
   run;

   %if &syserr > 4 %then %let abort = 1; %if &abort %then %goto endit;


   proc sgrender data=__tmpdat template=__marginal;
      &dynamics ncells=&ncells pplot=&pplot;
   run;

   %if &syserr > 4 %then %let abort = 1; %if &abort %then %goto endit;
%end;

%endit:

proc datasets nolist; delete __tmpcon __tmpdat; run; quit;

%if &syserr > 4 %then %let abort = 1;

%if &tmpl and not %symexist(tmplsave) %then %do;
   proc template; delete __marginal; run; quit;
   %if &syserr > 4 %then %let abort = 1;
%end;

%let syslast = &holdlast;

options &savenote;

%if &abort %then %do;
   %if &syserr = 1016 or &syserr = 116  %then %put ERROR: Insufficient memory.;
   %if &syserr = 2000 or &syserr = 3000 %then
      %put ERROR: Syntax error.  Check your macro arguments for validity.;
   %put ERROR: The MARGINAL macro ended abnormally.;
%end;

%let time = %sysfunc(round(%sysevalf(%sysfunc(datetime()) - &time), 0.01));
%put NOTE: The MARGINAL macro used &time seconds.;

%mend;