01-29-2016 12:58 PM
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?
01-29-2016 01:42 PM
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.
01-29-2016 03:02 PM
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.
01-29-2016 04:32 PM - edited 01-30-2016 06:33 AM
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"
01-30-2016 08:58 AM
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:
So, if there is still a SAS wishlist ... I'd be happy to find GTL default values near the top
02-01-2016 06:55 AM - edited 02-01-2016 07:09 AM
In case it's helpful to others: SAS documentation suggests another approach.
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
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.
02-01-2016 07:26 AM - edited 02-01-2016 07:30 AM
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;
02-01-2016 08:05 AM - edited 02-01-2016 08:06 AM
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;
02-01-2016 09:29 AM
Good points, Rick. Your approach is real improvement -- better balance & symmetry, less nesting, and overall a cleaner IF/ELSE structure. Thanks!
02-01-2016 09:50 AM
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.
02-02-2016 09:18 AM
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?
02-02-2016 10:10 AM
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.
02-02-2016 11:58 AM
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.
02-02-2016 12:42 PM
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.
02-02-2016 01:37 PM
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.