This article demonstrates a SAS programming technique that I call Kuhfeld's template modification technique. The technique enables you to dynamically modify an ODS template and immediately call the modified template to produce a new graph or table. By following the five steps in this article, you can implement the technique in less than 20 lines of SAS code.
Kuhfeld's Template Modification Technique (TMT)
The template modification technique (TMT) is one of several advanced techniques that Warren Kuhfeld created and promoted in a series of articles that culminated with Kuhfeld (2016). (Slides are also available.) The technique that I call the TMT is a DATA _NULL_ program that modifies a SAS-supplied template, uses CALL EXECUTE to compiles it "on the fly," and immediately calls the modified template to render a new graph (pp. 12ff in the paper; p. 14 of his slides).
When I attended Warren's 2016 presentation, I felt a bit overwhelmed by the volume of information. It was difficult for me to see the forest because of all the interconnected "trees." Graph customization is easier to understand if you restrict your attention only to GTL templates. You can make simple modifications to a template (for example, changing the value of an option) by using the following five steps:
- Prepend a private item store to the ODS search path. The private item store will store the modified template.
- Write the existing template to a temporary text file.
- Run a DATA step that reads the existing template one line at a time. Check each line to see if it contains the option that you want to change. If so, modify the line to change the template's behavior.
- Compile the modified template into the private item store.
- Call PROC SGRENDER to render the graph by using the new template.
This article is intentionally brief. For more information see the chapter "ODS Graphics Template Modification" in the SAS/STAT documentation.
Most of Kuhfeld's examples use the TMT to modify ODS templates that are distributed with SAS. These templates can be complex and often contain conditional logic. In an effort to produce a simpler example, the next section creates a simple template that uses a heat map to display a response variable on a uniform grid.
Example: A template that specifies a color ramp
Programmers who use the GTL to create graphs have probably encountered dynamic variables and run-time macro variables. You can use these to specify certain options at run time, rather than hard-coding information into a template. However, the documentation states that "dynamic variables ... cannot resolve to statement or option keywords." In particular, the GTL does not support changing the color ramp at run time: whatever color ramp is hard-coded in the template is the color ramp that is used. (Note: If you have SAS 9.4M3, the HEATMAPPARM statement in PROC SGPLOT supports specifying a color ramp at run time.)
However, you can use Kuhfeld's TMT to modify the color ramp at run time. The following GTL defines a template named "HEATMAP." The template uses the HEATMAPPARM statement and hard-codes the COLORMODEL= option to be the "ThreeColorRamp" in the current ODS style. The template is adapted from the article "Creating a basic heat map in SAS."
/* Construct example template: a basic heat map with continuous color ramp */ proc template; define statgraph HEATMAP; dynamic _X _Y _Z; /* dynamic variables */ begingraph; layout overlay; heatmapparm x=_X y=_Y colorresponse=_Z / /* specify variables at run time */ name="heatmap" primary=true xbinaxis=false ybinaxis=false colormodel = THREECOLORRAMP; /* <== hard-coded color ramp */ continuouslegend "heatmap"; endlayout; endgraph; end; run;
When the PROC TEMPLATE step runs, the HEATMAP template is stored in a location that is determined by your ODS search path. You can run ods path show; to display the search path. You can discover where the HEATMAP template is stored by running proc template; list HEATMAP; run;.
The following DATA step defines (x,y) data on a uniform grid and defines z to be a cubic function of x and y. The heat map shows an approximate contour plot of the cubic function:
/* Construct example data: A cubic function z = f(x,y) on regular grid */ do x = -1 to 1 by 0.05; do y = -1 to 1 by 0.05; z = x**3 - y**2 - x + 0.5; output; end; end; run; /* visualize the data using the built-in color ramp */ proc sgrender data=Cubic template=Heatmap; dynamic _X='X' _Y='Y' _Z='Z'; run;
The remainder of this article shows how to use Kuhfeld's TMT to create a new template which is identical to the HEATMAP template except that it uses a different color ramp. Kuhfeld's technique copies the existing template line by line but replaces the text "THREECOLORRAMP" with text that defines a new custom color ramp.
In the following program, capital letters are used for the name of the existing template, the name of the new template, and the value of the new color ramp. Most of the uncapitalized terms are "boilerplate" for the TMT, although each application will require unique logic in the DATA _NULL_ step.
Step 1: Prepend a private item store to the ODS search path
PROC TEMPLATE writes templates to the first (writable) item store on the ODS search path. PROC SGRENDER looks through the ODS search path until it locates a template with a given name. The first step is to prepend a private item store to the search path, as shown by the following statement:
/* 1. Modify the search path for ODS. Prepend an item store in WORK */ ods path (prepend) work.modtemp(update);
The ODS PATH statement ensures that the modified template will be stored in WORK.MODTEMP. You can provide any name you want. The name WORK.TEMPLAT is a convention that is used in the SAS documentation. Because you might already have WORK.TEMPLAT on your ODS path, I used a different name. (Note: Any item stores in WORK are deleted when SAS exits.) The SAS/STAT documentation provides more information about the template search path.
Step 2: Write the existing template to a temporary text file
You need to write the source code for the HEATMAP template to a plain text file so that it can be read by the SAS DATA step. You can use the FILENAME statement to point to a hard-coded location (such as filename t "C:/temp/tmplt.txt";), but I prefer to use the SAS keyword TEMP, which is more portable. The TEMP keyword tells SAS to create a temporary file name in a location that is independent of the operating system and the file system:
/* 2. Write body of existing template to text file */ filename _tmplt TEMP; /* create temporary file and name */ proc template; source HEATMAP / file=_tmplt; /* write template source to text file */ run;
If you omit the FILE= option, the template source is written to the SAS log. The text file contains only the body of the HEATMAP template.
Steps 3 and 4: Create a new template from the old template
The next step reads and modifies the existing template line by line. There are many SAS character functions that enable you to discover whether some keyword exists in a line of text. Examples include the INDEX and FIND family of functions, the PRXCHANGE function (which uses regular expressions), and the TRANWRD function.
The goal of the current example is to replace the word "THREECOLORRAMP" by another string. The TRANWRD function replaces the string if it is found; the line is unchanged if the word is not found. Therefore it is safe to call the TRANWRD function on every line in the existing template.
You can use the CALL EXECUTE function to place each (possibly modified) line of the template onto a queue, which will be executed when the DATA step completes. However, the text file does not start with a PROC TEMPLATE statement nor end with a RUN statement. You must add those statements to the execution queue in order to compile the template, as follows:
/* one possible way to specify a new color ramp */ %let NEWCOLORRAMP = (blue cyan yellow red); /* 3. Read old template; make modifications. 4. Use CALL EXECUTE to compile (modified) template with a new name. */ data _null_; /* Modify graph template to replace default color ramp */ infile _tmplt end=eof; /* read from text file; last line sets EOF flag to true */ input; /* read one line at a time */ if _n_ = 1 then call execute('proc template;'); /* add 'PROC TEMPLATE;' before */ if findw(_infile_, 'define statgraph') then _infile_ = "define statgraph NEWHEATMAP;"; /* optional: assign new template name */ /* use TRANWRD to make substitutions here */ _infile_ = tranwrd(_infile_, 'THREECOLORRAMP', "&NEWCOLORRAMP"); /* replace COLORMODEL= option */ call execute(_infile_); /* push line onto queue; compile after DATA step */ if eof then call execute('run;'); /* add 'RUN;' at end */ run;
The DATA step copies the existing template source, but modifies the template name and the COLORMODEL= option. The CALL EXECUTE statements push each (potentially modified) statement onto a queue that is executed when the DATA step exits. The result is that PROC TEMPLATE compiles a new template and stores it in the WORK.MODTEMP item store. Recall that the TRANWRD function and other string-manipulation functions are case sensitive, so be sure to use the exact capitalization that exists in the template. In other words, this technique requires that you know details of the template that you wish to modify!
If you are using this technique to modify one of the built-in SAS templates that are used by SAS procedure, do not modify the template name.
Inspect the modified template
Before you attempt to use the new template, you might want to confirm that it is correct. The following call to PROC TEMPLATE shows the location and creation date for the new template. The source for the template is displayed in the log so that you can verify that the substitutions were made correctly.
proc template; list NEWHEATMAP / stats=created; /* location of the modified template */ source NEWHEATMAP; /* confirm that substitutions were performed */ run;
Step 5: Render the graph by using the new template
The final step is to call PROC SGRENDER to use the newly created template to create the new graph:
/* 5. Render graph by using new template */ proc sgrender data=Cubic template=NEWHEATMAP; dynamic _X='X' _Y='Y' _Z='Z'; run;
The output demonstrates that the original color ramp ("ThreeColorRamp") has been replaced by a four-color ramp that uses the colors blue, cyan, yellow, and red.
When is this necessary?
You might wonder if the result is worth all this complexity and abstraction. Couldn't I have merely copied the template into a program editor and changed the color ramp? Yes, you can use a program editor to modify a template for your personal use. However, this programming technique enables you to write macros, tasks, and SAS/IML functions that other people can use. For example, you could encapsulate the program in this article into a %CreateHeatmap macro that accepts a color ramp and data set as an argument. Or in SAS/IML you can write modules such as the HEATMAPCONT and HEATMAPDISC subroutines, which enable SAS/IML programmers to specify any color ramp to visualize a matrix.
If you work in a regulated industry and are modifying templates that SAS distributes, this technique provides reproducibility. A reviewer or auditor can run a program to reproduce a graph.
Kuhfeld's template modification technique (TMT) enables you to write a SAS program that modifies an ODS template. The TMT combines many SAS programming techniques and can be difficult to comprehend when you first see it. This article breaks down the technique into five simpler steps. You can use the TMT to edit ODS templates that are distributed by SAS and used in SAS procedures, or (as shown in this article) to change options at run time for a template that you wrote yourself. You can download the SAS program for this article.
Happy Halloween to all my readers. I hope you'll agree that Kuhfeld's TRICKS are a real TREAT!
Thanks for the kind words, Rick! I really like the idea of writing code that creates reproducible results. Thanks for spreading the word! -- Warren