BookmarkSubscribeRSS Feed
DDT
Obsidian | Level 7 DDT
Obsidian | Level 7

I'm using SAS 9.4M2 on Windows.

 

Is it true that there is no way to specify a default value for a DYNAMIC variable in a GTL template?

I can't find any mention of (or syntax for) default values in the SAS docs.

 

I would expect something like below to work, to set the default DESIGNWIDTH to 260mm and the default DESIGNHEIGHT to 170mm.

 

But it seems that there is no such syntax. I only want to change these dimensions occassionally.

 

 

 

      proc template;
        define statgraph xalignedstats;
          dynamic _DESIGN_WIDTH='260mm' _DESIGN_HEIGHT='170mm'
                  _GRP _CAT _MEASURE;

          begingraph / border=false dataskin=none designwidth=_DESIGN_WIDTH designheight=_DESIGN_HEIGHT;
            entrytitle 'Dynamic design width & height';

            layout overlay / walldisplay=none xaxisopts=(discreteopts=(tickvaluefitpolicy=split));
              boxplot x=_CAT y=_MEASURE / group=_GRP groupdisplay=cluster;
            endlayout;
          endgraph;
        end;
      run;

      proc sgrender data=sashelp.heart template=xalignedstats;
        dynamic _GRP     ='bp_status'
                _CAT     ='smoking_status' 
                _MEASURE ='diastolic'
                ;
      run;

 

I also considered Conditional GTL Logic, but it seems I can't use logic to change a DYNAMIC variable. For example, to initialize a non-existant dynamic var to a default value. I can only use logic to write one statement or another. With 2 potential default values, I'd need to control 4 very similar statements.

 

Wrapping the proc template up as a macro just to pass in design width and height seems odd.

 

Any better options for default DYNAMIC values, to avoid passing in common settings with every SGRENDER?

16 REPLIES 16
Rick_SAS
SAS Super FREQ

Here is the documentation for the DYNAMIC statement in GTL.

 

In the GTL, you do not use equal signs.  However, you DO use equal signs in PROC SGRENDER when you assign the value of a dynamic variable.

DDT
Obsidian | Level 7 DDT
Obsidian | Level 7

I started with that doc page. It seems these text strings are not actually default values, but more like comments or labels -- just "an optional text-string denoting [the variable's] purpose or usage".

 

And a quick test confirms that putting this in the GTL template

 

          dynamic _DESIGN_WIDTH '260mm' _DESIGN_HEIGHT '170mm'
                  _GRP _CAT _MEASURE;

results in the same default 640 x 480 plot.

Rick_SAS
SAS Super FREQ

Sorry for the confusion. I should have check my facts before posting.

 

GTL supports IF/ELSE/ENDIF logic. It also supports the exists() function, which returns 1 is a dynamic variable exists and 0 if it doesn't. Therefore, you can write a template that uses a dynamic variable if it exists and uses a default value otherwise:

 

proc template;
define statgraph Test;
dynamic _X _Title;        /* dynamic variables */
  begingraph;
  if (exists(_TITLE))
     entrytitle  _Title;
  else
     entrytitle "A Default Title";
  endif;
  layout overlay;
     histogram _X; 
  endlayout;
endgraph;
end;
run;

ods graphics / width=400px;
proc sgrender data=sashelp.cars template=Test;
   dynamic _X='MPG_City'; /* use default title */
run;

proc sgrender data=sashelp.cars template=Test;
   dynamic _X='MPG_City' _Title="Distribution of MPG";
run;

For other examples, see the documentation for "Conditional Logic in GTL"

DDT
Obsidian | Level 7 DDT
Obsidian | Level 7

Thanks for the confirmation, Rick. I was surprised to find this, as well.

 

The typical GTL approach that you describe is one of the scenarios I tried to avoid, as mentioned above. I have 2 values on one line, so I would end up with GTL code as follows (not tested, but this demonstrates GTL design, I think, with dynamics in red and defaults in blue😞

 

  if (exists(_WIDTH) and exists(_HEIGHT))
     begingraph / border=false dataskin=none designwidth=_WIDTH designheight=_HEIGHT;
  else if (exists(_WIDTH))
     begingraph / border=false dataskin=none designwidth=_WIDTH designheight=170mm;
  else if (exists(_HEIGHT))
     begingraph / border=false dataskin=none designwidth=260mm designheight=_HEIGHT;
  else
     begingraph / border=false dataskin=none designwidth=260mm designheight=170mm;
  endif;

 

If GTL instead supported default values, I would have far less repetitive complexity and as a result correspondingly:

  • much more concise, and therefore more readable, templates -- worth noting that my example is trivial by design, but lack of default settings results in 9 lines of GTL code instead of 1 line.
  • less chance of maintenance errors due to repeating variable names and default values -- again in my trivial example, by GTL design I have to reference each variable or default value 6 times instead of precisely 1 time.

So, if there is still a SAS wishlist ... I'd be happy to find GTL default values near the top 🙂

DDT
Obsidian | Level 7 DDT
Obsidian | Level 7

In case it's helpful to others: SAS documentation suggests another approach.

Worth considering:

Wrap the calling code in a macro that can fully validate the code that executes the template (e.g., setting defaults, checking validity of dynamic values and types).

Reference: SAS Online Doc, Initializing dynamic & macro variables in GTL
http://support.sas.com/documentation/cdl/en/grstatug/67914/HTML/default/viewer.htm#n1j7cpxoazlv08n1f...

 

This nonetheless shifts responsibility to the user (calling code) from the GTL developer, who should be able to write concise, re-usable templates that check parameters and respond accordingly. In general, the separation of logic that belongs together seems like a bad idea to me. Wrapping calling code in a macro that validates GTL parameters just leads to further repetition of logic in each calling macro; logic that really belongs in one place: within the re-usable GTL template.

DDT
Obsidian | Level 7 DDT
Obsidian | Level 7

I'm still learning, so perhaps this is helpful to others. According to SAS 9.4 docs: GTL does not provide ELSE IF syntax... 

 

So you end up instead with nested IF/ ELSE blocks; you end up with 13 lines of template and logic code, rather than the 9 I initially anticipated (still not tested, but closer to correct syntax):

 

 

          IF (EXISTS(_WIDTH) AND EXISTS(_HEIGHT))
            begingraph / ... designwidth=_WIDTH designheight=_HEIGHT ;
          ELSE 
            IF (EXISTS(_WIDTH))
              begingraph / ... designwidth=_WIDTH designheight=170mm ;
            ELSE 
              IF (EXISTS(_HEIGHT))
                begingraph / ... designwidth=260mm designheight=_HEIGHT ;
              ELSE
                begingraph / ... designwidth=260mm designheight=170mm ;
              ENDIF;
            ENDIF;
          ENDIF;

 

Rick_SAS
SAS Super FREQ

Yes, although of those 13 lines, only 7 contain any logic. The ELSE and ENDIF statements are essentially syntatical placeholders.

 

I think my preference for writing the logic you want would be as follows:

 

IF (EXISTS(_WIDTH) 
   IF EXISTS(_HEIGHT))
      begingraph / ... designwidth=_WIDTH designheight=_HEIGHT ;
   ELSE 
      begingraph / ... designwidth=_WIDTH designheight=170mm ;
   ENDIF;
ELSE 
   IF (EXISTS(_HEIGHT))
      begingraph / ... designwidth=260mm designheight=_HEIGHT ;
   ELSE
      begingraph / ... designwidth=260mm designheight=170mm ;
   ENDIF;
ENDIF;

 

DDT
Obsidian | Level 7 DDT
Obsidian | Level 7

Good points, Rick. Your approach is real improvement -- better balance & symmetry, less nesting, and overall a cleaner IF/ELSE structure. Thanks!

DDT
Obsidian | Level 7 DDT
Obsidian | Level 7

As I mentioned, I'm still learning GTL ...

 

I've now learned from my log file, as well as from the online docs that the IF block can be placed anywhere within the BEGINGRAPH / ENDGRAPH block., so cannot be used to control the BEGINGRAPH statement, itself, or a partial GTL statement, such as the just the options that follow "begingraph / ".

 

I think that leaves me to select one of the options to validate parameters in the calling code, outside the GTL.

Quentin
Super User

Interesting question.  My perspective (and I'm not that confident that it is a good one, so would welcome feedback) is that as a macro programmer, I'd rather use the macro language for conditional logic than dynamic GTL variables.  Macro language gives extensive control over the generated GTL, and allows me to put all the control structures in one place.  And I already undrerstand the scoping rules, etc.  With that approach, I often end up with macros like:

 

%macro plotsomething(data=,x=,y=);
  proc template ...;
  run;
  proc sgrender... ;
  run;
%mend;

 

 

The down side of course is that every time you call the macro, it rebuilds the template.  But so far it seems the time to compile the template is usually negligible compared to the time of the SGRENDER step, so I haven't minded paying that cost. 

 

Curious how much that approach would make your head hurt?

BASUG is hosting free webinars Next up: Jane Eslinger presenting PROC REPORT and the ODS EXCEL destination on Mar 27 at noon ET. Register now at the Boston Area SAS Users Group event page: https://www.basug.org/events.
DanH_sas
SAS Super FREQ

If you are "inlining" your template code into your program, then using macro code is okay. However, if you are writing a "black box" library of templates to be used by others via SGRENDER, using the built-in GTL conditional logic is the way to go.

Quentin
Super User

Thanks @DanH_sas.   In my case it's more like I'm writing "black box" library of macros to be used by others to produce graphics.  So the macro becomes a wrapper for GTL and SGRENDER, and the macro designer (group) can decide on how much standardization to enforce / customizability to allow. 

BASUG is hosting free webinars Next up: Jane Eslinger presenting PROC REPORT and the ODS EXCEL destination on Mar 27 at noon ET. Register now at the Boston Area SAS Users Group event page: https://www.basug.org/events.
DDT
Obsidian | Level 7 DDT
Obsidian | Level 7

My intention was to create a library of compiled templates, which do not require inline execution.

 

But given the lack of default values for dynamic variable, and the absence of conditional logic for partial GTL statements or anywhere outside the begingraph/ block, I think the macro-wrapper approach is more convenient for SGRENDER users. (By comparison, Macro Language supports default values, complex conditional logic and macro-value adjustment, and can be used to write parts of statements.)

 

Somehow recompiling templates doesn't seem right to me, at least initially.  But in fact this is how I've always worked with SAS macros. I've never worked in a SAS group that uses a compiled macro catalog or process store or similar; always inline macro compilation.

 

So perhaps inline templates should be a comfortable approach.

Quentin
Super User

I definitely think you're right to question recompiling GTL templates.  I still question whether what I'm doing is "right", or just too lazy to learn more GTL.

 

In particular, when you have a %SurvivalPlot() macro, that macro would typically only be compiled once in a session.  If in that macro you create a GTL template, that template is rebuilt every time the macro executes (every time you make a survival plot).  And many times the only difference may be the dependent variable or even the input dataset.  So definitely recompiling GTL template feels (and is) wasteful of CPU cycles.  I just haven't felt enough cost from it to justify learning the dynamic components of the GTL language.

 

There are also benefits to be gained from storing GTL code (in macro), rather than storing a compiled template, in terms of cross-platform compatibility.  And if you already have a group accustomed to using a library of reporting macros, wrapping the GTL in a macro serves as useful information hiding.  They can call %SurvivalPlot() in the same way they are used to calling %AdverseEventTable().

 

Other than the need to recompile a template multiple times in a single session, the tradeoff between storing GTL code vs templates is not that different from storing macro code vs compiled macros, or PROC format code vs permanent format libraries, or proc template style code vs permanent template item stores.  In many of these cases I end up leaving the information in the code, and pay the price of rebuilding the template items/format catalog/macro in each SAS session.  Usually the bottlenecks in my code are big data or sloppy DATA/PROC steps I wrote to process data, not the steps that create various metadata.

BASUG is hosting free webinars Next up: Jane Eslinger presenting PROC REPORT and the ODS EXCEL destination on Mar 27 at noon ET. Register now at the Boston Area SAS Users Group event page: https://www.basug.org/events.

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
  • 16 replies
  • 2672 views
  • 11 likes
  • 5 in conversation