SAS Communities Library

We’re smarter together. Learn from this collection of community knowledge and add your expertise.
BookmarkSubscribeRSS Feed

Creating R Markdown like output in SAS 9 (using SAS EG or other SAS clients).

Started ‎10-19-2024 by
Modified ‎10-19-2024 by
Views 1,414

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:

  1. Create or Open the ODS output file
  2. Repeat the 2 steps below (a and b) for each code block:
    1. Create a macro variable containing the SAS code for the step / code block. 
    2. 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).
  3. 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;
The ODSTEXTparseIt macro:
As described above, the macro takes a single macro variable name as a parameter. The macro variable is assigned the code for the code block that needs to be executed. The macro parses the code block into individual statements and use the "ods text" statement to print individual statements to the ODS output file.
 
The code also attemps to illustrate that it is not very difficult to indent the code to make it easy to read in the output document. While this example does only first level indentation of PROC and DATA step code, it can be expanded to other statement blocks such as do-end block.
 
/* 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;
This macro can be included in the sasautos and the user would only need to know how to invoke it.
 
Output:
Using the code shown above, the following output was created in the pdf format. 
Partial output displayed below. Notice how the code is indented which makes it easy to read.
 
ODSOut1.jpg
 
ODSOut2.jpg
ODSOut3.jpg
 
Conclusion:
As with any good programming language ,there are many methods to do the same thing in SAS. Here we explored the idea of using ODS and a reusable macro to create both the code and output in a single document, similar to the R Markdown document in a simple 4 step process.
 
While this might be trivial for advanced SAS coders, my hope is to encourage beginner coders and my 'point and click' friends to utilize SAS to create RMD like output (as opposed to having to use other clients just for this purpose).
 
If you use a different way to do this, please leave a comment below. I would love to learn about it.
 
Happy coding!
Comments

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.

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 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(%nrstr(&firstword)))"="%str(run)" or
"%str(%lowcase(%nrstr(&firstword)))"="%str(mend)" %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(%nrstr(&firstword)))"="%str(data)" 
or "%str(%lowcase(%nrstr(&firstword)))"="%str(proc)" 
or "%str(%lowcase(%nrstr(&firstword)))"="%nrstr(%macro)" 
%then %do;
%let instep=1;
%end;
 
%if not("%str(%lowcase(%nrstr(&firstword)))"="%str(data)"
or "%str(%lowcase(%nrstr(&firstword)))"="%str(proc)" or 
"%str(%lowcase(%nrstr(&firstword)))"="%str(run)" or 
"%str(%lowcase(%nrstr(&firstword)))"="%nrstr(%macro)") 
and &instep=1 %then %do;
ods text=%quote(".... &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=%quote("&thisline. ;"); /* otherwise don't indent the text */
%end;
 
%end;
/* execute the code contained in the macro variable to create the output */
%unquote(&&&macvarname);
 
%mend ODSTEXTparseIt;
 
The call to this macro should use %nrstr (instead of %str) since you don't want the macro code to be resolved until it is actually executed.
 
%let step3=%nrstr(
%macro printclass;
proc print data=sashelp.class; run;
%mend;
%printclass
);
 
In this example code, I noticed that %m (in %macro) doesn't print in html, so I had to switch to PDF to see the correct text printed in the ODS output (partially shown below).
 
Vj_Govindarajan_0-1740081182062.png
I am pretty sure this will not work in all situations but hopefully provides the idea to extend the concept.
 
Thank you,
Vj
Version history
Last update:
‎10-19-2024 11:00 AM
Updated by:
Contributors

sas-innovate-white.png

Our biggest data and AI event of the year.

Don’t miss the livestream kicking off May 7. It’s free. It’s easy. And it’s the best seat in the house.

Join us virtually with our complimentary SAS Innovate Digital Pass. Watch live or on-demand in multiple languages, with translations available to help you get the most out of every session.

 

Register now!

SAS AI and Machine Learning Courses

The rapid growth of AI technologies is driving an AI skills gap and demand for AI talent. Ready to grow your AI literacy? SAS offers free ways to get started for beginners, business leaders, and analytics professionals of all skill levels. Your future self will thank you.

Get started

Article Tags