Running SAS in Quarto. Part 2

An earlier post demonstrated how the jupyter SAS kernel could be used to run SAS from within a Quarto document. This post demonstrates the saspy library and how SAS can be incorporated into Quarto, ultimately passing data to an interactive observable visualization.

Setup and Configure

saspy needs to be installed and configured. See the previous post for details on configuring sascfg_personal.py and _authinfo files.

Quarto qmd Document

The saspy library can be used to communicate between python and sas. It works within Quarto python chunks without requiring the SAS kernel. Below is an example document which demonstrates how to call SAS code directly within a python chunk, how to execute SAS commands from within a python chunk and how to pass data from SAS to python. Once data have been passed from SAS to python we have the opportunity of using observable to render interactive visualizations.

  1---
  2title: "Quarto Demo with SAS"
  3format: html
  4self-contained: true
  5---
  6
  7A simple demo of using SAS in Quarto.  In this document we demonstrate the following:
  8
  9-  Running SAS code directly
 10-  Importing a SAS dataset
 11-  Executing SAS commands using saspy
 12-  Converting SAS dataset to a Pandas DataFrame
 13-  Interactive visualization using observable 
 14
 15# Setup
 16
 17saspy is required to run SAS commands from python/jupyter.
 18
 19```{python}
 20import saspy
 21import pandas as pd
 22
 23sas = saspy.SASsession()
 24```
 25
 26# Running SAS code directly from the notebook
 27
 28The `sas.submit()` method can be used to submit SAS code directly.  The method returns a dictionary with two keys, _LOG_ and _LST_.  The _LOG_ can simply be printed and the _LST_ rendered as HTML using `sas.HTML()`.
 29
 30```{python}
 31#| echo: true
 32#| results: asis
 33#| warning: false
 34
 35ll = sas.submit("""
 36libname work list;
 37
 38proc sql;
 39   select type, count(*) as 'number of models'n, avg(MPG_city) as 'Avg(MPG_City)'n
 40   from sashelp.cars
 41   group by type
 42   order by 3 desc;
 43quit; 
 44""")
 45
 46```
 47
 48### keys
 49list of dictionary keys returned by SAS
 50```{python}
 51ll.keys()
 52```
 53
 54### LST
 55LST output
 56```{python}
 57sas.HTML(ll['LST'])
 58```
 59
 60Alternatively, the _LST_ can be rendered automatically using the `sas.submitLST()` method
 61
 62```{python}
 63#| echo: true
 64#| results: asis
 65#| warning: false
 66
 67ll = sas.submitLST("""
 68libname work list;
 69
 70proc sql;
 71   select type, count(*) as 'number of models'n, avg(MPG_city) as 'Avg(MPG_City)'n
 72   from sashelp.cars
 73   group by type
 74   order by 3 desc;
 75quit; 
 76""")
 77
 78```
 79
 80# Working with SAS and python together
 81
 82## Importing data from SAS
 83```{python}
 84data_sas = sas.sasdata('cars', 'sashelp')
 85```
 86
 87## Running SAS PROC MEANS from Quarto
 88```{python}
 89data_sas.means()
 90```
 91
 92## A simple bar chart (from SAS)
 93```{python}
 94data_sas.bar('EngineSize')
 95```
 96
 97## Converting the data to a Pandas DataFrame
 98```{python}
 99data_pd = data_sas.to_df()
100data_pd.describe()
101```
102
103# Interactive visualization with observable
104
105SAS -> python -> observable   
106Now that our data is available in python we can use the `ojs_define` function to make it available to observable.
107
108```{python}
109#| warning: false
110# make data available to observable
111ojs_define(ojs_data = data_pd)
112```
113
114The observabale code to generate the plot below is included as folded code.
115
116```{ojs}
117//| code-fold: true
118// create an interactive input, limiting MPG_City
119viewof MPG_City = Inputs.range(
120  [10, 60], 
121  {value: 10, step: 1, label: "MPG City"}
122)
123
124// filter data based on the value based on the MPG_City input
125filtered_data = transpose(ojs_data).filter(function(car) {
126  return MPG_City < car.MPG_City;
127})
128```
129
130
131```{ojs}
132//| code-fold: true
133// build a dotplot from the filtered data
134plt = Plot.plot({
135    marks: [
136        Plot.dot(filtered_data, {x: "EngineSize", y: "Horsepower"})
137    ]
138})
139```

Output