├── CONTRIBUTING.md ├── ContributorAgreement.txt ├── Issue_examples ├── Issue147.ipynb ├── Issue168-Resolution.ipynb ├── Issue168.ipynb ├── Issue176_SAS_kernel.ipynb ├── Issue176_saspy.ipynb ├── Issue227.ipynb ├── Issue279.ipynb └── Java11cfg.ipynb ├── LICENSE ├── README.md ├── SAS_contrib ├── Ask_the_Expert_Demo.ipynb ├── Ask_the_Expert_Germany_2021.ipynb ├── BasicExampleUsingIrisData.ipynb ├── PassingVariablesBetweenSASAndPython.ipynb ├── SASPy for Machine Learning.ipynb ├── SGF_2019_SuperDemo1.ipynb ├── SGF_2019_SuperDemo2_SASPy.ipynb ├── SGF_2019_SuperDemo2_SAS_Kernel.ipynb ├── SGF_2020_Demo1.ipynb ├── SGF_2020_SuperDemo1.ipynb ├── Using_SYMGET_and_SYMPUT.ipynb ├── autocfg.ipynb └── saspy_example_github.ipynb └── User_contrib ├── SASpy_with_R_markdown.Rmd ├── SASpy_with_R_markdown.md ├── SASpy_with_R_markdown.pdf └── sas_tabulate_example.ipynb /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # To contribute to saspy-examples, there are very few rules and conventions to follow. 2 | 3 | 0. Try to have your example notbook able to be run by anyone successfully. Use SASHELP data or other publicly available data so it will run for anyone. 4 | 5 | 0. feel free to open an issue to discuss any questions you have or ask about ideas for contributions 6 | 7 | 0. Also, follow the instructions in the saspy-examples/ContributorAgreement.txt file 8 | 9 | And **thanks** for contributing! It will make this project better for everyone! 10 | -------------------------------------------------------------------------------- /ContributorAgreement.txt: -------------------------------------------------------------------------------- 1 | Contributor Agreement 2 | 3 | Version 1.1 4 | 5 | Contributions to this software are accepted only when they are 6 | properly accompanied by a Contributor Agreement. The Contributor 7 | Agreement for this software is the Developer's Certificate of Origin 8 | 1.1 (DCO) as provided with and required for accepting contributions 9 | to the Linux kernel. 10 | 11 | In each contribution proposed to be included in this software, the 12 | developer must include a "sign-off" that denotes consent to the 13 | terms of the Developer's Certificate of Origin. The sign-off is 14 | a line of text in the description that accompanies the change, 15 | certifying that you have the right to provide the contribution 16 | to be included. For changes provided in source code control (for 17 | example, via a Git pull request) the sign-off must be included in 18 | the commit message in source code control. For changes provided 19 | in email or issue tracking, the sign-off must be included in the 20 | email or the issue, and the sign-off will be incorporated into the 21 | permanent commit message if the contribution is accepted into the 22 | official source code. 23 | 24 | If you can certify the below: 25 | 26 | Developer's Certificate of Origin 1.1 27 | 28 | By making a contribution to this project, I certify that: 29 | 30 | (a) The contribution was created in whole or in part by me and I 31 | have the right to submit it under the open source license 32 | indicated in the file; or 33 | 34 | (b) The contribution is based upon previous work that, to the best 35 | of my knowledge, is covered under an appropriate open source 36 | license and I have the right under that license to submit that 37 | work with modifications, whether created in whole or in part 38 | by me, under the same open source license (unless I am 39 | permitted to submit under a different license), as indicated 40 | in the file; or 41 | 42 | (c) The contribution was provided directly to me by some other 43 | person who certified (a), (b) or (c) and I have not modified 44 | it. 45 | 46 | (d) I understand and agree that this project and the contribution 47 | are public and that a record of the contribution (including all 48 | personal information I submit with it, including my sign-off) is 49 | maintained indefinitely and may be redistributed consistent with 50 | this project or the open source license(s) involved. 51 | 52 | then you just add a line saying 53 | 54 | Signed-off-by: Random J Developer 55 | 56 | using your real name (sorry, no pseudonyms or anonymous contributions.) 57 | -------------------------------------------------------------------------------- /Issue_examples/Issue168-Resolution.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Using v94m2 and v94m3 to show this bug in 2.2.7 saspy where the translated ODS templates changed encodings. See 'charset' in the html\n", 8 | "# Using Chinese, as I was set up with that investigating this prior to this issue." 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": 1, 14 | "metadata": { 15 | "collapsed": false 16 | }, 17 | "outputs": [ 18 | { 19 | "data": { 20 | "text/plain": [ 21 | "" 22 | ] 23 | }, 24 | "execution_count": 1, 25 | "metadata": {}, 26 | "output_type": "execute_result" 27 | } 28 | ], 29 | "source": [ 30 | "import saspy\n", 31 | "saspy" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": 2, 37 | "metadata": { 38 | "collapsed": false 39 | }, 40 | "outputs": [ 41 | { 42 | "name": "stdout", 43 | "output_type": "stream", 44 | "text": [ 45 | "SAS Connection established. Subprocess id is 18340\n", 46 | "\n" 47 | ] 48 | }, 49 | { 50 | "data": { 51 | "text/plain": [ 52 | "Access Method = IOM\n", 53 | "SAS Config name = winiomjwin\n", 54 | "WORK Path = /\n", 55 | "SAS Version = 9.04.01M2P08192014\n", 56 | "SASPy Version = 2.2.7\n", 57 | "Teach me SAS = False\n", 58 | "Batch = False\n", 59 | "Results = Pandas\n", 60 | "SAS Session Encoding = EUC-CN\n", 61 | "Python Encoding value = gb2312" 62 | ] 63 | }, 64 | "execution_count": 2, 65 | "metadata": {}, 66 | "output_type": "execute_result" 67 | } 68 | ], 69 | "source": [ 70 | "sas_m2 = saspy.SASsession(cfgname='winiomjwin', encoding='gb2312', iomport=6777)\n", 71 | "sas_m2" 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": 3, 77 | "metadata": { 78 | "collapsed": false 79 | }, 80 | "outputs": [], 81 | "source": [ 82 | "cars_m2 = sas_m2.sasdata('cars','sashelp')" 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": 4, 88 | "metadata": { 89 | "collapsed": false 90 | }, 91 | "outputs": [ 92 | { 93 | "ename": "UnicodeDecodeError", 94 | "evalue": "'utf-8' codec can't decode byte 0xbb in position 35697: invalid start byte", 95 | "output_type": "error", 96 | "traceback": [ 97 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 98 | "\u001b[0;31mUnicodeDecodeError\u001b[0m Traceback (most recent call last)", 99 | "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mcars_m2\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdescribe\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", 100 | "\u001b[0;32mC:\\ProgramData\\Anaconda3\\lib\\site-packages\\saspy\\sasbase.py\u001b[0m in \u001b[0;36mdescribe\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 1508\u001b[0m \u001b[1;33m:\u001b[0m\u001b[1;32mreturn\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m 1509\u001b[0m \"\"\"\n\u001b[0;32m-> 1510\u001b[0;31m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mmeans\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1511\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m 1512\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mmeans\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", 101 | "\u001b[0;32mC:\\ProgramData\\Anaconda3\\lib\\site-packages\\saspy\\sasbase.py\u001b[0m in \u001b[0;36mmeans\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 1527\u001b[0m code = \"proc means data=%s.%s %s stackodsoutput n nmiss median mean std min p25 p50 p75 max; ods output Summary=work._summary; run;\" % (\n\u001b[1;32m 1528\u001b[0m self.libref, self.table, self._dsopts())\n\u001b[0;32m-> 1529\u001b[0;31m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_returnPD\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mcode\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'_summary'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1530\u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m 1531\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mHTML\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", 102 | "\u001b[0;32mC:\\ProgramData\\Anaconda3\\lib\\site-packages\\saspy\\sasbase.py\u001b[0m in \u001b[0;36m_returnPD\u001b[0;34m(self, code, tablename, **kwargs)\u001b[0m\n\u001b[1;32m 1107\u001b[0m \u001b[1;32mif\u001b[0m \u001b[1;34m'libref'\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mkwargs\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m 1108\u001b[0m \u001b[0mlibref\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mkwargs\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m'libref'\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1109\u001b[0;31m \u001b[0mll\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msas\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_io\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msubmit\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mcode\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1110\u001b[0m \u001b[0mcheck\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0merrorMsg\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_checkLogForError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mll\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m'LOG'\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m 1111\u001b[0m \u001b[1;32mif\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[0mcheck\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", 103 | "\u001b[0;32mC:\\ProgramData\\Anaconda3\\lib\\site-packages\\saspy\\sasioiom.py\u001b[0m in \u001b[0;36msubmit\u001b[0;34m(self, code, results, prompt)\u001b[0m\n\u001b[1;32m 875\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mstdin\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0modsclose\u001b[0m\u001b[1;33m+\u001b[0m\u001b[0mlogcodei\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mencode\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m+\u001b[0m\u001b[1;34mb'tom says EOL='\u001b[0m\u001b[1;33m+\u001b[0m\u001b[0mlogcodeo\u001b[0m\u001b[1;33m+\u001b[0m\u001b[1;34mb'\\n'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m 876\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m--> 877\u001b[0;31m \u001b[0mlstf\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mlstf\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdecode\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 878\u001b[0m \u001b[0mlogf\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mlogf\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdecode\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m 879\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", 104 | "\u001b[0;31mUnicodeDecodeError\u001b[0m: 'utf-8' codec can't decode byte 0xbb in position 35697: invalid start byte" 105 | ] 106 | } 107 | ], 108 | "source": [ 109 | "cars_m2.describe()" 110 | ] 111 | }, 112 | { 113 | "cell_type": "code", 114 | "execution_count": null, 115 | "metadata": { 116 | "collapsed": true 117 | }, 118 | "outputs": [], 119 | "source": [ 120 | "cars_m2 = sas_m2.sasdata('cars','sashelp', results='html')" 121 | ] 122 | }, 123 | { 124 | "cell_type": "markdown", 125 | "metadata": {}, 126 | "source": [ 127 | "## of course this won't work either, but with the fix, you can see the results, pasted below" 128 | ] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "execution_count": 5, 133 | "metadata": { 134 | "collapsed": false 135 | }, 136 | "outputs": [], 137 | "source": [ 138 | "#sas_m2.batch=True\n", 139 | "#x = cars_m2.head()['LST']\n", 140 | "#sas_m2.batch=False\n", 141 | "#print(x[:200])\n", 142 | "\n", 143 | "#with fix from master you can see the html is encoded GBK, not utf-8\n", 144 | "\n", 145 | "#\n", 146 | "#\n", 147 | "#\n", 148 | "#\n", 149 | "#SAS Output\n", 150 | "#\n", 120 | "\n", 121 | "\n", 122 | "

\n", 123 | "\n", 124 | "
42   ods listing close;ods html5 (id=saspy_internal) file=stdout options(bitmap_mode='inline') device=svg; ods graphics on /
42 ! outputfmt=png;
NOTE: Writing HTML5(SASPY_INTERNAL) Body file: STDOUT
43
44 Filename file1 "&fname.";
45
46 %put &fname.;
myfile.txt
47
48 proc summary data=sashelp.class;
49 var weight;
50 run;
ERROR: Neither the PRINT option nor a valid output statement has been given.
NOTE: The SAS System stopped processing this step because of errors.
NOTE: PROCEDURE SUMMARY used (Total process time):
real time 0.00 seconds
cpu time 0.00 seconds

51 %let summaryrc=&syserr;
52 %put &summaryrc.;
1012
53
54 proc univariate data=sashelp.class noprint;
55 var height weight;
56 run;
NOTE: PROCEDURE UNIVARIATE used (Total process time):
real time 0.00 seconds
cpu time 0.00 seconds

57 %let unirc=&syserr;
58 %put &unirc.;
0
59
60 ods html5 (id=saspy_internal) close;ods listing;

61
\n", 125 | "\n", 126 | "\n" 127 | ], 128 | "text/plain": [ 129 | "" 130 | ] 131 | }, 132 | "execution_count": 34, 133 | "metadata": {}, 134 | "output_type": "execute_result" 135 | } 136 | ], 137 | "source": [ 138 | "%%SAS sas1\n", 139 | "Filename file1 \"&fname.\";\n", 140 | "\n", 141 | "%put &fname.;\n", 142 | "\n", 143 | "proc summary data=sashelp.class;\n", 144 | " var weight;\n", 145 | "run;\n", 146 | "%let summaryrc=&syserr;\n", 147 | "%put &summaryrc.;\n", 148 | "\n", 149 | "proc univariate data=sashelp.class noprint;\n", 150 | " var height weight;\n", 151 | "run;\n", 152 | "%let unirc=&syserr;\n", 153 | "%put &unirc.;" 154 | ] 155 | }, 156 | { 157 | "cell_type": "code", 158 | "execution_count": 32, 159 | "metadata": {}, 160 | "outputs": [ 161 | { 162 | "name": "stdout", 163 | "output_type": "stream", 164 | "text": [ 165 | "there were errors for index 0. RC=1012\n", 166 | "everything ran normally for index 1\n" 167 | ] 168 | } 169 | ], 170 | "source": [ 171 | "rc = []\n", 172 | "rc.append(sas1.symget('summaryrc'))\n", 173 | "rc.append(sas1.symget('unirc'))\n", 174 | "for idx, value in enumerate(rc):\n", 175 | " if value==0:\n", 176 | " print (\"everything ran normally for index {}\".format(idx))\n", 177 | " else:\n", 178 | " print(\"there were errors for index {}. RC={}\".format(idx,value))" 179 | ] 180 | }, 181 | { 182 | "cell_type": "code", 183 | "execution_count": null, 184 | "metadata": {}, 185 | "outputs": [], 186 | "source": [] 187 | } 188 | ], 189 | "metadata": { 190 | "kernelspec": { 191 | "display_name": "Python 3", 192 | "language": "python", 193 | "name": "python3" 194 | }, 195 | "language_info": { 196 | "codemirror_mode": { 197 | "name": "ipython", 198 | "version": 3 199 | }, 200 | "file_extension": ".py", 201 | "mimetype": "text/x-python", 202 | "name": "python", 203 | "nbconvert_exporter": "python", 204 | "pygments_lexer": "ipython3", 205 | "version": "3.5.4" 206 | } 207 | }, 208 | "nbformat": 4, 209 | "nbformat_minor": 2 210 | } 211 | -------------------------------------------------------------------------------- /SAS_contrib/Using_SYMGET_and_SYMPUT.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [ 10 | { 11 | "name": "stdout", 12 | "output_type": "stream", 13 | "text": [ 14 | "SAS Connection established. Subprocess id is 17688\n", 15 | "\n" 16 | ] 17 | }, 18 | { 19 | "data": { 20 | "text/plain": [ 21 | "Access Method = IOM\n", 22 | "SAS Config name = winlocal\n", 23 | "WORK Path = C:\\Users\\sastpw\\AppData\\Local\\Temp\\SAS Temporary Files\\_TD21580_d10a626_\\Prc2\\\n", 24 | "SAS Version = 9.04.01M5P09132017\n", 25 | "SASPy Version = 2.2.9\n", 26 | "Teach me SAS = False\n", 27 | "Batch = False\n", 28 | "Results = Pandas\n", 29 | "SAS Session Encoding = WLATIN1\n", 30 | "Python Encoding value = cp1252" 31 | ] 32 | }, 33 | "execution_count": 1, 34 | "metadata": {}, 35 | "output_type": "execute_result" 36 | } 37 | ], 38 | "source": [ 39 | "import saspy\n", 40 | "sas = saspy.SASsession(cfgname='winlocal')\n", 41 | "sas" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 2, 47 | "metadata": { 48 | "collapsed": true 49 | }, 50 | "outputs": [], 51 | "source": [ 52 | "sas.symput('new_var', 52)" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": 3, 58 | "metadata": { 59 | "collapsed": false 60 | }, 61 | "outputs": [ 62 | { 63 | "data": { 64 | "text/plain": [ 65 | "52" 66 | ] 67 | }, 68 | "execution_count": 3, 69 | "metadata": {}, 70 | "output_type": "execute_result" 71 | } 72 | ], 73 | "source": [ 74 | "sas.symget('new_var')" 75 | ] 76 | }, 77 | { 78 | "cell_type": "code", 79 | "execution_count": 4, 80 | "metadata": { 81 | "collapsed": true 82 | }, 83 | "outputs": [], 84 | "source": [ 85 | "sas.symput('nuther_var', 'Hi there')" 86 | ] 87 | }, 88 | { 89 | "cell_type": "code", 90 | "execution_count": 5, 91 | "metadata": { 92 | "collapsed": false 93 | }, 94 | "outputs": [ 95 | { 96 | "data": { 97 | "text/plain": [ 98 | "'Hi there'" 99 | ] 100 | }, 101 | "execution_count": 5, 102 | "metadata": {}, 103 | "output_type": "execute_result" 104 | } 105 | ], 106 | "source": [ 107 | "sas.symget('nuther_var')" 108 | ] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "execution_count": 6, 113 | "metadata": { 114 | "collapsed": true 115 | }, 116 | "outputs": [], 117 | "source": [ 118 | "py_var = sas.symget('nuther_var')" 119 | ] 120 | }, 121 | { 122 | "cell_type": "code", 123 | "execution_count": 7, 124 | "metadata": { 125 | "collapsed": false 126 | }, 127 | "outputs": [ 128 | { 129 | "name": "stdout", 130 | "output_type": "stream", 131 | "text": [ 132 | "Hi there\n" 133 | ] 134 | } 135 | ], 136 | "source": [ 137 | "print(py_var)" 138 | ] 139 | }, 140 | { 141 | "cell_type": "code", 142 | "execution_count": 8, 143 | "metadata": { 144 | "collapsed": false 145 | }, 146 | "outputs": [ 147 | { 148 | "name": "stdout", 149 | "output_type": "stream", 150 | "text": [ 151 | "Hi there y'all\n" 152 | ] 153 | } 154 | ], 155 | "source": [ 156 | "sas.symput('from_py_var', py_var+\" y'all\")\n", 157 | "print(sas.symget('from_py_var'))" 158 | ] 159 | }, 160 | { 161 | "cell_type": "code", 162 | "execution_count": 9, 163 | "metadata": { 164 | "collapsed": true 165 | }, 166 | "outputs": [], 167 | "source": [ 168 | "ll = sas.submit('''\n", 169 | "%let what_happened=it worked!;\n", 170 | "\n", 171 | "data a; x=symget('from_py_var'); y=symget('new_var');run;\n", 172 | "''')" 173 | ] 174 | }, 175 | { 176 | "cell_type": "code", 177 | "execution_count": 10, 178 | "metadata": { 179 | "collapsed": false 180 | }, 181 | "outputs": [ 182 | { 183 | "name": "stdout", 184 | "output_type": "stream", 185 | "text": [ 186 | "\n", 187 | " The SAS System 12:38 Wednesday, October 10, 2018 1\n", 188 | "\n", 189 | " Obs x y\n", 190 | "\n", 191 | " 1 Hi there y'all 52\n", 192 | "\n" 193 | ] 194 | } 195 | ], 196 | "source": [ 197 | "data_a = sas.sasdata('a', results='text')\n", 198 | "data_a.head()" 199 | ] 200 | }, 201 | { 202 | "cell_type": "code", 203 | "execution_count": 11, 204 | "metadata": { 205 | "collapsed": false 206 | }, 207 | "outputs": [ 208 | { 209 | "name": "stdout", 210 | "output_type": "stream", 211 | "text": [ 212 | "it worked!\n" 213 | ] 214 | } 215 | ], 216 | "source": [ 217 | "print(sas.symget('what_happened'))\n" 218 | ] 219 | }, 220 | { 221 | "cell_type": "code", 222 | "execution_count": 12, 223 | "metadata": { 224 | "collapsed": false, 225 | "scrolled": true 226 | }, 227 | "outputs": [ 228 | { 229 | "name": "stdout", 230 | "output_type": "stream", 231 | "text": [ 232 | "\f", 233 | "1 The SAS System 12:38 Wednesday, October 10, 2018\n", 234 | "\n", 235 | "NOTE: Copyright (c) 2016 by SAS Institute Inc., Cary, NC, USA. \n", 236 | "NOTE: SAS (r) Proprietary Software 9.4 (TS1M5) \n", 237 | " Licensed to Microsoft Windows for x64 All Compatible Non-Plann, Site 70068130.\n", 238 | "NOTE: This session is executing on the X64_10PRO platform.\n", 239 | "\n", 240 | "\n", 241 | "\n", 242 | "NOTE: Updated analytical products:\n", 243 | " \n", 244 | " SAS/STAT 14.3\n", 245 | " SAS/ETS 14.3\n", 246 | " SAS/OR 14.3\n", 247 | " SAS/QC 14.3\n", 248 | "\n", 249 | "NOTE: Additional host information:\n", 250 | "\n", 251 | " X64_10PRO WIN 10.0.16299 Workstation\n", 252 | "\n", 253 | "NOTE: SAS Initialization used (Total process time):\n", 254 | " real time 0.00 seconds\n", 255 | " cpu time 0.01 seconds\n", 256 | " \n", 257 | "1 ;*';*\";*/;\n", 258 | "2 options svgtitle='svgtitle'; options validvarname=any pagesize=max nosyntaxcheck; ods graphics on;\n", 259 | "3 \n", 260 | "4 ;*';*\";*/;\n", 261 | "5 %put E3969440A681A2408885998500000001;\n", 262 | "E3969440A681A2408885998500000001\n", 263 | "6 \n", 264 | "\f", 265 | "2 The SAS System 12:38 Wednesday, October 10, 2018\n", 266 | "\n", 267 | "7 ods listing close;ods html5 (id=saspy_internal) file=_tomods1 options(bitmap_mode='inline') device=svg style=HTMLBlue;\n", 268 | "7 ! ods graphics on / outputfmt=png;\n", 269 | "NOTE: Writing HTML5(SASPY_INTERNAL) Body file: _TOMODS1\n", 270 | "8 ;*';*\";*/;\n", 271 | "9 %put WORKpath=%sysfunc(pathname(work));\n", 272 | "WORKpath=C:\\Users\\sastpw\\AppData\\Local\\Temp\\SAS Temporary Files\\_TD21580_d10a626_\\Prc2\n", 273 | "10 \n", 274 | "11 ;*';*\";*/;ods html5 (id=saspy_internal) close;ods listing;\n", 275 | "12 \n", 276 | "13 %put E3969440A681A2408885998500000002;\n", 277 | "E3969440A681A2408885998500000002\n", 278 | "14 \n", 279 | "\f", 280 | "3 The SAS System 12:38 Wednesday, October 10, 2018\n", 281 | "\n", 282 | "15 ods listing close;ods html5 (id=saspy_internal) file=_tomods1 options(bitmap_mode='inline') device=svg style=HTMLBlue;\n", 283 | "15 ! ods graphics on / outputfmt=png;\n", 284 | "NOTE: Writing HTML5(SASPY_INTERNAL) Body file: _TOMODS1\n", 285 | "16 ;*';*\";*/;\n", 286 | "17 %put SYSV=&sysvlong4;\n", 287 | "SYSV=9.04.01M5P09132017\n", 288 | "18 \n", 289 | "19 ;*';*\";*/;ods html5 (id=saspy_internal) close;ods listing;\n", 290 | "20 \n", 291 | "21 %put E3969440A681A2408885998500000003;\n", 292 | "E3969440A681A2408885998500000003\n", 293 | "22 \n", 294 | "\f", 295 | "4 The SAS System 12:38 Wednesday, October 10, 2018\n", 296 | "\n", 297 | "23 ods listing close;ods html5 (id=saspy_internal) file=_tomods1 options(bitmap_mode='inline') device=svg style=HTMLBlue;\n", 298 | "23 ! ods graphics on / outputfmt=png;\n", 299 | "NOTE: Writing HTML5(SASPY_INTERNAL) Body file: _TOMODS1\n", 300 | "24 ;*';*\";*/;\n", 301 | "25 proc options option=encoding;run;\n", 302 | "\n", 303 | " SAS (r) Proprietary Software Release 9.4 TS1M5\n", 304 | "\n", 305 | " ENCODING=WLATIN1 Specifies the default character-set encoding for the SAS session.\n", 306 | "NOTE: PROCEDURE OPTIONS used (Total process time):\n", 307 | " real time 0.00 seconds\n", 308 | " cpu time 0.00 seconds\n", 309 | " \n", 310 | "\n", 311 | "26 \n", 312 | "27 ;*';*\";*/;ods html5 (id=saspy_internal) close;ods listing;\n", 313 | "28 \n", 314 | "29 %put E3969440A681A2408885998500000004;\n", 315 | "E3969440A681A2408885998500000004\n", 316 | "30 \n", 317 | "\f", 318 | "5 The SAS System 12:38 Wednesday, October 10, 2018\n", 319 | "\n", 320 | "31 ods listing close;ods html5 (id=saspy_internal) file=_tomods1 options(bitmap_mode='inline') device=svg style=HTMLBlue;\n", 321 | "31 ! ods graphics on / outputfmt=png;\n", 322 | "NOTE: Writing HTML5(SASPY_INTERNAL) Body file: _TOMODS1\n", 323 | "32 ;*';*\";*/;\n", 324 | "33 %let new_var=%NRBQUOTE(52);\n", 325 | "34 \n", 326 | "35 \n", 327 | "36 ;*';*\";*/;ods html5 (id=saspy_internal) close;ods listing;\n", 328 | "37 \n", 329 | "38 %put E3969440A681A2408885998500000005;\n", 330 | "E3969440A681A2408885998500000005\n", 331 | "39 \n", 332 | "\f", 333 | "6 The SAS System 12:38 Wednesday, October 10, 2018\n", 334 | "\n", 335 | "40 ods listing close;ods html5 (id=saspy_internal) file=_tomods1 options(bitmap_mode='inline') device=svg style=HTMLBlue;\n", 336 | "40 ! ods graphics on / outputfmt=png;\n", 337 | "NOTE: Writing HTML5(SASPY_INTERNAL) Body file: _TOMODS1\n", 338 | "41 ;*';*\";*/;\n", 339 | "42 %put new_var=&new_var;\n", 340 | "new_var=52\n", 341 | "43 \n", 342 | "44 \n", 343 | "45 ;*';*\";*/;ods html5 (id=saspy_internal) close;ods listing;\n", 344 | "46 \n", 345 | "47 %put E3969440A681A2408885998500000006;\n", 346 | "E3969440A681A2408885998500000006\n", 347 | "48 \n", 348 | "\f", 349 | "7 The SAS System 12:38 Wednesday, October 10, 2018\n", 350 | "\n", 351 | "49 ods listing close;ods html5 (id=saspy_internal) file=_tomods1 options(bitmap_mode='inline') device=svg style=HTMLBlue;\n", 352 | "49 ! ods graphics on / outputfmt=png;\n", 353 | "NOTE: Writing HTML5(SASPY_INTERNAL) Body file: _TOMODS1\n", 354 | "50 ;*';*\";*/;\n", 355 | "51 %let nuther_var=%NRBQUOTE(Hi there);\n", 356 | "52 \n", 357 | "53 \n", 358 | "54 ;*';*\";*/;ods html5 (id=saspy_internal) close;ods listing;\n", 359 | "55 \n", 360 | "56 %put E3969440A681A2408885998500000007;\n", 361 | "E3969440A681A2408885998500000007\n", 362 | "57 \n", 363 | "\f", 364 | "8 The SAS System 12:38 Wednesday, October 10, 2018\n", 365 | "\n", 366 | "58 ods listing close;ods html5 (id=saspy_internal) file=_tomods1 options(bitmap_mode='inline') device=svg style=HTMLBlue;\n", 367 | "58 ! ods graphics on / outputfmt=png;\n", 368 | "NOTE: Writing HTML5(SASPY_INTERNAL) Body file: _TOMODS1\n", 369 | "59 ;*';*\";*/;\n", 370 | "60 %put nuther_var=&nuther_var;\n", 371 | "nuther_var=Hi there\n", 372 | "61 \n", 373 | "62 \n", 374 | "63 ;*';*\";*/;ods html5 (id=saspy_internal) close;ods listing;\n", 375 | "64 \n", 376 | "65 %put E3969440A681A2408885998500000008;\n", 377 | "E3969440A681A2408885998500000008\n", 378 | "66 \n", 379 | "\f", 380 | "9 The SAS System 12:38 Wednesday, October 10, 2018\n", 381 | "\n", 382 | "67 ods listing close;ods html5 (id=saspy_internal) file=_tomods1 options(bitmap_mode='inline') device=svg style=HTMLBlue;\n", 383 | "67 ! ods graphics on / outputfmt=png;\n", 384 | "NOTE: Writing HTML5(SASPY_INTERNAL) Body file: _TOMODS1\n", 385 | "68 ;*';*\";*/;\n", 386 | "69 %put nuther_var=&nuther_var;\n", 387 | "nuther_var=Hi there\n", 388 | "70 \n", 389 | "71 \n", 390 | "72 ;*';*\";*/;ods html5 (id=saspy_internal) close;ods listing;\n", 391 | "73 \n", 392 | "74 %put E3969440A681A2408885998500000009;\n", 393 | "E3969440A681A2408885998500000009\n", 394 | "75 \n", 395 | "\f", 396 | "10 The SAS System 12:38 Wednesday, October 10, 2018\n", 397 | "\n", 398 | "76 ods listing close;ods html5 (id=saspy_internal) file=_tomods1 options(bitmap_mode='inline') device=svg style=HTMLBlue;\n", 399 | "76 ! ods graphics on / outputfmt=png;\n", 400 | "NOTE: Writing HTML5(SASPY_INTERNAL) Body file: _TOMODS1\n", 401 | "77 ;*';*\";*/;\n", 402 | "78 %let from_py_var=%NRBQUOTE(Hi there y'all);\n", 403 | "79 \n", 404 | "80 \n", 405 | "81 ;*';*\";*/;ods html5 (id=saspy_internal) close;ods listing;\n", 406 | "82 \n", 407 | "83 %put E3969440A681A2408885998500000010;\n", 408 | "E3969440A681A2408885998500000010\n", 409 | "84 \n", 410 | "\f", 411 | "11 The SAS System 12:38 Wednesday, October 10, 2018\n", 412 | "\n", 413 | "85 ods listing close;ods html5 (id=saspy_internal) file=_tomods1 options(bitmap_mode='inline') device=svg style=HTMLBlue;\n", 414 | "85 ! ods graphics on / outputfmt=png;\n", 415 | "NOTE: Writing HTML5(SASPY_INTERNAL) Body file: _TOMODS1\n", 416 | "86 ;*';*\";*/;\n", 417 | "87 %put from_py_var=&from_py_var;\n", 418 | "from_py_var=Hi there y'all\n", 419 | "88 \n", 420 | "89 \n", 421 | "90 ;*';*\";*/;ods html5 (id=saspy_internal) close;ods listing;\n", 422 | "91 \n", 423 | "92 %put E3969440A681A2408885998500000011;\n", 424 | "E3969440A681A2408885998500000011\n", 425 | "93 \n", 426 | "\f", 427 | "12 The SAS System 12:38 Wednesday, October 10, 2018\n", 428 | "\n", 429 | "94 ods listing close;ods html5 (id=saspy_internal) file=_tomods1 options(bitmap_mode='inline') device=svg style=HTMLBlue;\n", 430 | "94 ! ods graphics on / outputfmt=png;\n", 431 | "NOTE: Writing HTML5(SASPY_INTERNAL) Body file: _TOMODS1\n", 432 | "95 ;*';*\";*/;\n", 433 | "96 \n", 434 | "97 %let what_happened=it worked!;\n", 435 | "98 \n", 436 | "99 data a; x=symget('from_py_var'); y=symget('new_var');run;\n", 437 | "\n", 438 | "NOTE: The data set WORK.A has 1 observations and 2 variables.\n", 439 | "NOTE: DATA statement used (Total process time):\n", 440 | " real time 0.01 seconds\n", 441 | " cpu time 0.00 seconds\n", 442 | " \n", 443 | "\n", 444 | "100 \n", 445 | "101 \n", 446 | "102 ;*';*\";*/;ods html5 (id=saspy_internal) close;ods listing;\n", 447 | "103 \n", 448 | "104 %put E3969440A681A2408885998500000012;\n", 449 | "E3969440A681A2408885998500000012\n", 450 | "105 \n", 451 | "\f", 452 | "13 The SAS System 12:38 Wednesday, October 10, 2018\n", 453 | "\n", 454 | "106 ;*';*\";*/;\n", 455 | "107 data _null_; e = exist('user.a');\n", 456 | "108 v = exist('user.a', 'VIEW');\n", 457 | "109 if e or v then e = 1;\n", 458 | "110 te='TABLE_EXISTS='; put te e;run;\n", 459 | "\n", 460 | "TABLE_EXISTS= 0\n", 461 | "NOTE: DATA statement used (Total process time):\n", 462 | " real time 0.00 seconds\n", 463 | " cpu time 0.00 seconds\n", 464 | " \n", 465 | "\n", 466 | "111 \n", 467 | "112 \n", 468 | "113 ;*';*\";*/;\n", 469 | "114 %put E3969440A681A2408885998500000013;\n", 470 | "E3969440A681A2408885998500000013\n", 471 | "115 \n", 472 | "\f", 473 | "14 The SAS System 12:38 Wednesday, October 10, 2018\n", 474 | "\n", 475 | "116 ;*';*\";*/;\n", 476 | "117 data _null_; e = exist('WORK.a');\n", 477 | "118 v = exist('WORK.a', 'VIEW');\n", 478 | "119 if e or v then e = 1;\n", 479 | "120 te='TABLE_EXISTS='; put te e;run;\n", 480 | "\n", 481 | "TABLE_EXISTS= 1\n", 482 | "NOTE: DATA statement used (Total process time):\n", 483 | " real time 0.00 seconds\n", 484 | " cpu time 0.00 seconds\n", 485 | " \n", 486 | "\n", 487 | "121 \n", 488 | "122 \n", 489 | "123 ;*';*\";*/;\n", 490 | "124 %put E3969440A681A2408885998500000014;\n", 491 | "E3969440A681A2408885998500000014\n", 492 | "125 \n", 493 | "\f", 494 | "15 The SAS System 12:38 Wednesday, October 10, 2018\n", 495 | "\n", 496 | "126 ;*';*\";*/;\n", 497 | "127 data _null_; e = exist('WORK.a');\n", 498 | "128 v = exist('WORK.a', 'VIEW');\n", 499 | "129 if e or v then e = 1;\n", 500 | "130 te='TABLE_EXISTS='; put te e;run;\n", 501 | "\n", 502 | "TABLE_EXISTS= 1\n", 503 | "NOTE: DATA statement used (Total process time):\n", 504 | " real time 0.00 seconds\n", 505 | " cpu time 0.00 seconds\n", 506 | " \n", 507 | "\n", 508 | "131 \n", 509 | "132 \n", 510 | "133 ;*';*\";*/;\n", 511 | "134 %put E3969440A681A2408885998500000015;\n", 512 | "E3969440A681A2408885998500000015\n", 513 | "135 \n", 514 | "\f", 515 | "16 The SAS System 12:38 Wednesday, October 10, 2018\n", 516 | "\n", 517 | "136 ;*';*\";*/;\n", 518 | "137 proc print data=WORK.a(obs=5 );run;\n", 519 | "\n", 520 | "NOTE: There were 1 observations read from the data set WORK.A.\n", 521 | "NOTE: The PROCEDURE PRINT printed page 1.\n", 522 | "NOTE: PROCEDURE PRINT used (Total process time):\n", 523 | " real time 0.00 seconds\n", 524 | " cpu time 0.01 seconds\n", 525 | " \n", 526 | "\n", 527 | "138 \n", 528 | "139 ;*';*\";*/;\n", 529 | "140 %put E3969440A681A2408885998500000016;\n", 530 | "E3969440A681A2408885998500000016\n", 531 | "141 \n", 532 | "\f", 533 | "17 The SAS System 12:38 Wednesday, October 10, 2018\n", 534 | "\n", 535 | "142 ods listing close;ods html5 (id=saspy_internal) file=_tomods1 options(bitmap_mode='inline') device=svg style=HTMLBlue;\n", 536 | "142 ! ods graphics on / outputfmt=png;\n", 537 | "NOTE: Writing HTML5(SASPY_INTERNAL) Body file: _TOMODS1\n", 538 | "143 ;*';*\";*/;\n", 539 | "144 %put what_happened=&what_happened;\n", 540 | "what_happened=it worked!\n", 541 | "145 \n", 542 | "146 \n", 543 | "147 ;*';*\";*/;ods html5 (id=saspy_internal) close;ods listing;\n", 544 | "148 \n", 545 | "149 %put E3969440A681A2408885998500000017;\n", 546 | "E3969440A681A2408885998500000017\n", 547 | "150 \n", 548 | "\n" 549 | ] 550 | } 551 | ], 552 | "source": [ 553 | "print(sas.saslog())" 554 | ] 555 | }, 556 | { 557 | "cell_type": "code", 558 | "execution_count": null, 559 | "metadata": { 560 | "collapsed": true 561 | }, 562 | "outputs": [], 563 | "source": [] 564 | } 565 | ], 566 | "metadata": { 567 | "kernelspec": { 568 | "display_name": "Python 3", 569 | "language": "python", 570 | "name": "python3" 571 | }, 572 | "language_info": { 573 | "codemirror_mode": { 574 | "name": "ipython", 575 | "version": 3 576 | }, 577 | "file_extension": ".py", 578 | "mimetype": "text/x-python", 579 | "name": "python", 580 | "nbconvert_exporter": "python", 581 | "pygments_lexer": "ipython3", 582 | "version": "3.6.0" 583 | } 584 | }, 585 | "nbformat": 4, 586 | "nbformat_minor": 2 587 | } 588 | -------------------------------------------------------------------------------- /SAS_contrib/autocfg.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# autocfg.py\n", 8 | "\n", 9 | "## This example notebook shows how to use the autocfg.py file in saspy to automatically generate a working saspy config file for connecting to a local SAS install on a Windows system.\n", 10 | "## autocfg.py can be run as a batch script, or from within python.\n", 11 | "\n", 12 | "## Even if this works for you, this is no excuse for not reading the install/config doc :)\n", 13 | "## https://sassoftware.github.io/saspy/install.html\n", 14 | "\n", 15 | "### There are three optional parameters you can provide. \n", 16 | "* cfgfile - this is the path to the config file that will be created. By default it will be sascfg_personal.py in the saspy install directory\n", 17 | "* SASHome - this is the path to your SASHome directory; the SAS installation directory. It defaults to C:\\Program Files\\SASHome which is the SAS default install path\n", 18 | "* Java - this is the path to your java.exe on your system. It defaults to java which is usually found in your path when you install java on Windwos\n", 19 | "\n", 20 | "## These parameters can be specified positionally or by keyword interactivly. For batch, they are positional only, but you can specify None to not specify some. See the examples for that below.\n", 21 | "\n" 22 | ] 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "metadata": {}, 27 | "source": [ 28 | "### Here is the signature for the function. The interactive use case follows" 29 | ] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "execution_count": 1, 34 | "metadata": { 35 | "collapsed": true 36 | }, 37 | "outputs": [], 38 | "source": [ 39 | "#def main(cfgfile: str = None, SASHome: str = None, java: str = None):" 40 | ] 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "metadata": { 45 | "collapsed": false 46 | }, 47 | "source": [ 48 | "## You can create a Windows local sascfg_personal.py file using autocfg.py from within Python\n", 49 | "## Here is the simplest case, for a freshly installed saspy. It will create a config file that works imediatly; assuming I have SAS and Java installed in default locations" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": 2, 55 | "metadata": { 56 | "collapsed": false 57 | }, 58 | "outputs": [ 59 | { 60 | "name": "stdout", 61 | "output_type": "stream", 62 | "text": [ 63 | "CFGFILE ALREADY EXISTS: C:\\ProgramData\\Anaconda3\\lib\\site-packages\\saspy\\sascfg_personal.py\n" 64 | ] 65 | } 66 | ], 67 | "source": [ 68 | "from saspy import autocfg\n", 69 | "autocfg.main()" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": 3, 75 | "metadata": { 76 | "collapsed": false 77 | }, 78 | "outputs": [ 79 | { 80 | "name": "stdout", 81 | "output_type": "stream", 82 | "text": [ 83 | "Please enter the name of the SAS Config you wish to run. Available Configs are: ['default', 'ssh', 'iomlinux', 'iomwin', 'winlocal', 'winiomlinux', 'winiomwin', 'httpsviya', 'httpviya', 'iomcom'] winlocal\n", 84 | "SAS Connection established. Subprocess id is 28656\n", 85 | "\n" 86 | ] 87 | }, 88 | { 89 | "data": { 90 | "text/plain": [ 91 | "Access Method = IOM\n", 92 | "SAS Config name = winlocal\n", 93 | "SAS Config file = C:\\ProgramData\\Anaconda3\\lib\\site-packages\\saspy\\sascfg_personal.py\n", 94 | "WORK Path = C:\\Users\\sastpw\\AppData\\Local\\Temp\\SAS Temporary Files\\_TD1468_d10a626_\\Prc2\\\n", 95 | "SAS Version = 9.04.01M5P09132017\n", 96 | "SASPy Version = 3.7.4\n", 97 | "Teach me SAS = False\n", 98 | "Batch = False\n", 99 | "Results = Pandas\n", 100 | "SAS Session Encoding = wlatin1\n", 101 | "Python Encoding value = windows-1252\n", 102 | "SAS process Pid value = 1468\n" 103 | ] 104 | }, 105 | "execution_count": 3, 106 | "metadata": {}, 107 | "output_type": "execute_result" 108 | } 109 | ], 110 | "source": [ 111 | "import saspy\n", 112 | "sas = saspy.SASsession()\n", 113 | "sas" 114 | ] 115 | }, 116 | { 117 | "cell_type": "code", 118 | "execution_count": 4, 119 | "metadata": { 120 | "collapsed": false 121 | }, 122 | "outputs": [ 123 | { 124 | "name": "stdout", 125 | "output_type": "stream", 126 | "text": [ 127 | "SAS_config_names = ['default', 'ssh', 'iomlinux', 'iomwin', 'winlocal', 'winiomlinux', 'winiomwin', 'httpsviya', 'httpviya', 'iomcom']\n", 128 | "\n", 129 | "SAS_config_options = {'lock_down': False,\n", 130 | " 'verbose' : True,\n", 131 | " 'prompt' : True\n", 132 | " }\n", 133 | "\n", 134 | "SAS_output_options = {'output' : 'html5'} # not required unless changing any of the default\n", 135 | "\n", 136 | "default = {'saspath' : '/opt/sasinside/SASHome/SASFoundation/9.4/bin/sas_u8'\n", 137 | " }\n", 138 | "\n", 139 | "ssh = {'saspath' : '/opt/sasinside/SASHome/SASFoundation/9.4/bin/sas_en',\n", 140 | " 'ssh' : '/usr/bin/ssh',\n", 141 | " 'host' : 'remote.linux.host', \n", 142 | " 'encoding': 'latin1',\n", 143 | " 'options' : [\"-fullstimer\"]\n", 144 | " }\n", 145 | "\n", 146 | "iomlinux = {'java' : '/usr/bin/java',\n", 147 | " 'iomhost' : 'linux.iom.host',\n", 148 | " 'iomport' : 8591,\n", 149 | " } \n", 150 | "\n", 151 | "iomwin = {'java' : '/usr/bin/java',\n", 152 | " 'iomhost' : 'windows.iom.host',\n", 153 | " 'iomport' : 8591,\n", 154 | " }\n", 155 | "\n", 156 | "winlocal = {'java' : 'java',\n", 157 | " 'encoding' : 'windows-1252',\n", 158 | " }\n", 159 | "\n", 160 | "winiomlinux = {'java' : 'java',\n", 161 | " 'iomhost' : 'linux.iom.host',\n", 162 | " 'iomport' : 8591,\n", 163 | " }\n", 164 | "\n", 165 | "winiomwin = {'java' : 'java',\n", 166 | " 'iomhost' : 'windows.iom.host',\n", 167 | " 'iomport' : 8591,\n", 168 | " }\n", 169 | "\n", 170 | "winiomIWA = {'java' : 'java',\n", 171 | " 'iomhost' : 'windows.iom.host',\n", 172 | " 'iomport' : 8591,\n", 173 | " 'sspi' : True\n", 174 | " }\n", 175 | "\n", 176 | "iomcom = {\n", 177 | " 'iomhost' : 'mynode.mycompany.org',\n", 178 | " 'iomport' : 8591,\n", 179 | " 'class_id': '440196d4-90f0-11d0-9f41-00a024bb830c',\n", 180 | " 'provider': 'sas.iomprovider',\n", 181 | " 'encoding': 'windows-1252'}\n", 182 | "\n", 183 | "httpsviya = {'url' : 'https://some.viya.deployment',\n", 184 | " 'context' : 'Data Mining compute context',\n", 185 | " 'authkey' : 'viya_user-pw',\n", 186 | " 'options' : [\"fullstimer\", \"memsize=1G\"]\n", 187 | " }\n", 188 | "\n", 189 | "httpviya = {'utl' : 'http://some.viya.deployment',\n", 190 | " 'context' : 'Data Mining compute context',\n", 191 | " 'authkey' : 'viya_user-pw',\n", 192 | " 'options' : [\"fullstimer\", \"memsize=1G\"]\n", 193 | " }\n", 194 | "\n" 195 | ] 196 | } 197 | ], 198 | "source": [ 199 | "sas.cat('C:\\ProgramData\\Anaconda3\\lib\\site-packages\\saspy\\sascfg_personal.py')" 200 | ] 201 | }, 202 | { 203 | "cell_type": "markdown", 204 | "metadata": { 205 | "collapsed": false 206 | }, 207 | "source": [ 208 | "## You can create a different sascfg file that you can refer to in SASsession()\n", 209 | "## Also, lets specify Java ourselves to see that it is used in the config file" 210 | ] 211 | }, 212 | { 213 | "cell_type": "code", 214 | "execution_count": 5, 215 | "metadata": { 216 | "collapsed": false 217 | }, 218 | "outputs": [ 219 | { 220 | "name": "stdout", 221 | "output_type": "stream", 222 | "text": [ 223 | "Generated configurations file: my_cfg.py\n", 224 | "\n" 225 | ] 226 | } 227 | ], 228 | "source": [ 229 | "from saspy import autocfg\n", 230 | "autocfg.main('my_cfg.py', java=r'C:\\ProgramData\\Oracle\\Java\\javapath\\java.exe')" 231 | ] 232 | }, 233 | { 234 | "cell_type": "code", 235 | "execution_count": 6, 236 | "metadata": { 237 | "collapsed": false 238 | }, 239 | "outputs": [ 240 | { 241 | "name": "stdout", 242 | "output_type": "stream", 243 | "text": [ 244 | "Using SAS Config named: autogen_winlocal\n", 245 | "SAS Connection established. Subprocess id is 12580\n", 246 | "\n" 247 | ] 248 | }, 249 | { 250 | "data": { 251 | "text/plain": [ 252 | "Access Method = IOM\n", 253 | "SAS Config name = autogen_winlocal\n", 254 | "SAS Config file = my_cfg.py\n", 255 | "WORK Path = C:\\Users\\sastpw\\AppData\\Local\\Temp\\SAS Temporary Files\\_TD30200_d10a626_\\Prc2\\\n", 256 | "SAS Version = 9.04.01M5P09132017\n", 257 | "SASPy Version = 3.7.4\n", 258 | "Teach me SAS = False\n", 259 | "Batch = False\n", 260 | "Results = Pandas\n", 261 | "SAS Session Encoding = wlatin1\n", 262 | "Python Encoding value = windows-1252\n", 263 | "SAS process Pid value = 30200\n" 264 | ] 265 | }, 266 | "execution_count": 6, 267 | "metadata": {}, 268 | "output_type": "execute_result" 269 | } 270 | ], 271 | "source": [ 272 | "import saspy\n", 273 | "sas = saspy.SASsession(cfgfile='my_cfg.py')\n", 274 | "sas" 275 | ] 276 | }, 277 | { 278 | "cell_type": "code", 279 | "execution_count": 7, 280 | "metadata": { 281 | "collapsed": false 282 | }, 283 | "outputs": [ 284 | { 285 | "name": "stdout", 286 | "output_type": "stream", 287 | "text": [ 288 | "SAS_config_names=[\"autogen_winlocal\"]\n", 289 | "\n", 290 | "SAS_config_options = {\n", 291 | "\t\"lock_down\": False,\n", 292 | "\t\"verbose\" : True\n", 293 | "\t}\n", 294 | "\n", 295 | "autogen_winlocal = {\n", 296 | "\t\"java\" : \"C:\\\\ProgramData\\\\Oracle\\\\Java\\\\javapath\\\\java.exe\",\n", 297 | "\t\"encoding\" : \"windows-1252\"}\n", 298 | "\n", 299 | "import os\n", 300 | "os.environ[\"PATH\"] += \";C:\\\\Program Files\\\\SASHome\\\\SASFoundation\\\\9.4\\\\core\\\\sasext\"\n" 301 | ] 302 | } 303 | ], 304 | "source": [ 305 | "sas.cat('my_cfg.py')" 306 | ] 307 | }, 308 | { 309 | "cell_type": "markdown", 310 | "metadata": {}, 311 | "source": [ 312 | "## Now lets see how to run this as a Batch script\n", 313 | "\n", 314 | "### First, run the default, right after a fresh install of saspy " 315 | ] 316 | }, 317 | { 318 | "cell_type": "markdown", 319 | "metadata": {}, 320 | "source": [ 321 | "```\n", 322 | "C:\\tom1>python \\ProgramData\\Anaconda3\\Lib\\site-packages\\saspy\\autocfg.py\n", 323 | "Generated configurations file: C:\\ProgramData\\Anaconda3\\lib\\site-packages\\saspy\\sascfg_personal.py\n", 324 | "\n", 325 | "\n", 326 | "C:\\tom1>type C:\\ProgramData\\Anaconda3\\lib\\site-packages\\saspy\\sascfg_personal.py\n", 327 | "SAS_config_names=[\"autogen_winlocal\"]\n", 328 | "\n", 329 | "SAS_config_options = {\n", 330 | " \"lock_down\": False,\n", 331 | " \"verbose\" : True\n", 332 | " }\n", 333 | "\n", 334 | "autogen_winlocal = {\n", 335 | " \"java\" : \"java\",\n", 336 | " \"encoding\" : \"windows-1252\"}\n", 337 | "\n", 338 | "import os\n", 339 | "os.environ[\"PATH\"] += \";C:\\\\Program Files\\\\SASHome\\\\SASFoundation\\\\9.4\\\\core\\\\sasext\"\n", 340 | "\n", 341 | "C:\\tom1>python\n", 342 | "Python 3.6.0 |Anaconda 4.3.0 (64-bit)| (default, Dec 23 2016, 11:57:41) [MSC v.1900 64 bit (AMD64)] on win32\n", 343 | "Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n", 344 | ">>> import saspy\n", 345 | ">>> sas = saspy.SASsession()\n", 346 | "Using SAS Config named: autogen_winlocal\n", 347 | "SAS Connection established. Subprocess id is 25264\n", 348 | "\n", 349 | ">>> sas\n", 350 | "Access Method = IOM\n", 351 | "SAS Config name = autogen_winlocal\n", 352 | "SAS Config file = C:\\ProgramData\\Anaconda3\\lib\\site-packages\\saspy\\sascfg_personal.py\n", 353 | "WORK Path = C:\\Users\\sastpw\\AppData\\Local\\Temp\\SAS Temporary Files\\_TD29172_d10a626_\\Prc2\\\n", 354 | "SAS Version = 9.04.01M5P09132017\n", 355 | "SASPy Version = 3.7.4\n", 356 | "Teach me SAS = False\n", 357 | "Batch = False\n", 358 | "Results = Pandas\n", 359 | "SAS Session Encoding = wlatin1\n", 360 | "Python Encoding value = windows-1252\n", 361 | "SAS process Pid value = 29172\n", 362 | "\n", 363 | "\n", 364 | ">>>\n", 365 | "```" 366 | ] 367 | }, 368 | { 369 | "cell_type": "markdown", 370 | "metadata": {}, 371 | "source": [ 372 | "### Now lets create a different sascfg file that you can refer to in SASsession()\n", 373 | "### And, lets specify Java ourselves to see that it is used in the config file\n", 374 | "* Note that Java is the 3rd positional parm. To skip SASHome, just specify None\n", 375 | "* also, use double quotes to enclose paths, especially when they have spaces in them like \"C:\\Program Files\\SASHome\"" 376 | ] 377 | }, 378 | { 379 | "cell_type": "markdown", 380 | "metadata": { 381 | "collapsed": true 382 | }, 383 | "source": [ 384 | "
\n",
385 |     "C:\\tom1>python \\ProgramData\\Anaconda3\\Lib\\site-packages\\saspy\\autocfg.py batch_cfg.py None \"C:\\ProgramData\\Oracle\\Java\\javapath\\java.exe\"\n",
386 |     "Generated configurations file: batch_cfg.py\n",
387 |     "\n",
388 |     "\n",
389 |     "C:\\tom1>type batch_cfg.py\n",
390 |     "SAS_config_names=[\"autogen_winlocal\"]\n",
391 |     "\n",
392 |     "SAS_config_options = {\n",
393 |     "        \"lock_down\": False,\n",
394 |     "        \"verbose\"  : True\n",
395 |     "        }\n",
396 |     "\n",
397 |     "autogen_winlocal = {\n",
398 |     "        \"java\"      : \"C:\\\\ProgramData\\\\Oracle\\\\Java\\\\javapath\\\\java.exe\",\n",
399 |     "        \"encoding\"  : \"windows-1252\"}\n",
400 |     "\n",
401 |     "import os\n",
402 |     "os.environ[\"PATH\"] += \";C:\\\\Program Files\\\\SASHome\\\\SASFoundation\\\\9.4\\\\core\\\\sasext\"\n",
403 |     "\n",
404 |     "C:\\tom1>python\n",
405 |     "Python 3.6.0 |Anaconda 4.3.0 (64-bit)| (default, Dec 23 2016, 11:57:41) [MSC v.1900 64 bit (AMD64)] on win32\n",
406 |     "Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n",
407 |     ">>> import saspy\n",
408 |     ">>> sas = saspy.SASsession(cfgfile='batch_cfg.py')\n",
409 |     "Using SAS Config named: autogen_winlocal\n",
410 |     "SAS Connection established. Subprocess id is 26772\n",
411 |     "\n",
412 |     ">>>\n",
413 |     "
" 414 | ] 415 | }, 416 | { 417 | "cell_type": "code", 418 | "execution_count": 8, 419 | "metadata": { 420 | "collapsed": true 421 | }, 422 | "outputs": [], 423 | "source": [ 424 | "### One last case showing all three parameters. " 425 | ] 426 | }, 427 | { 428 | "cell_type": "markdown", 429 | "metadata": {}, 430 | "source": [ 431 | "
\n",
432 |     "C:\\tom1>python \\ProgramData\\Anaconda3\\Lib\\site-packages\\saspy\\autocfg.py \\ProgramData\\Anaconda3\\Lib\\site-packages\\saspy\\all_parms.py \"C:\\Program Files\\SASHome\" java.exe\n",
433 |     "Generated configurations file: \\ProgramData\\Anaconda3\\Lib\\site-packages\\saspy\\all_parms.py\n",
434 |     "\n",
435 |     "\n",
436 |     "C:\\tom1>type  \\ProgramData\\Anaconda3\\Lib\\site-packages\\saspy\\all_parms.py\n",
437 |     "SAS_config_names=[\"autogen_winlocal\"]\n",
438 |     "\n",
439 |     "SAS_config_options = {\n",
440 |     "        \"lock_down\": False,\n",
441 |     "        \"verbose\"  : True\n",
442 |     "        }\n",
443 |     "\n",
444 |     "autogen_winlocal = {\n",
445 |     "        \"java\"      : \"java.exe\",\n",
446 |     "        \"encoding\"  : \"windows-1252\"}\n",
447 |     "\n",
448 |     "import os\n",
449 |     "os.environ[\"PATH\"] += \";C:\\\\Program Files\\\\SASHome\\\\SASFoundation\\\\9.4\\\\core\\\\sasext\"\n",
450 |     "C:\\tom1>python\n",
451 |     "Python 3.6.0 |Anaconda 4.3.0 (64-bit)| (default, Dec 23 2016, 11:57:41) [MSC v.1900 64 bit (AMD64)] on win32\n",
452 |     "Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n",
453 |     ">>> import saspy\n",
454 |     ">>> sas = saspy.SASsession(cfgfile='c:\\\\ProgramData\\\\Anaconda3\\\\Lib\\\\site-packages\\\\saspy\\\\all_parms.py')\n",
455 |     "Using SAS Config named: autogen_winlocal\n",
456 |     "SAS Connection established. Subprocess id is 18192\n",
457 |     "\n",
458 |     ">>> sas\n",
459 |     "Access Method         = IOM\n",
460 |     "SAS Config name       = autogen_winlocal\n",
461 |     "SAS Config file       = c:\\ProgramData\\Anaconda3\\Lib\\site-packages\\saspy\\all_parms.py\n",
462 |     "WORK Path             = C:\\Users\\sastpw\\AppData\\Local\\Temp\\SAS Temporary Files\\_TD24128_d10a626_\\Prc2\\\n",
463 |     "SAS Version           = 9.04.01M5P09132017\n",
464 |     "SASPy Version         = 3.7.4\n",
465 |     "Teach me SAS          = False\n",
466 |     "Batch                 = False\n",
467 |     "Results               = Pandas\n",
468 |     "SAS Session Encoding  = wlatin1\n",
469 |     "Python Encoding value = windows-1252\n",
470 |     "SAS process Pid value = 24128\n",
471 |     "\n",
472 |     "\n",
473 |     ">>>\n",
474 |     "
" 475 | ] 476 | }, 477 | { 478 | "cell_type": "code", 479 | "execution_count": null, 480 | "metadata": { 481 | "collapsed": true 482 | }, 483 | "outputs": [], 484 | "source": [] 485 | } 486 | ], 487 | "metadata": { 488 | "kernelspec": { 489 | "display_name": "Python 3", 490 | "language": "python", 491 | "name": "python3" 492 | }, 493 | "language_info": { 494 | "codemirror_mode": { 495 | "name": "ipython", 496 | "version": 3 497 | }, 498 | "file_extension": ".py", 499 | "mimetype": "text/x-python", 500 | "name": "python", 501 | "nbconvert_exporter": "python", 502 | "pygments_lexer": "ipython3", 503 | "version": "3.6.0" 504 | } 505 | }, 506 | "nbformat": 4, 507 | "nbformat_minor": 2 508 | } 509 | -------------------------------------------------------------------------------- /User_contrib/SASpy_with_R_markdown.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "SAS and Markdown documents" 3 | subtitle: "Using SASpy with R markdown and knitr" 4 | author: "Frederico Muñoz" 5 | date: "October 2021" 6 | output: 7 | pdf_document: 8 | fig_caption: yes 9 | --- 10 | 11 | # Introduction 12 | 13 | [Markdown](https://daringfireball.net/projects/markdown/) is a 14 | document markup language that is widely used to produce documentation 15 | that can be easily edited, tracked through version control and 16 | transformed into many different output formats. 17 | 18 | One popular application of Markdown is [R 19 | Markdown](https://bookdown.org/yihui/rmarkdown/), as used by the 20 | package `knitr` and that allows the inclusion and execution of R code 21 | in markdown documents: it's a similar approach to Jupyter notebooks, 22 | but based on Markdown documents and that implements [literate 23 | programming](https://en.wikipedia.org/wiki/Literate_programming). 24 | 25 | It should be stressed that R Markdown documents are not limited to the 26 | R language: the `knitr` library is in R and R is obviously a 27 | first-class citizen, but it supports many different _engines_, one of 28 | them being Python, and a single document can contain code in different 29 | languages: as we will see we can use Python, R and SAS using SASpy. 30 | 31 | # The challenge 32 | 33 | SASpy output of non-tabular results is HTML, which makes sense 34 | considering that all the target environments where it is used are 35 | browser-based: Jupyter, Zeppelin and Databricks are the available 36 | options at the time of writing. Markdown documents, however, do not 37 | render HTML: the output of the code chunk should be in markdown, and 38 | this is what then makes possible for tools like 39 | [Pandoc](https://pandoc.org) to convert to target formats. 40 | 41 | This highlights a good use-case for using SASpy with markdown: 42 | producing PDFs that can be highly customisable and include all the 43 | tables and images (something currently not possible by exporting a 44 | Jupyter notebook, for example). 45 | 46 | 47 | # Making it work 48 | 49 | To make SASpy work transparently in a `.Rmd` document we will take the 50 | following approach: 51 | 52 | * For _tables_ we can take advantage of the default format that SASpy 53 | uses, Pandas, that support markdown. 54 | * For everything else, including plots, we will parse the HTML and 55 | convert it to markdown. 56 | 57 | 58 | ```{r setup, include=FALSE} 59 | library(reticulate) 60 | use_condaenv("saspy_clean") 61 | 62 | library(knitr) 63 | hook_output = knit_hooks$get('output') 64 | knit_hooks$set(output = function(x, options) { 65 | # this hook is used only when the linewidth option is not NULL 66 | if (!is.null(n <- options$linewidth)) { 67 | x = knitr:::split_lines(x) 68 | # any lines wider than n should be wrapped 69 | if (any(nchar(x) > n)) x = strwrap(x, width = n) 70 | x = paste(x, collapse = '\n') 71 | } 72 | hook_output(x, options) 73 | }) 74 | ``` 75 | 76 | We start by establishing the session, exactly like we would do in a 77 | Jupyter notebook, with a twist: we activate [batch 78 | mode](https://sassoftware.github.io/saspy/advanced-topics.html?highlight=batch#using-batch-mode) 79 | to have access to the HTML code, instead of the iPython object. 80 | 81 | ```{python, saspy-setup, warning=FALSE, echo="true", results="markup", linewidth=60} 82 | import saspy 83 | import pandas as pd 84 | sas = saspy.SASsession(cfgname='mycfg', results='Pandas') 85 | ``` 86 | 87 | With batch mode activated we always get a dictionary with two keys, 88 | `LOG` and `LST`, the latter containing the HTML code (or Pandas object 89 | for tabular data). 90 | 91 | ```{python, saspy-batch, warning=FALSE, echo="true", results="markup", linewidth=50} 92 | sas.set_batch(True) 93 | ``` 94 | 95 | For Pandas we can make use of the `to_markdown` method directly: the 96 | utility function `p2m` simply wraps this to print the markdown table. 97 | 98 | For HTML we get the body of the HTML (using BeautifulSoup), and 99 | convert it to markdown through the `markdownify` library. The `h2m` 100 | function encapsulates this logic. 101 | 102 | ```{python, saspy-utility, warning=FALSE, echo="true"} 103 | from bs4 import BeautifulSoup as BS 104 | from markdownify import markdownify as md 105 | 106 | def p2m (p): 107 | print(p.to_markdown(tablefmt="github")) 108 | 109 | def h2m (d): 110 | html = d['LST'] 111 | soup = BS(html) 112 | body = soup.find('body') 113 | print(md(str(body)).strip()) 114 | ``` 115 | 116 | With this functions defined the code is almost identical to what would 117 | be the equivalent Jupyter notebook: only the last command changes 118 | since we want to use the utility functions to output markdown. 119 | 120 | The `columnInfo` method of SASpy will produce a markdown table (which 121 | will then be correctly displayed in a PDF, through \LaTeX). 122 | 123 | ```{python, saspy-table, warning=FALSE, echo="true", results="asis"} 124 | iris = sas.sasdata('iris','sashelp') 125 | p2m(iris.columnInfo()) 126 | ``` 127 | 128 | The same applies to plots, in this case a heatmap (which runs `proc 129 | sgplot heatmap` in the backend): 130 | 131 | ```{python, saspy-heatmap, results="asis", echo="true", warning=FALSE, fig.pos="!H"} 132 | h2m(iris.heatmap('PetalLength', 'PetalWidth')) 133 | ``` 134 | 135 | Submitting SAS code works the same way: here we use `proc sgplot 136 | histogram` directly via sas.submit: 137 | 138 | ```{python, saspy-submit, results="asis", echo="true", warning=FALSE, fig.pos="!H"} 139 | h2m(sas.submit(""" 140 | proc sgplot data=sashelp.iris ; 141 | histogram SepalLength; 142 | run; 143 | """)) 144 | ``` 145 | 146 | # From Rmd to PDF 147 | 148 | R Markdown can be converted into standard markdown in several ways, 149 | but `knitr` is the most common one and is integrated into [RStudio](https://www.rstudio.com/), for example. 150 | 151 | In the shell the following command can be used (depends on having R and `knitr` installed, of course): 152 | 153 | ```{bash, eval=FALSE} 154 | $ R -e 'library(knitr); knit("SASpy_with_R_markdown.Rmd", 155 | "SASpy_with_R_markdown.md")' 156 | ``` 157 | 158 | To produce the PDF there are again many different options, with 159 | `pandoc` being one of the most common. There are quite a few options 160 | to modify the output, namely the ability to specify a \LaTeX template 161 | -- check pandoc's documentation for details. 162 | 163 | 164 | ```{bash, eval=FALSE, linewidth=50} 165 | $ pandoc SASpy_with_R_markdown.md -o SASpy_with_R_markdown.pdf 166 | ``` 167 | 168 | # Other markup formats 169 | 170 | The same approach describe in this document can be applied to other 171 | formats, like [Asciidoc](https://asciidoc-py.github.io/) or 172 | [reStructuredText](https://docutils.sourceforge.io/rst.html), as long 173 | as the utility functions are adapted to output the correct markup. 174 | -------------------------------------------------------------------------------- /User_contrib/SASpy_with_R_markdown.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "SAS and Markdown documents" 3 | subtitle: "Using SASpy with R markdown and knitr" 4 | author: "Frederico Muñoz" 5 | date: "October 2021" 6 | output: 7 | pdf_document: 8 | fig_caption: yes 9 | --- 10 | 11 | # Introduction 12 | 13 | [Markdown](https://daringfireball.net/projects/markdown/) is a 14 | document markup language that is widely used to produce documentation 15 | that can be easily edited, tracked through version control and 16 | transformed into many different output formats. 17 | 18 | One popular application of Markdown is [R 19 | Markdown](https://bookdown.org/yihui/rmarkdown/), as used by the 20 | package `knitr` and that allows the inclusion and execution of R code 21 | in markdown documents: it's a similar approach to Jupyter notebooks, 22 | but based on Markdown documents and that implements [literate 23 | programming](https://en.wikipedia.org/wiki/Literate_programming). 24 | 25 | It should be stressed that R Markdown documents are not limited to the 26 | R language: the `knitr` library is in R and R is obviously a 27 | first-class citizen, but it supports many different _engines_, one of 28 | them being Python, and a single document can contain code in different 29 | languages: as we will see we can use Python, R and SAS using SASpy. 30 | 31 | # The challenge 32 | 33 | SASpy output of non-tabular results is HTML, which makes sense 34 | considering that all the target environments where it is used are 35 | browser-based: Jupyter, Zeppelin and Databricks are the available 36 | options at the time of writing. Markdown documents, however, do not 37 | render HTML: the output of the code chunk should be in markdown, and 38 | this is what then makes possible for tools like 39 | [Pandoc](https://pandoc.org) to convert to target formats. 40 | 41 | This highlights a good use-case for using SASpy with markdown: 42 | producing PDFs that can be highly customisable and include all the 43 | tables and images (something currently not possible by exporting a 44 | Jupyter notebook, for example). 45 | 46 | 47 | # Making it work 48 | 49 | To make SASpy work transparently in a `.Rmd` document we will take the 50 | following approach: 51 | 52 | * For _tables_ we can take advantage of the default format that SASpy 53 | uses, Pandas, that support markdown. 54 | * For everything else, including plots, we will parse the HTML and 55 | convert it to markdown. 56 | 57 | 58 | 59 | 60 | We start by establishing the session, exactly like we would do in a 61 | Jupyter notebook, with a twist: we activate [batch 62 | mode](https://sassoftware.github.io/saspy/advanced-topics.html?highlight=batch#using-batch-mode) 63 | to have access to the HTML code, instead of the iPython object. 64 | 65 | 66 | ```python 67 | import saspy 68 | import pandas as pd 69 | sas = saspy.SASsession(cfgname='mycfg', results='Pandas') 70 | ``` 71 | 72 | ``` 73 | ## SAS server started using Context Data Mining compute 74 | context with 75 | SESSION_ID=41e0ab9c-5409-406a-b563-4e0213976d38-ses0000 76 | ``` 77 | 78 | With batch mode activated we always get a dictionary with two keys, 79 | `LOG` and `LST`, the latter containing the HTML code (or Pandas object 80 | for tabular data). 81 | 82 | 83 | ```python 84 | sas.set_batch(True) 85 | ``` 86 | 87 | For Pandas we can make use of the `to_markdown` method directly: the 88 | utility function `p2m` simply wraps this to print the markdown table. 89 | 90 | For HTML we get the body of the HTML (using BeautifulSoup), and 91 | convert it to markdown through the `markdownify` library. The `h2m` 92 | function encapsulates this logic. 93 | 94 | 95 | ```python 96 | from bs4 import BeautifulSoup as BS 97 | from markdownify import markdownify as md 98 | 99 | def p2m (p): 100 | print(p.to_markdown(tablefmt="github")) 101 | 102 | def h2m (d): 103 | html = d['LST'] 104 | soup = BS(html) 105 | body = soup.find('body') 106 | print(md(str(body)).strip()) 107 | ``` 108 | 109 | With this functions defined the code is almost identical to what would 110 | be the equivalent Jupyter notebook: only the last command changes 111 | since we want to use the utility functions to output markdown. 112 | 113 | The `columnInfo` method of SASpy will produce a markdown table (which 114 | will then be correctly displayed in a PDF, through \LaTeX). 115 | 116 | 117 | ```python 118 | iris = sas.sasdata('iris','sashelp') 119 | p2m(iris.columnInfo()) 120 | ``` 121 | 122 | | | Member | Num | Variable | Type | Len | Pos | Label | 123 | |----|--------------|-------|-------------|--------|-------|-------|-------------------| 124 | | 0 | SASHELP.IRIS | 4 | PetalLength | Num | 8 | 16 | Petal Length (mm) | 125 | | 1 | SASHELP.IRIS | 5 | PetalWidth | Num | 8 | 24 | Petal Width (mm) | 126 | | 2 | SASHELP.IRIS | 2 | SepalLength | Num | 8 | 0 | Sepal Length (mm) | 127 | | 3 | SASHELP.IRIS | 3 | SepalWidth | Num | 8 | 8 | Sepal Width (mm) | 128 | | 4 | SASHELP.IRIS | 1 | Species | Char | 10 | 32 | Iris Species | 129 | 130 | The same applies to plots, in this case a heatmap (which runs `proc 131 | sgplot heatmap` in the backend): 132 | 133 | 134 | ```python 135 | h2m(iris.heatmap('PetalLength', 'PetalWidth')) 136 | ``` 137 | 138 | ![The SGPlot Procedure](data:image/png;base64,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) 139 | 140 | Submitting SAS code works the same way: here we use `proc sgplot 141 | histogram` directly via sas.submit: 142 | 143 | 144 | ```python 145 | h2m(sas.submit(""" 146 | proc sgplot data=sashelp.iris ; 147 | histogram SepalLength; 148 | run; 149 | """)) 150 | ``` 151 | 152 | ![The SGPlot Procedure](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoAAAAHgCAIAAAC6s0uzAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAb7klEQVR4nO3de3TU9d3g8V/iZEgQKBYRL+tBSdW1gi0PRUULVOutrcUiSvG2FHQLRivSY5VVgQfZ4qWP9UoWFY1ctlaponSlStFDxcuWitnk8VKtukdcH5UotDSEEJLJ/jFtTgQvJEz4jMnrdTycmW9+85tP/InvzPxmJgVVVVUJALB7pZIkOfLII6PHAIAupLq6ujB6BgDoigQYAAIIMAAEEGAACCDAABBAgAEggAADQAABBoAAAgwAAVLRA+TA/1zycG1tbfQUAPAP++/b7/vfOeWzt+kMAa6trZ004b9ETwEA/3BnxcLP3cZT0AAQQIABIEBneAoa2HXbtm2bPXt29BS5dMUVV/To0SN6CvhUAgwkSZJs27btuuuvv2DyZdGD5Maie8q/P+a8oV87NHoQ+FQCDPxDUSrVaQL8m/sXRI8An8M5YAAIIMAAEECAASCAAANAAAEGgAACDAABBBgAAggwAAQQYAAIIMAAEMBHUUL7vfrqqyefckrSHD1HLjQ3Nzc2NkVPAV2IAEP7bdu2rVu3kpvnLY4eJAc2/e2vPxr3vegpoAsRYNglRUVF++53QPQUOVBcUhI9AnQtzgEDQAABBoAAAgwAAQQYAAIIMAAEEGAACBAZ4OXLl3/729/u3r179+7dzz///Jqamux6eXl5aWlpaWlpeXl54HgA0HEiA7xo0aLJkydv2rSptrb21FNPPf/885MkqaioWLJkyZo1ayorK5cvX15RURE4IQB0kMgA33///WeddVYqlSosLDz33HOfeuqpJEnmz59/7bXX9unTp1evXjNnzpw/f37ghADQQfLlk7BeeumlI488MkmSysrK4447Lrs4ZMiQysrK1ptlMpn3169vvVJY6DT2F8yHH3740ksvRU+RG2+99VZjk89PBtojLwJcV1c3adKkm2++OUmSLVu2tDS1sLCwoaGh9ZbbGhvXVla3XunWLb3b5iQnnn766Qv/648POfQ/Rw+SAxs3fLRlS130FMAXUnyAP/roo7Fjx/7sZz8bPnx4kiQlJSWZTCbb4Ewmk05/rK/d0unvf+fk7fZwZ8XC3TYtOTFk6DFzfnlX9BQ58NSKx26/6b9HTwF8IQU/f/vKK6+cdtppc+bM+cEPfpBdGTp06OrVq7OX165dO3jw4LjpAKCjRAb48ccfv/jiix9++OGjjz66ZXHixIkzZsyoqanZuHHj9OnTL7zwwsAJAaCDRD4F/Z3vfCdJkv33379lpbm5efz48Zs3bz7mmGOSJJk6deqECRPC5gOADhMZ4Obm5k9cLysrKysr283DAMDu5D08ABBAgAEggAADQAABBoAAAgwAAQQYAAIIMAAEEGAACCDAABBAgAEggAADQAABBoAAAgwAAQQYAAIIMAAEEGAACCDAABBAgAEggAADQAABBoAAAgwAAQQYAAIIMAAEEGAACCDAABBAgAEggAADQAABBoAAAgwAAQQYAAIIMAAEEGAACCDAABBAgAEggAADQAABBoAAAgwAAQQYAAIIMAAEEGAACCDAABBAgAEggAADQAABBoAAAgwAAQQYAAIIMAAEEGAACCDAABBAgAEggAADQAABBoAAAgwAAQQYAAIIMAAEEGAACCDAABBAgAEggAADQAABBoAAAgwAAQQYAAIIMAAEEGAACCDAABBAgAEggAADQAABBoAAAgwAAQQYAAIIMAAEEGAACCDAABBAgAEggAADQAABBoAAAgwAAQQYAAIIMAAEEGAACCDAABBAgAEggAADQAABBoAAwQFubGy87rrrCgoKWlYKdhA4HgB0kOAA9+jR49lnn91usfnjQgYDgA6Vir37+vr6JEk8zAWgq8m7c8A9e/bs1atXOp0uLS1dsGBB9DgA0CGCHwHvaNOmTdkLb7/99pQpUw466KCRI0e2fHXbtsYXq6pab1+UKtqt8wFALuRdgFv0799/8eLFI0aMePHFF1sWCwqSbt2KW29WlMrfbwEAPk1e1yuTyey5556tV1Kp1NcHHbHdZs/87z/uxqEAIAfy7hzw2WefvWLFikwmU1NTM3HixBtvvDF6IgDIvXwM8OzZs9Pp9EknnXTeeecNGzYseiIAyL28eAq69Zt9R40aNWrUqMBhAGA3yIsAA+RWY2Pjv1dXNtZ9FD1IbgwcOLBnz57RU5BjAgx0QvVb6n5x/c9Lios/f9O89/prry66/zejv39K9CDkmAADnVBhYeHN/2Nhn733iR4kB358/ugtWxujpyD38u5FWADQFQgwAAQQYAAIIMAAEECAASCAAANAAAEGgAACDAABBBgAAggwAAQQYAAIIMAAEECAASCA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suWrW7c2LP3t8tbbFxd3263zAUAu5F2AW/Tv33/x4sUjRox48cUXWxaLi7ud98Mzt9vyzoqFu3c0ANhVefcUdGuZTGbPPfeMngIAci/vAnz22WevWLEik8nU1NRMnDjxxhtvjJ4IAHIvHwM8e/bsdDp90kknnXfeecOGDYueCAByL+/OAY8aNWrUqFHRUwBAx8q7AAPQ2nv/8f9mXn35v103I3qQ3Jj2364Ze+bo6CnyggAD5LVMJvPDcy849PAjogfJgbvn/vK1N9+JniJfCDBAvus/4CuHH/G16ClyoPdeX44eIY/k3YuwAKArEGAACCDAABBAgAEggAADQAABBoAAAgwAAQQYAAIIMAAE8ElYAOwm9fVbata//8orr0QPkht9+/bt27dvu28uwADsJm++/ucX//T87377cPQgObBhw0fjzhk/97Z/a/ceBBiA3aSwsPCSn1592g/GRg+SAxV33rZl65bm5qSgoJ17cA4YAAIIMAAEEGAACCDAABBAgAEggAADQAABBoAAAgwAAQQYAAIIMAAEEGAACCDAABBAgAEggAADQAABBoAAAgwAAQQYAAIIMAAEEGAACCDAABBAgAEggAADQAABBoAAqegBOtDll/9s4cKF0VPkRl1d3eix50VPAUDOdOYA//Wvfz17/KTvjhoTPUgOTL/ikqQ5eggAcqczBzhJkh49e365T9/oKXIgXZSOHgGAXHIOGAACCDAABBBgAAggwAAQQIABIIAAA0AAAQaAAAIMAAEEGAACCDAABBBgAAggwAAQQIABIIAAA0AAAQaAAAIMAAEEGAACCDAABBBgAAggwAAQQIABIIAAA0AAAQaAAAIMAAEEGAACCDAABBBgAAggwAAQQIABIIAAA0AAAQaAAAIMAAEEGAACCDAABBBgAAggwAAQQIABIIAAA0AAAQaAAAIMAAEEGAACCDAABBBgAAggwAAQIB8DXF5eXlpaWlpaWl5eHj0LAHSIVPQA26uoqFiyZMmaNWuKiorOOeeckpKSCRMmRA8FADmWd4+A58+ff+211/bp06dXr14zZ86cP39+9EQAkHt59wi4srLyuOOOy14eMmRIZWVl6682NjW98eZbrVf22GOPz9jb/1m7piApyPmQu9/77/9HY1PTo7/5VfQgOfCX11+t+eD9zvG9vPpy9ebNmzvH97K5bnNzJtM5vpckSZqT5sf/19IePXpGD5IDjY2Nq1f9/u233ogeJAc2121+8U/PNzU2Rg+SA6+8XDXgK4fuyh4KqqqqjjzyyFwNtOsKCgqam5tbrqZSqcZWh6ph27bn17zQevt0UdFrf3lja0PD7hsRAD5Tz557nnPmmM/YoLq6Ou8eAZeUlGQymcLCwiRJMplMOp1u/dV0UdHI44Ztd5NhR33jE3f19LPP9+279+GHHtJBo9I+//ftdX95862TT/hW9CB8TGNj44L7H7zg/HOiB2F7jy5//Ogh/7Jvv32iB+FjXqz696amxqH/Mrjde8i7c8BDhw5dvXp19vLatWsHD27/9wYAeSvvAjxx4sQZM2bU1NRs3Lhx+vTpF154YfREAJB7efcU9Pjx4zdv3nzMMcckSTJ16lTvQQKgU8q7ACdJUlZWVlZWFj0FAHSgvHsVdA41NDQUFhamUvn4Q0ZX1tTU1NjY2K1bt+hB2N7muro9u3ePnoLt1ddvTaeLsi9NJX9s27atOUnSRUXtu3k+vgo6h7Z7BTV5Yo899vjsd28TRX3zU3Gxn1bzUVF709vCj1QAEECAASCAAANAAAEGgAACDAABOmGA//znP3dv9WLO8vLy0tLS0tLS8vLywKm6rIIdZNcdl3CPP/7417/+9eLi4tLS0gULFmQXHZdw/srkp/fee++MM87o3r17cXHxGWec8cEHH2TXd+W4dLYAZzKZ8847b8uWLdmrFRUVS5YsWbNmTWVl5fLlyysqKmLH65qaPy5xXPLAH//4x0svvXTx4sX19fUPPvjgqlWrEsclP7T+y/K73/1u3LhxiUOTB8aNGzd48OBNmzbV1tYOGjQoN8elqqqquRO58sorb7311uSf/xEfe+yxTz/9dPbymjVrjj322LjRuqhkhwA3Oy55YMyYMU899dR2i45LvjnqqKNeffXVZocmDxQVFbVcbmpqyl7dleNSVVXVqQL8zDPPnHjiic2t/qdfUlLS1NSUvdzU1FRSUhI2XFfVs2fPnj17FhUVDRgw4L777ssuOi7hevbsefPNN++7776DBg1qKbHjkld+//vfjxkzJnvZoQk3evToO++8s6mpaevWrVddddXpp5/evGvHpaqqqvN8ElZtbe3FF1/8xBNPtF7csmVLy+e3FRYWNjQ0RIzWpW3atCl74e23354yZcpBBx00cuRIxyXc3//+97Vr11ZXV3/pS1/6yU9+kiTJ8ccf77jklZkzZ955553Zyw5NuHvuuWfYsGGTJk1KkuSQQw559tlnk10+Lp3nHPDFF188Y8aMfv36tV4sKSnJZDLZy5lMxodTBurfv//ixYunTp2aOC55oKSkZNGiRX379k2n07fffrvjkm/+8Ic/9OnTZ+DAgdmrDk24CRMmXHLJJdu2bWtqarrssssuuOCCZJePS+cJ8MKFC8eMGdPyosHsn0OHDl29enV2g7Vr1w4ePDhyxC4vk8nsueeeieOSB44++ui1a9e2XN1rr70SxyWfXHPNNddee23LVYcm3PLlyy+55JJUKlVYWFhWVvb4448nu35cOtM54BbJP88B33fffSNGjFi/fv2GDRtOOeWUe++9N3awLmjcuHFPPPFEU1PT+vXrx4wZ89xzzzU7LnngoYceOvbYY9evX79169aLL744exrYcckTzzzzzPe+973WKw5NuBNOOOGqq67KPgKeO3fuiBEjmnftuHS2F2G1SFq98nbu3LkDBgwYMGDA7bffHjhSl/Xoo49+85vf3GOPPb72ta8tXbq0Zd1xCffrX//6iCOO2HfffW+99daWRcclH3zrW9964YUXtlt0aGKtX7/+rLPO6tatW7du3c4666z169dn19t9XKqqqjrz7wMGgPxUXV3dec4BA8AXiAADQAABBoAAAgwAAQQYAAIIMAAEEGAACCDAABBAgAEggAADQAABBoAAAgwAAQQYdpOampqf/vSnBxxwQDqdPvTQQ2fNmlVXV5eTPWd/+/XOLObwjnZm/7W1tZdddtmu3+/ll1++adOmXd8P5BUBht3ktNNOu/rqq999992GhoaXXnpp6NCh11xzTfRQHWvixImXXnrpru+nrKxs0qRJu74fyCsCDLvJunXrunXrlr2cTqe/+93v/vKXv8xe3bhx42mnnVZcXHzqqadu3Lgxu1hQUPDII4/07t172LBhH3zwQXbx4Ycf/sY3vpFOp0tLS1euXNnWGT7tjubNm7fffvvtt99+y5Ytyy7W1NQMGzase/fu8+bNyz7Ybfmz5bHvjrdqbcWKFfvvv/+AAQOytyovL+/Xr1+/fv2WLVt2xx139O7du1+/fg8//HDLDJ+xwYABA/r27btixYq2fr+QzwQYdpPbbrvtqKOOKi8vr6mp2e5L06dPHz16dH19/ZVXXjl79uyW9eeee27Dhg1nn332jBkzsitLly7913/914aGhptuuqmsrKytM3zaHb377rvvvPPO3Llzp0yZkl2ZNm3a5MmT6+rqamtrsyvNzc3ZP7MXPvFWrc2bN+/0009vubpu3bp33323oqLiRz/6UWVl5fr16+++++6pU6fu5AZjxoy566672vr9Qj4rqKqqOvLII6PHgC7hpZdeuvfee5csWXLQQQddeumlZ511Vna9X79+77zzTjqdzmQyBxxwwHvvvZckSUFBwebNm7t3797Q0LD33nvveBI0lUo1NjZmt2yJYotPXPy0O2rZsmWfvXv33rBhQ2FhYesNWm/5ibdqrVevXhs2bEilUjtzw8/d4NP+JcAXVHV1tQBDgNWrV//iF7844YQTsq9Rav2Cpj322GPHJqXT6YaGhiRJXn/99ZkzZ65atepvf/vbli1bduxii09b/Ow7arncuqmfG+BPvK+WmXfmhjuz59Y7hC+66upqT0FDgOHDhz/yyCMtTyzvs88+TU1N2Wd3Wz+UrK+vT5KkoaFhr732yq6MGjVq2LBhr732WvteQf1pd7SjXr16ZWv32Zt9huLi4hz2sqGhobi4OFd7g3wgwLCbTJo06ZFHHsk2adOmTdddd92IESOyX/rhD384f/78JEmqq6snTJjQcpPsado77rij5cnq999/f/To0UmSTJs2rR0zfNod7WjcuHHZLVu/VHvQoEHZZ613xoknnrhq1ap2DPmJnn/++ZNPPjlXe4N8IMCwm9x0002VlZWHHHJIKpU67LDDNm7c+Ktf/Sr7pTlz5qxcuTKVSo0ePfqEE05oucmQIUN69Ojx6KOPzpo1K7syd+7cww8/fNCgQYcddti+++7b8krmT1TQymff0Y5mzZq1aNGi4uLiQw45pGXxscceGzly5PHHH78z3+/kyZMfeeSRndlyZyxZsuTHP/5xrvYG+cA5YMhTn3hidfdrbGzMvhCsHbcdO3bsnDlzvvKVr+ziDG+99da0adMefPDBXdwP5A/ngIFPdu6552bfZ3zDDTe0+2f0e++9t7y8fNeHue222+69995d3w/kFY+AIU8VFxdnX4QVYsmSJbNmzXrjjTcGDhy4cOHCr371q1GTQKfkbUgAEMBT0AAQQ4ABIIAAA0AAAQaAAAIMAAEEGAACCDAABBBgAAggwAAQQIABIIAAA0CAVJIk1dXV0WMAQNfy/wGQqoDQrTZTeAAAAABJRU5ErkJggg==) 153 | 154 | # From Rmd to PDF 155 | 156 | R Markdown can be converted into standard markdown in several ways, 157 | but `knitr` is the most common one and is integrated into [RStudio](https://www.rstudio.com/), for example. 158 | 159 | In the shell the following command can be used (depends on having R and `knitr` installed, of course): 160 | 161 | 162 | ```bash 163 | $ R -e 'library(knitr); knit("SASpy_with_R_markdown.Rmd", 164 | "SASpy_with_R_markdown.md")' 165 | ``` 166 | 167 | To produce the PDF there are again many different options, with 168 | `pandoc` being one of the most common. There are quite a few options 169 | to modify the output, namely the ability to specify a \LaTeX template 170 | -- check pandoc's documentation for details. 171 | 172 | 173 | 174 | ```bash 175 | $ pandoc SASpy_with_R_markdown.md -o SASpy_with_R_markdown.pdf 176 | ``` 177 | 178 | # Other markup formats 179 | 180 | The same approach describe in this document can be applied to other 181 | formats, like [Asciidoc](https://asciidoc-py.github.io/) or 182 | [reStructuredText](https://docutils.sourceforge.io/rst.html), as long 183 | as the utility functions are adapted to output the correct markup. 184 | -------------------------------------------------------------------------------- /User_contrib/SASpy_with_R_markdown.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sassoftware/saspy-examples/58605bdfd88356d96becbf6becec5b468b4cf21e/User_contrib/SASpy_with_R_markdown.pdf --------------------------------------------------------------------------------