Getting started¶
This is an interface module to the SAS System. It connects to SAS 9.4 (released July 2013) or newer and enables Python programmers to take advantage of their licensed SAS infrastructure through Python 3.x.
The interface is designed to enable programmers to use Python syntax and constructs to interact with SAS. The interface makes SAS the analytical engine–or “calculator” for data analysis. In its most simple form, it is a code translator that accepts Python commands and converts them into SAS language statements. The statements are run, and then the results are returned to Python to be displayed or accessed.
This is an open source project. Your contributions are appreciated and encouraged. Please open issues in gitlab for problems that you see!
The rest of this section demonstrates how to use this module with a simple example. The example uses Kaggle Resources Analytics data.
We now have a saspy-examples gityhub repo: https://github.com/sassoftware/saspy-examples
There are a number of example Jupyter notebooks there, some contributed by SAS and others by users. If you have one you’d like to contribute, please open a pull request.
The saspy_example_github.ipynb notebook is a perfect one to look though first, as it walks you through the various capabilities of saspy. You can even download it, and then upload it into your Jupyter and run it. It uses SASHELP tables, so it should run on your system already. You can edit it and play around with it directly on your system. The direct link to it is: https://github.com/sassoftware/saspy-examples/blob/main/SAS_contrib/saspy_example_github.ipynb
Initial import¶
It is assumed you have already done the installation and configuration. If you have not, refer to that section for more information.
import saspy
import pandas as pd
Start a SAS session¶
In the following code we start a SAS session named sas
using the default
configuration. Each SAS session is a connection to a separate SAS instance.
The cfgname parameter specifies the configuration definition (in sascfg_personal.py)
to use for the connection to SAS.
If sascfg_personal.py has only one connection definition, then you do not need to specify the cfgname parameter. If the file has more than one connection definition and you do not specify the one to use with cfgname, you are prompted for the connection to use.
After a connection is made and a SAS session is started, a note that is similar to the the one below is displayed.
sas = saspy.SASsession(cfgname='default')
SAS Connection established. Subprocess id is 28207
Any of the keys in the configuration definition can be supplied as parameters on the SASsession() method. This allows you to change these values at run time, without having to edit the configuration file. However, whether you are allowed to override keys that are defined in the config def, is controlled by one of the configuration options (also in the sascfg[_personal].py); lock_down. If lock_down is True, the only keys you can supply as parameters on SASsession() are ones that are not specified in the config def. If lock_down is False, any key can be specified on the SASsession() method.
For instance, with lock_down set to False and the following config def, you can override things at run time:
ssh = {'saspath' : '/opt/sasinside/SASHome/SASFoundation/9.4/bin/sas_u8',
'ssh' : '/usr/bin/ssh',
'host' : 'remote.linux.host',
'options' : ["-fullstimer"]
}
sas = saspy.SASsession(cfgname='ssh',
options=["-fullstimer", "-autoexec", "/home/my.autoexec.sas"],
host="'other.host.with.sas")
Load data into SAS¶
Data can be loaded easily from many sources. The following examples show
the most common methods. In each case, hr
is a SASdata object that
represents a SAS data set.
CSV¶
In the following example, the CSV file is accessible to Python. The
sas
object reads the CSV file and creates a SAS data set in the
SAS session.
hr = sas.read_csv("./HR_comma_sep.csv")
Pandas DataFrame¶
In the following example, the CSV file is accessible to Python. First,
the CSV file is read into a data frame. Then the sas
object
reads the data frame and creates a SAS data set in the SAS session.
hr_pd = pd.read_csv("./HR_comma_sep.csv")
hr = sas.df2sd(hr_pd) # the short form of: hr = sas.dataframe2sasdata(hr_pd)
Existing SAS data set¶
In the following example, no data file is accessible to Python. An existing
SAS data set that is accessible to the SAS session is associated with the
hr
object.
hr = sas.sasdata('hr', 'mylibref')
# or simply: hr = sas.sasdata('hr')
# ...if hr.sas7bdat is in your 'work' or 'user' library
Explore the data¶
There are a number of tabular and graphical methods to view your data. The following examples show common methods. See the API Reference for a complete list.
List the variables¶
hr.columnInfo()
See the first observations¶
hr.head()
Summary of numeric columns¶
hr.means()
Basic bar chart¶
hr.bar('salary')
Basic histogram¶
hr.hist('last_evaluation')
Basic heatmap¶
hr.heatmap('last_evaluation', 'satisfaction_level')
Submit SAS code directly from Python session¶
The proceeding examples demonstrate commonly used Python methods that are available with this module.
If you encounter a situation where you need to submit SAS statements directly to the SAS system, you can use any of the 3 submit* methods to accomplish that. The following example creates a side-by-side panel plot to compare employees who have left versus employees still working at the company, based on their business unit, median performance rating, and satisfaction level.
The submit method returns a dictionary with two keys: LOG and LST. You can print() the LOG and sas.HTML() the LST. Or you can use the submitLST() method or the submitLOG() methods which automatically render the respective output for you.
c = sas.submitLST("""
proc sgpanel data=work._csv;
PANELBY left;
hbar sales / response=last_evaluation stat=median;
hbar sales / response=satisfaction_level stat=median;
run;
""")
Split the data into training and test¶
Partitioning data is essential to avoid overfitting during model development. This can be achieved using the partition method. In this example, the data is partitioned in-place and performs stratified sampling, based on the variable ‘left.’ If you do not specify a variable or the variable is an interval, then simple random sampling (SRS) is done.
We create two partitions: test and training.
hr.partition('left')
hr_train = hr.where('_PartInd_=1')
hr_test = hr.where('_PartInd_=0')
Build an analytical model¶
One of the key activities for this module is analytical modeling. The SAS system is capable of modeling in a number of distinct areas (statistics, machine learning, econometric time series, and so on).
These capabilities are organized similarly to make it easier for users. Grouping functionality also avoids cluttered tab-complete lists with methods that you might not have licensed.
The session object has methods to create an instance for each supported product.
STAT (SAS/STAT)
ETS (SAS/ETS)
Machine learning (SAS Enterprise Miner)
QC (SAS/QC)
UTIL (SAS Base procedures)
Here is a code example to create an object for each product:
stat = sas.sasstat()
ml = sas.sasml()
ets = sas.sasets()
qc = sas.sasqc()
util = sas.sasutil()
Each of these objects contains a set of methods that perform analytical functions, namely modeling. These methods closely follow the SAS procedures for naming and organization.
Note
The existing list of methods is not an exhaustive list of the SAS procedures that are available with each product. Please consider contributing the methods you’ve written to do your work.
The API Reference documentation shows how to add a method that corresponds to a SAS procedure. The API has a complete list of methods for each object.
You can use the dir()
function to see a list of the available methods.
For example, dir(stat)
provides a list of the methods that are available.
Not all of the methods correspond to a procedure but the vast majority do.
dir(stat)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__',
'__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__',
'glm',
'hplogistic',
'hpreg',
'hpsplit',
'logger',
'logistic',
'mixed',
'reg',
'sas',
'sasproduct',
'tpspline']
To build a model, you need to supply the required parameters to the modeling method. I’ll start with an example then explain the syntax. We’ll continue using the HR data from above.
t1='left'
inputs = {
'nominal':['work_accident','promotion_last_5years','sales','salary'],
'interval':['satisfaction_level','last_evaluation','number_project','average_montly_hours','time_spend_company']
}
rf_model = ml.forest(data=hr, target=t1, input=inputs)
The preceding code creates two variables:
- t1
A string that represents the target variable.
- inputs
A dictionary that represents the model inputs with two keys–interval and nominal–which represent the interval and nominal variables respectively to consider for modeling.
Here is another way to specify the same as above–using the nominal parameter and a list of inputs. The target variable is now a dictionary.
t1={'nominal':'left'}
nom = ['work_accident', 'promotion_last_5years', 'sales', 'salary']
inputs =['work_accident', 'promotion_last_5years', 'sales', 'salary', 'satisfaction_level',
'last_evaluation', 'number_project', 'average_montly_hours', 'time_spend_company']
rf_model = ml.forest(data=hr, target=t1, input=inputs, nominals = nom)
Here is another way–using the nominal parameter and a string of inputs. The target is a list (nominals must be a list).
t1=['left']
nom = ['work_accident', 'promotion_last_5years', 'sales', 'salary', 'left']
inputs ='work_accident promotion_last_5years sales salary satisfaction_level last_evaluation number_project
average_montly_hours time_spend_company'
rf_model = ml.forest(data=hr, target=t1, input=inputs, nominals = nom)
More about the target and input parameters¶
These rules apply to both target and input:
The parameters accept strings (str), lists (list), or dictionaries (dict) types.
The target and input parameters are modified by a nominals parameter to identify the proper variables treatment.
The nominals parameter must be a list type or you receive a syntax warning.
Variables are treated as nominals if any of the following are met:
The variable is a character type in SAS.
The variable is specified in the nominals list.
The variable is paired with dictionary key
'nominal'
.
Note
If a variable is a SAS character type then it does not need to be
specified in the nominals parameter but does need to be assigned
to the 'nominal'
dictionary key if you use the dictionary
object type.
Evaluating model diagnostics¶
Perhaps the most important part of modeling is evaluating the quality of the model. This is made very easy by leveraging the rich graphical and tabular output of SAS ODS.
The output of a model in is a SASresults
object. It contains all
the ODS tables and graphics that were produced by the SAS procedure. You can
see all the available objects by using dir()
or tab-complete on the object.
dir(rf_model)
The returned list shows the available diagnostic output for this model. The output lists vary slightly, depending on the modeling algorithm, the settings, and the target type (nominal or interval).
['BASELINE',
'DATAACCESSINFO',
'FITSTATISTICS',
'LOG',
'MODELINFO',
'NOBS',
'PERFORMANCEINFO',
'VARIABLEIMPORTANCE']
To view a particular diagnostic, submit it as shown below. The default objects
for tables are Pandas DataFrames and for plots are HTML graphics. You can use
use the results
option to choose HTML for tables too, if you choose.
rf_model.FITSTATISTICS
Note
If an error occurred during processing, the only artifact is ERROR_LOG. This object contains the SAS log to aid you in resolving your error.
Below is an example where the variable name left is typed incorrectly as lefty.
rf_model = ml.forest(data=hr, target='lefty', input=inputs, nominals = nom)
SubmissionError: ERRORS found in SAS log:
ERROR: Variable LEFTY not found.
We can see a brief detail of the error but if more context is needed, you can see the entire log for the model submission with code like the following:
rf_model.ERROR_LOG