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```