Creating R Markdown like output in SAS 9 (using SAS EG or other SAS clients).
- Article History
- RSS Feed
- Mark as New
- Mark as Read
- Bookmark
- Subscribe
- Printer Friendly Page
- Report Inappropriate Content
Problem Statement:
Recently I was asked by a SAS customer if there was a way to create R Markdown like output in SAS. They liked the ease of creating R Markdown documents but wanted to code and execute in SAS EG. When I enquired more about the ask, it boiled down to creating a PDF output with a block of code followed by its output, the next block of code and its output and so on.
I wasn't sure, so I looked around a bit. There were options to create R Markdown documents for SAS code from Jupyter Notebook and Visual Studio Code. There is the SASMarkdown package that you can use in R Studio.
In terms of creating R Markdown like output in SAS EG, there was not a whole lot of information available. There was this discussion here on communities about using proc document but it had limitations. I ventured into the rabbit hole to see if something could be cooked up.
Following the R setup:
If idea is to mimic an R Markdown output, we could certainly mimic the way the R code is set up - as blocks of code! If there are 5 blocks of code, the R Markdown output will have each code block and the respective output sequentially displayed.
The SAS process:
Using SAS ODS, we can easily create the output in PDF or HTML format which seem to be the most popular knitted output for an RMD file. However, there is no native way to include the code being submitted and this is where we will need to get a bit creative.
In order to minimize the amount of work the SAS user would need to perform, we can create a macro program that will take as input, the block of code (as a macro variable), and print the code and the output to an ODS document. So the user will have to invoke the macro as many times as there are blocks of code to execute.
This would leave us with the following steps:
- Create or Open the ODS output file
- Repeat the 2 steps below (a and b) for each code block:
- Create a macro variable containing the SAS code for the step / code block.
- Call the macro program and pass the macro variable name as input. (this is used to print the code and output into the ODS output document).
- Close the ODS file.
Sample Code (that implements the process described above):
/* STEP 1 *//* Define ODS related paths and files */
%let pdfOut=%str(c:\temp\SASODSOUT.pdf);
%let htmlPath=%str(c:\temp);
%let gpath=%str(c:\temp);
/* STEP 2 *//* set up the macro variables for each code block */
/* this example uses 2 blocks of code - therefore 2 macro variables are set up */
/* 1. Create macro variables containing the SAS code for each step. */
/* code block 1 */
%let step1=%str(
data class;
set sashelp.class;
bmi=weight*702/(height*height);
run;
proc print data=class;
run;
);
/*code block 2*/
%let step2=%str(
proc reg data=sashelp.class;
model weight = height age;
output out=reg_results r=residual p=predicted;
run;
proc sgplot data=reg_results;
scatter x=predicted y=weight;
refline 0 / axis=y lineattrs=(pattern=shortdash);
xaxis label="Predicted Weight";
yaxis label="Actual Weight";
title %str(Scatter Plot of Predicted vs Actual Weight);
run;);
/* STEP 3 */
/* Call the macro and pass the macro variable name */
Title "SASHELP.CLASS with BMI";
/* call the ODSTEXTparseit macro for first block of code - the macro is reviewed in the next section below */
%ODSTEXTparseIt(step1)
Title "";
/*Repeat for the next code block*/
%ODSTEXTparseIt(step2)
/* STEP 4 */
/* Close the ODS output */
ods html close;
ods pdf close;
ODSTEXTparseIt
macro:/* The macro - takes the macro variable name as input */
%macro ODSTEXTparseIt(macvarname);
/* parse the code in the macro variable into separate lines of code */
/* print each line using ods text */
/* this code also creates a first level indentation of the code while printing to ODS */
%let totalCount=%sysfunc(countw(%str(&&&macvarname.),';')); /* count number of semi colons to get the number of SAS Statements */
%let instep=0; /* keeps track of whether we are in a data step or a proc - to indent one level */
%do ictr=1 %to &totalCount.; /* loop over number of statements */
%let thisline=%qscan(%str(&&&macvarname.),&ictr,';'); /* parse each line */
%let firstword=%qscan(%str(&thisline.),1,' '); /* get the first word of each line to see if it is data or proc or run statement */
%if "%str(%lowcase(&firstword))"="%str(run)" %then %let instep=0; /* if its run, then we are not in a step any more */
/* if it is data or proc, then we are entering a step */
%if "%str(%lowcase(&firstword))"="%str(data)"
or "%str(%lowcase(&firstword))"="%str(proc)"
%then %do;
%let instep=1;
%end;
%if not("%str(%lowcase(&firstword))"="%str(data)"
or "%str(%lowcase(&firstword))"="%str(proc)" or
"%str(%lowcase(&firstword))"="%str(run)")
and &instep=1 %then %do;
ods text=".... &thisline. ;"; /* if in a step but the line is not the data or proc statement itself, then indent the text */
%end;
%else %do;
ods text="&thisline. ;"; /* otherwise don't indent the text */
%end;
%end;
/* execute the code contained in the macro variable to create the output */
&&&macvarname;
%mend ODSTEXTparseIt;
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Hi,
This is great initiative.
However, it did not work in scenario when a let statement is declared.
For instance, if I create a macro today date as follows;
%let todaysdate=%sysfunc(today(),date9.); (or if we have created a %macro name()....%mend name)
and pass it in a step 3 ,it does not work . (I was expecting to see that written in result window).
I tried some modifications of your ODSTEXTparseIt macro but could not make it to work.
Thank you for sharing this work.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Hi @sascode
Thank you for the feedback. My original intent was to illustrate the possibility of a Markdown like document in SAS but as you rightly pointed out it is not a complete program by any means.
However, I made some modifications to include macro code and this seems to work.
Here is the updated macro code.
%macro printclass;
proc print data=sashelp.class; run;
%mend;
%printclass
);