Getting Started¶
Before you can use the SWAT package, you will need a running CAS server. The SWAT package can connect to either the binary port or the HTTP port. If you have the option of either, the binary protocol will give you better performance.
Other than the CAS host and port, you just need a user name and password to connect. User names and passwords can be implemented in various ways, so you may need to see your system administrator on how to acquire an account.
To connect to a CAS server, you simply import SWAT and use the swat.CAS class to create a connection. This has a couple of different forms. The most basic is to pass the hostname, port, username, and password.
In [1]: import swat
In [2]: conn = swat.CAS(host, port, username, password)
However, if you are using a REST connection to CAS, a URL is the more natural way to specify a host, port, and protocol.
In [3]: conn = swat.CAS('https://my-cas-host.com:443/cas-shared-default-http/',
...: username='...', password='...')
...:
Notice that in the URL case, username and password, must be specified as keyword parameters since the port parameter is being skipped. Also, in this case we are using a proxy server that requires the base path of ‘cas-shared-default-http’. If you are connecting directly to a CAS server, this is typically not required.
Now that we have a connection to CAS, we can run some actions on it.
Running CAS Actions¶
To test your connection, you can run the serverstatus action.
In [4]: out = conn.serverstatus()
Note: Grid node action status report: 1 nodes, 8 total actions executed. In [5]: out Out[5]: [About] {'CAS': 'Cloud Analytic Services', 'Version': '4.00', 'VersionLong': 'V.04.00M0D12042024', 'Copyright': 'Copyright © 2014-2024 SAS Institute Inc. All Rights Reserved.', 'ServerTime': '2024-12-05T16:04:23Z', 'System': {'Hostname': 'snap016', 'OS Name': 'Linux', 'OS Family': 'LIN X64', 'OS Release': '2.6.32-358.2.1.el6.x86_64', 'OS Version': '#1 SMP Wed Feb 20 12:17:37 EST 2013', 'Model Number': 'x86_64', 'Linux Distribution': 'Red Hat Enterprise Linux Server release 6.2 (Santiago)'}, 'Documentation': 'http://mycompany.com:8080/job/Actions_ref_doc/ws/casaref/index.html', 'license': {'site': 'SAS Institute Inc.', 'siteNum': 1, 'expires': '30Jan2025:00:00:00', 'gracePeriod': 62, 'warningPeriod': 31}, 'CASHostAccountRequired': 'OPTIONAL', 'Transferred': 'NO', 'CASCacheLocation': 'CAS Disk Cache'} [server] Server Status nodes actions 0 1 8 [nodestatus] Node Status name role uptime running stalled 0 snap016 controller 0.248 0 0 + Elapsed: 0.00131s, user: 0.002s, mem: 0.306mb
Handling the Output¶
All CAS actions return a CASResults object. This is simply an ordered Python dictionary with a few extra methods and attributes added. In the output above, you’ll see the keys of the dictionary surrounded in square brackets. They are ‘About’, ‘server’, and ‘nodestatus’. Since this is a dictionary, you can just use the standard way of accessing keys.
In [6]: out['nodestatus']
Out[6]:
Node Status
name role uptime running stalled
0 snap016 controller 0.248 0 0
In addition, you can access the keys as attributes. This convenience was added to keep your code looking a bit cleaner. However, be aware that if the name of a key collides with a standard Python attribute or method, you’ll get that attribute or method instead. So this form is fine for interactive programming, but you may want to use the syntax above for actual programs.
In [7]: out.nodestatus
Out[7]:
Node Status
name role uptime running stalled
0 snap016 controller 0.248 0 0
The types of the result keys can vary as well. In this case, the ‘About’ key holds a dictionary. The ‘server’ and ‘nodestatus’ keys hold SASDataFrame objects (a subclass of pandas.DataFrame).
In [8]: for key, value in out.items():
...: print(key, type(value))
...:
About <class 'dict'>
server <class 'swat.SASDataFrame'>
nodestatus <class 'swat.SASDataFrame'>
Since the values in the result are standard Python (and pandas) objects, you can work with them as you normally do.
In [9]: out.nodestatus.role Out[9]: 0 controller Name: role, dtype: object In [10]: out.About['Version'] Out[10]: '4.00'
Simple Statistics¶
We can’t have a getting started section without doing some sort of statistical analysis. First, we need to see what CAS action sets are loaded. We can get a listing of all of the action sets and actions using the help CAS action. If you run help without any arguments, it will display all of the loaded actions and their descriptions. Rather than printing that large listing, we’ll specifically ask for the simple action set since we already know that’s the one we want.
In [11]: conn.help(actionset='simple');
Let’s start with the summary action. Of course, we first need to load some data. The simplest way to load data is to do it from the client side. Note that while this is the simplest way, it’s probably not the best way for large data sets. Those should be loaded from the server side if possible.
The CAS.read_csv() method works just like the pandas.read_csv() function. In fact, CAS.read_csv() uses pandas.read_csv() in the background. When pandas.read_csv() finishes parsing the CSV file into a pandas.DataFrame, it gets uploaded to a CAS table by CAS.read_csv(). The returned object is a CASTable object.
In [12]: tbl = conn.read_csv('https://raw.githubusercontent.com/'
....: 'sassoftware/sas-viya-programming/master/data/cars.csv')
....:
Note: Cloud Analytic Services made the uploaded file available as table TMPV59RWIM1 in caslib CASUSER(castest).
Note: The table TMPV59RWIM1 has been created in caslib CASUSER(castest) from binary data uploaded to Cloud Analytic Services.
CASTable objects are essentially client-side views of the table of data in the CAS server. You can interact with them using CAS actions as well as many of the pandas.DataFrame methods and attributes. The pandas.DataFrame API is mirrored as much as possible, the only difference is that behind-the-scenes the real work is being done by CAS.
If you don’t want the difficult-to-read generated name for a table, you can specify one using the casout= parameter.
In [13]: tbl = conn.read_csv('https://raw.githubusercontent.com/'
....: 'sassoftware/sas-viya-programming/master/data/cars.csv',
....: casout='cars')
....:
Note: Cloud Analytic Services made the uploaded file available as table CARS in caslib CASUSER(castest).
Note: The table CARS has been created in caslib CASUSER(castest) from binary data uploaded to Cloud Analytic Services.
Since we started down this path with the intent to use the summary action, let’s do that first.
In [14]: out = conn.summary(table=tbl)
In [15]: out
Out[15]:
[Summary]
Descriptive Statistics for CARS
Column Min Max N NMiss Mean Sum Std StdErr Var USS CSS CV TValue ProbT Skewness Kurtosis
0 MSRP 10280.0 192465.0 428.0 0.0 32774.855140 14027638.0 19431.716674 939.267478 3.775916e+08 6.209854e+11 1.612316e+11 59.288490 34.894059 4.160412e-127 2.798099 13.879206
1 Invoice 9875.0 173560.0 428.0 0.0 30014.700935 12846292.0 17642.117750 852.763949 3.112443e+08 5.184789e+11 1.329013e+11 58.778256 35.196963 2.684398e-128 2.834740 13.946164
2 EngineSize 1.3 8.3 428.0 0.0 3.196729 1368.2 1.108595 0.053586 1.228982e+00 4.898540e+03 5.247754e+02 34.679034 59.656105 3.133745e-209 0.708152 0.541944
3 Cylinders 3.0 12.0 426.0 2.0 5.807512 2474.0 1.558443 0.075507 2.428743e+00 1.540000e+04 1.032216e+03 26.834946 76.913766 1.515569e-251 0.592785 0.440378
4 Horsepower 73.0 500.0 428.0 0.0 215.885514 92399.0 71.836032 3.472326 5.160415e+03 2.215110e+07 2.203497e+06 33.275059 62.173176 4.185344e-216 0.930331 1.552159
5 MPG_City 10.0 60.0 428.0 0.0 20.060748 8586.0 5.238218 0.253199 2.743892e+01 1.839580e+05 1.171642e+04 26.111777 79.229235 1.866284e-257 2.782072 15.791147
6 MPG_Highway 12.0 66.0 428.0 0.0 26.843458 11489.0 5.741201 0.277511 3.296139e+01 3.224790e+05 1.407451e+04 21.387709 96.729204 1.665621e-292 1.252395 6.045611
7 Weight 1850.0 7190.0 428.0 0.0 3577.953271 1531364.0 758.983215 36.686838 5.760555e+05 5.725125e+09 2.459757e+08 21.212776 97.526890 5.812547e-294 0.891824 1.688789
8 Wheelbase 89.0 144.0 428.0 0.0 108.154206 46290.0 8.311813 0.401767 6.908624e+01 5.035958e+06 2.949982e+04 7.685150 269.196577 0.000000e+00 0.962287 2.133649
9 Length 143.0 238.0 428.0 0.0 186.362150 79763.0 14.357991 0.694020 2.061519e+02 1.495283e+07 8.802687e+04 7.704349 268.525733 0.000000e+00 0.181977 0.614725
+ Elapsed: 0.0149s, user: 0.013s, sys: 0.002s, mem: 4.54mb
In addition, you can also call the summary action directly on the CASTable object. It will automatically populate the table= parameter.
In [16]: out = tbl.summary()
In [17]: out
Out[17]:
[Summary]
Descriptive Statistics for CARS
Column Min Max N NMiss Mean Sum Std StdErr Var USS CSS CV TValue ProbT Skewness Kurtosis
0 MSRP 10280.0 192465.0 428.0 0.0 32774.855140 14027638.0 19431.716674 939.267478 3.775916e+08 6.209854e+11 1.612316e+11 59.288490 34.894059 4.160412e-127 2.798099 13.879206
1 Invoice 9875.0 173560.0 428.0 0.0 30014.700935 12846292.0 17642.117750 852.763949 3.112443e+08 5.184789e+11 1.329013e+11 58.778256 35.196963 2.684398e-128 2.834740 13.946164
2 EngineSize 1.3 8.3 428.0 0.0 3.196729 1368.2 1.108595 0.053586 1.228982e+00 4.898540e+03 5.247754e+02 34.679034 59.656105 3.133745e-209 0.708152 0.541944
3 Cylinders 3.0 12.0 426.0 2.0 5.807512 2474.0 1.558443 0.075507 2.428743e+00 1.540000e+04 1.032216e+03 26.834946 76.913766 1.515569e-251 0.592785 0.440378
4 Horsepower 73.0 500.0 428.0 0.0 215.885514 92399.0 71.836032 3.472326 5.160415e+03 2.215110e+07 2.203497e+06 33.275059 62.173176 4.185344e-216 0.930331 1.552159
5 MPG_City 10.0 60.0 428.0 0.0 20.060748 8586.0 5.238218 0.253199 2.743892e+01 1.839580e+05 1.171642e+04 26.111777 79.229235 1.866284e-257 2.782072 15.791147
6 MPG_Highway 12.0 66.0 428.0 0.0 26.843458 11489.0 5.741201 0.277511 3.296139e+01 3.224790e+05 1.407451e+04 21.387709 96.729204 1.665621e-292 1.252395 6.045611
7 Weight 1850.0 7190.0 428.0 0.0 3577.953271 1531364.0 758.983215 36.686838 5.760555e+05 5.725125e+09 2.459757e+08 21.212776 97.526890 5.812547e-294 0.891824 1.688789
8 Wheelbase 89.0 144.0 428.0 0.0 108.154206 46290.0 8.311813 0.401767 6.908624e+01 5.035958e+06 2.949982e+04 7.685150 269.196577 0.000000e+00 0.962287 2.133649
9 Length 143.0 238.0 428.0 0.0 186.362150 79763.0 14.357991 0.694020 2.061519e+02 1.495283e+07 8.802687e+04 7.704349 268.525733 0.000000e+00 0.181977 0.614725
+ Elapsed: 0.0141s, user: 0.013s, sys: 0.001s, mem: 4.52mb
Again, the output is a CASResults object (a subclass of a Python dictionary), so we can pull off the keys we want (there is only one in this case). This key contains a SASDataFrame, but since it’s a subclass of pandas.DataFrame, you can do all of the standard DataFrame operations on it.
In [18]: summ = out.Summary
In [19]: summ = summ.set_index('Column')
In [20]: summ.loc['Cylinders', 'Max']
Out[20]: 12.0
Loading CAS Action Sets¶
While CAS comes with a few pre-loaded action sets, you will likely want to load action sets with other capabilities such as percentiles, Data step, SQL, or even machine learning. Most action sets will require a license to run them, so you’ll have to take care of those issues before you can load them.
The action used to load action sets is called loadactionset.
In [21]: conn.loadactionset('percentile')
Note: Added action set 'percentile'.
Out[21]:
[actionset]
'percentile'
+ Elapsed: 0.00078s, user: 0.001s, mem: 0.203mb
Once you load an action set, its actions will be automatically added as methods to the CAS connection and any CASTable objects associated with that connection.
In [22]: tbl.percentile()
Out[22]:
[Percentile]
Percentiles for CARS
Variable Pctl Value Converged
0 MSRP 25.0 20329.50 1.0
1 MSRP 50.0 27635.00 1.0
2 MSRP 75.0 39215.00 1.0
3 Invoice 25.0 18851.00 1.0
4 Invoice 50.0 25294.50 1.0
5 Invoice 75.0 35732.50 1.0
6 EngineSize 25.0 2.35 1.0
7 EngineSize 50.0 3.00 1.0
8 EngineSize 75.0 3.90 1.0
9 Cylinders 25.0 4.00 1.0
10 Cylinders 50.0 6.00 1.0
11 Cylinders 75.0 6.00 1.0
12 Horsepower 25.0 165.00 1.0
13 Horsepower 50.0 210.00 1.0
14 Horsepower 75.0 255.00 1.0
15 MPG_City 25.0 17.00 1.0
16 MPG_City 50.0 19.00 1.0
17 MPG_City 75.0 21.50 1.0
18 MPG_Highway 25.0 24.00 1.0
19 MPG_Highway 50.0 26.00 1.0
20 MPG_Highway 75.0 29.00 1.0
21 Weight 25.0 3103.00 1.0
22 Weight 50.0 3474.50 1.0
23 Weight 75.0 3978.50 1.0
24 Wheelbase 25.0 103.00 1.0
25 Wheelbase 50.0 107.00 1.0
26 Wheelbase 75.0 112.00 1.0
27 Length 25.0 178.00 1.0
28 Length 50.0 187.00 1.0
29 Length 75.0 194.00 1.0
+ Elapsed: 0.0463s, user: 0.096s, sys: 0.014s, mem: 11.1mb
Note that the percentile action set has an action called percentile in it. you can call the action either as tbl.percentile or tbl.percentile.percentile.
CAS Tables as DataFrames¶
As we mentioned previously, CASTable objects implement many of the pandas.DataFrame methods and properties. This means that you can use the familiar pandas.DataFrame API, but use it on data that is far too large for pandas to handle. Here are a few simple examples.
In [23]: tbl.head()
Out[23]:
Selected Rows from Table CARS
Make Model Type Origin DriveTrain MSRP Invoice EngineSize Cylinders Horsepower MPG_City MPG_Highway Weight Wheelbase Length
0 Acura MDX SUV Asia All 36945.0 33337.0 3.5 6.0 265.0 17.0 23.0 4451.0 106.0 189.0
1 Acura RSX Type S 2dr Sedan Asia Front 23820.0 21761.0 2.0 4.0 200.0 24.0 31.0 2778.0 101.0 172.0
2 Acura TSX 4dr Sedan Asia Front 26990.0 24647.0 2.4 4.0 200.0 22.0 29.0 3230.0 105.0 183.0
3 Acura TL 4dr Sedan Asia Front 33195.0 30299.0 3.2 6.0 270.0 20.0 28.0 3575.0 108.0 186.0
4 Acura 3.5 RL 4dr Sedan Asia Front 43755.0 39014.0 3.5 6.0 225.0 18.0 24.0 3880.0 115.0 197.0
In [24]: tbl.describe()
Out[24]:
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% 20329.500000 18851.000000 2.350000 4.000000 165.000000 17.000000 24.000000 3103.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% 39215.000000 35732.500000 3.900000 6.000000 255.000000 21.500000 29.000000 3978.500000 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
In [25]: tbl[['MSRP', 'Invoice']].describe(percentiles=[0.3, 0.7])
Out[25]:
MSRP Invoice
count 428.000000 428.000000
mean 32774.855140 30014.700935
std 19431.716674 17642.117750
min 10280.000000 9875.000000
30% 22000.000000 20284.000000
50% 27635.000000 25294.500000
70% 35940.000000 32997.000000
max 192465.000000 173560.000000
For more information about CASTable, see the API Reference.
Closing the Connection¶
When you are finished with the connection, it’s always a good idea to close it.
In [26]: conn.close()
Authentication¶
The SWAT package supports three types of authentication when connecting to the CAS server:
Userid and Password
OAuth token
Kerberos ( binary protocol only )
Userid and Password¶
While it is possible to put your username and password in the CAS constructor, it’s generally not a good idea to have a password in your code. To get around this issue, the CAS class supports authinfo files. Authinfo files are a file used to store username and password information for specified hostname and port. They are protected by file permissions so that only you can read them. This allows you to set and protect your passwords in one place and have them used by all of your programs.
The format of the file is as follows:
host HOST user USERNAME password PASSWORD port PORT
machine is a synonym for host, login and account are synonyms for user, and protocol is a synonym for port.
You can specify as many of the host lines as possible. The port field is optional. If it is left off, all ports will use the same password. Hostnames much match the hostname used in the CAS constructor exactly. It does not do any DNS expanding of the names. So ‘host1’ and ‘host1.my-company.com’ are considered two different hosts.
Here is an example for a user named ‘user01’ and password ‘!s3cret’ on host ‘cas.my-company.com’ and port 12354:
host cas.my-company.com port 12354 user user01 password !s3cret
By default, the authinfo files are looked for in your home directory under the name .authinfo. You can also use the name .netrc which is the name of an older specification that authinfo was based on.
The permissions on the file must be readable and writable by the owner only. This is done with the following command:
chmod 0600 ~/.authinfo
If you don’t want to use an authinfo in your home directory, you can specify the name of a file explicitly using the authinfo= parameter.
In [27]: conn = swat.CAS('cas.my-company.com', 12354, authinfo='/path/to/authinfo.txt')
The username can also be specified using one of the following environment variables
CAS_USER
CAS_USERNAME
CASUSER
CASUSERNAME
The password can be specified using one of the following environment variables
CAS_TOKEN
CAS_PASSWORD
CASTOKEN
CASPASSWORD
Note
Userid and Password authentication will be deprecated in a future release. OAuth authentication should be used instead when possible.
OAuth Token¶
Authentication to the CAS server can be performed by using an OAuth token. The OAuth token can be specified in the CAS constructor using the password parameter. When specifying the OAuth token in the CAS constructor, do not specify the username parameter.
In [28]: conn = swat.CAS('cas.my-company.com', 12354, password='...')
The OAuth token can also be specified using one of the following environment variables
CAS_TOKEN
CAS_PASSWORD
CASTOKEN
CASPASSWORD
When using the HTTP protocol, the SWAT package can obtain an OAuth token on your behalf by specifying an authentication code in the CAS constructor using the authcode parameter.
In [29]: conn = swat.CAS('https://my-cas-host.com:443/cas-shared-default-http/',
....: authcode='...')
....:
The authentication code can also be specified using one of the following environment variables
CAS_AUTHCODE
VIYA_AUTHCODE
CASAUTHCODE
VIYAAUTHCODE
Beginning with release v1.14.0, the SWAT package supports using Proof Key for Code Exchange ( PKCE ) when using authentication codes to obtain an OAuth token with HTTP. Python 3.6 or later is required for PKCE.
To use PKCE, specify the pkce=True parameter in the CAS constructor. When specifying pkce=True, do not specify the authcode parameter. You will be provided a URL to use to obtain the authentication code and prompted to enter the authentication code obtained from that URL.
In [30]: conn = swat.CAS('https://my-cas-host.com:443/cas-shared-default-http/',
....: pkce=True)
....:
The pkce parameter can also be specified using one of the following environment variables
CAS_PKCE
VIYA_PKCE
CASPKCE
VIYAPKCE
Kerberos¶
The Kerberos Service Principal Name used by Viya 4 is different from the Kerberos Service Principal Name used by Viya 3.5. Releases of the SWAT package starting with 1.8.0 and later use the Viya 4 Service Principal Name. If you wish to connect to a Viya 3.5 CAS server using the SWAT package release 1.8.0 or later, you must use the CASSPN environment variable to specify the service principal name in the format recognized by Viya 3.5 . This value should start with ‘sascas@’ and be followed by the hostname.
In [31]: import os
In [32]: os.environ["CASSPN"] = "sascas@host"