import saspy
import pandas as pd
sas = saspy.SASsession()Using SAS Config named: oda
SAS Connection established. Subprocess id is 15284
A simple demo of using SAS in Quarto. In this document we demonstrate the following:
saspy is required to run SAS commands from python/jupyter.
import saspy
import pandas as pd
sas = saspy.SASsession()Using SAS Config named: oda
SAS Connection established. Subprocess id is 15284
The 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().
ll = sas.submit("""
libname work list;
proc sql;
select type, count(*) as 'number of models'n, avg(MPG_city) as 'Avg(MPG_City)'n
from sashelp.cars
group by type
order by 3 desc;
quit;
""")list of dictionary keys returned by SAS
ll.keys()dict_keys(['LOG', 'LST'])
LST output
sas.HTML(ll['LST'])| Type | number of models | Avg(MPG_City) |
|---|---|---|
| Hybrid | 3 | 55 |
| Wagon | 30 | 21.1 |
| Sedan | 262 | 21.08397 |
| Sports | 49 | 18.40816 |
| Truck | 24 | 16.5 |
| SUV | 60 | 16.1 |
Alternatively, the LST can be rendered automatically using the sas.submitLST() method
ll = sas.submitLST("""
libname work list;
proc sql;
select type, count(*) as 'number of models'n, avg(MPG_city) as 'Avg(MPG_City)'n
from sashelp.cars
group by type
order by 3 desc;
quit;
""")| Type | number of models | Avg(MPG_City) |
|---|---|---|
| Hybrid | 3 | 55 |
| Wagon | 30 | 21.1 |
| Sedan | 262 | 21.08397 |
| Sports | 49 | 18.40816 |
| Truck | 24 | 16.5 |
| SUV | 60 | 16.1 |
data_sas = sas.sasdata('cars', 'sashelp')data_sas.means()| Variable | Label | N | NMiss | Median | Mean | StdDev | Min | P25 | P50 | P75 | Max | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | MSRP | NaN | 428.0 | 0.0 | 27635.0 | 32774.855140 | 19431.716674 | 10280.0 | 20329.50 | 27635.0 | 39215.0 | 192465.0 |
| 1 | Invoice | NaN | 428.0 | 0.0 | 25294.5 | 30014.700935 | 17642.117750 | 9875.0 | 18851.00 | 25294.5 | 35732.5 | 173560.0 |
| 2 | EngineSize | Engine Size (L) | 428.0 | 0.0 | 3.0 | 3.196729 | 1.108595 | 1.3 | 2.35 | 3.0 | 3.9 | 8.3 |
| 3 | Cylinders | NaN | 426.0 | 2.0 | 6.0 | 5.807512 | 1.558443 | 3.0 | 4.00 | 6.0 | 6.0 | 12.0 |
| 4 | Horsepower | NaN | 428.0 | 0.0 | 210.0 | 215.885514 | 71.836032 | 73.0 | 165.00 | 210.0 | 255.0 | 500.0 |
| 5 | MPG_City | MPG (City) | 428.0 | 0.0 | 19.0 | 20.060748 | 5.238218 | 10.0 | 17.00 | 19.0 | 21.5 | 60.0 |
| 6 | MPG_Highway | MPG (Highway) | 428.0 | 0.0 | 26.0 | 26.843458 | 5.741201 | 12.0 | 24.00 | 26.0 | 29.0 | 66.0 |
| 7 | Weight | Weight (LBS) | 428.0 | 0.0 | 3474.5 | 3577.953271 | 758.983215 | 1850.0 | 3103.00 | 3474.5 | 3978.5 | 7190.0 |
| 8 | Wheelbase | Wheelbase (IN) | 428.0 | 0.0 | 107.0 | 108.154206 | 8.311813 | 89.0 | 103.00 | 107.0 | 112.0 | 144.0 |
| 9 | Length | Length (IN) | 428.0 | 0.0 | 187.0 | 186.362150 | 14.357991 | 143.0 | 178.00 | 187.0 | 194.0 | 238.0 |
data_sas.bar('EngineSize')data_pd = data_sas.to_df()
data_pd.describe()| MSRP | Invoice | EngineSize | Cylinders | Horsepower | MPG_City | MPG_Highway | Weight | Wheelbase | Length | |
|---|---|---|---|---|---|---|---|---|---|---|
| count | 428.000000 | 428.000000 | 428.000000 | 426.000000 | 428.000000 | 428.000000 | 428.000000 | 428.000000 | 428.000000 | 428.000000 |
| mean | 32774.855140 | 30014.700935 | 3.196729 | 5.807512 | 215.885514 | 20.060748 | 26.843458 | 3577.953271 | 108.154206 | 186.362150 |
| std | 19431.716674 | 17642.117750 | 1.108595 | 1.558443 | 71.836032 | 5.238218 | 5.741201 | 758.983215 | 8.311813 | 14.357991 |
| min | 10280.000000 | 9875.000000 | 1.300000 | 3.000000 | 73.000000 | 10.000000 | 12.000000 | 1850.000000 | 89.000000 | 143.000000 |
| 25% | 20334.250000 | 18866.000000 | 2.375000 | 4.000000 | 165.000000 | 17.000000 | 24.000000 | 3104.000000 | 103.000000 | 178.000000 |
| 50% | 27635.000000 | 25294.500000 | 3.000000 | 6.000000 | 210.000000 | 19.000000 | 26.000000 | 3474.500000 | 107.000000 | 187.000000 |
| 75% | 39205.000000 | 35710.250000 | 3.900000 | 6.000000 | 255.000000 | 21.250000 | 29.000000 | 3977.750000 | 112.000000 | 194.000000 |
| max | 192465.000000 | 173560.000000 | 8.300000 | 12.000000 | 500.000000 | 60.000000 | 66.000000 | 7190.000000 | 144.000000 | 238.000000 |
SAS -> python -> observable
Now that our data is available in python we can use the ojs_define function to make it available to observable.
# make data available to observable
ojs_define(ojs_data = data_pd)The observabale code to generate the plot below is included as folded code.
viewof MPG_City = Inputs.range(
[10, 60],
{value: 10, step: 1, label: "MPG City"}
)
// filter data based on the value based on the MPG_City input
filtered_data = transpose(ojs_data).filter(function(car) {
return MPG_City < car.MPG_City;
})plt = Plot.plot({
marks: [
Plot.dot(filtered_data, {x: "EngineSize", y: "Horsepower"})
]
})