SAS Certification Handout #11: Adv. Prog. Ch. 9-10 /************ Ch. 9 ********************/ /* SAS Macros -- like writing your own functions in SAS; especially useful for reproducing analyses or reports for new data Macro keys to remember: * %xyz calls macro named "xyz" * &abc is reference to macro variable (inside macro call, or elsewhere in SAS code if &abc is a global variable); Ch. 9 focuses on macro variables */ /* p. 308: automatic macro variables; see these using DICTIONARY table (recall Adv. Ch. 8 [end of handout #10] */ describe table dictionary.macros; create table DICTIONARY.MACROS ( scope char(32) label='macro Scope', name char(32) label='macro Variable Name', offset num label='offset into Macro Variable', value char(200) label='macro Variable Value' ); /* See [and use in simple title statements] a few automatic macro variables */ title1 "Report created &SYSDAY, &SYSDATE9"; title2 'in ' &_CLIENTAPP', running SAS Version' &SYSVER; footnote1 'Note that single quotes do not process macro variable names: &SYSDAY'; select scope, name, offset, value from dictionary.macros where name in ('SYSDATE9','SYSDAY','SYSVER','_CLIENTAPP'); title1; footnote1; Report created Monday, 16FEB2015 in &_CLIENTAPP, running SAS Version 9.4 Macro Scope Macro Variable Name Offset into Macro Variable Macro Variable Value AUTOMATIC SYSDATE9 AUTOMATIC SYSDAY 0 16FEB2015 0 Monday AUTOMATIC SYSVER 0 9.4 Note that single quotes do not process macro variable names: &SYSDAY 1
/* %LET creates user-defined macro variables, which are always string variables, even if entered as numeric (p. 310) */ %LET HOnum=11; /* Make example data -- same as Handouts #9 and #10. In SAS Studio, after creating SAS_Cert folder with username jrstevens: */ libname cert '/home/jrstevens/sas_cert'; data cert.sales&honum; format LastName $9. Sales dollar8.; input LastName $ EmplID Division $ Sales; cards; Smith 315 A 553312 Nelson 333 A 555827 Uribe 612 C 867159 Larson 331 B 521592 Larsen 216 B 123513 Rasmussen 217 A 125321 Knecht 861 B 983152 D'Angelou 772 A 332512 Larson 331 C 381592 ; /* pp. 312-315: How SAS processes macro variables: 1. Tokens [p. 314; like word units] make up SAS statements. 2. Statements are sent to the compiler until SAS hits a step Boundary (like RUN), when the code in the compiler is executed. Macro variable references are processed in step 1 here. Use SYMBOLGEN or %PUT (pp. 315-318) to see (in LOG) what macro variable values are used (for debugging, maybe) */ %let minsales = 700000; options symbolgen; select lastname, sales from cert.sales&honum where sales > &minsales; options nosymbolgen; LastName Sales Uribe $867,159 Knecht $983,152 NOTE: The data set CERT.SALES11 has 9 observations and 4 variables. 61 62 select lastname, sales 63 from cert.sales&honum SYMBOLGEN: Macro variable HONUM resolves to 11 64 where sales SYMBOLGEN: Macro variable MINSALES resolves to 700000 64! > &minsales; 65 2
data work.temp; set cert.sales&honum; if sales > &minsales; %PUT 'Macro variable &minsales has value' &minsales; 58 data work.temp; set cert.sales&honum; 59 if sales > &minsales; 60 %PUT 'Macro variable &minsales has value' &minsales; 'Macro variable &minsales has value' 700000 61 /* What if you need to allow special characters (like ' ; & % * /, ) in macro variable value? (Need to prevent compiler from treating these like operators or checks, so they need to be masked; often you can just be careful with double-quotes instead.) pp. 319-322: %STR < %NRSTR [handles & and %] < %BQUOTE [performs during execution] */ %let reptitle = "Company's biggest sellers; sales above &minsales"; title1 "&reptitle"; /* In SAS Studio, this gives a weird long title; in desktop SAS, it sees "" in title1 and chokes */ 'Companys biggest sellers; sales above 700000""; OPTIONS NONOTES NOSTIMER NOSOURCE NOSYNTAXCHECK;ODS HTML CLOSE;&GRAPHTERM; ;* 1 Uribe $867,159 612 C 2 Knecht $983,152 861 B title1 &reptitle; Company's biggest sellers; sales above 700000 1 Uribe $867,159 612 C 2 Knecht $983,152 861 B %let reptitle = Company''s biggest sellers; sales above &minsales; /* ERROR in LOG after first semicolon */ %let reptitle = "Company''s biggest sellers; sales above &minsales"; title1 &reptitle; Company''s biggest sellers; sales above 700000 1 Uribe $867,159 612 C 2 Knecht $983,152 861 B 3
/* %STR needs % before tokens that typically appear in pairs */ %let reptitle = %str(company%'s biggest sellers; sales above &minsales); title1 "&reptitle"; Company's biggest sellers; sales above 700000 1 Uribe $867,159 612 C 2 Knecht $983,152 861 B title1 &reptitle; /* ERROR in LOG after first semicolon (from TITLE1 statement) */ /* Character macro functions (pp. 322-330); similar to functions seen previously in Base Ch. 13, but more flexible */ /* %UPCASE */ %let checkval = c; where division="&checkval"; NOTE: There were 0 observations read from the data set CERT.SALES11. WHERE division='c'; where division="%upcase(&checkval)"; 3 Uribe $867,159 612 C 9 Larson $381,592 331 C /* Note what happens here without quotes: */ where division=%upcase(&checkval); ERROR: Variable C is not on file CERT.SALES11. /* %QUPCASE: allows special characters */ %let checkval = %str(d%'angelou); where upcase(lastname) = "%qupcase(&checkval)"; 8 D'Angelou $332,512 772 A /* Other character manipulation macros: %SUBSTR & %QSUBSTR, %INDEX, %SCAN & %QSCAN */ 4
/* pp. 330-332: %SYSFUNC < %QSYSFUNC to call other SAS functions where they normally wouldn't work (like in title) */; where division="c"; title1 "This report created today()."; proc print data=cert.sales&honum; where division="c"; title1 "This report created %SYSFUNC(today())"; where division="c"; title1 "This report created %SYSFUNC(today(),worddate.)"; This report created today(). 3 Uribe $867,159 612 C 9 Larson $381,592 331 C This report created 20135 3 Uribe $867,159 612 C 9 Larson $381,592 331 C This report created February 16, 2015 3 Uribe $867,159 612 C 9 Larson $381,592 331 C /* pp. 332-336: Can reference macro variable (&abc) right after anything, but may need period (&abc.) if want to reference right before something */ %let year=2; %let std=1; data temp; set cert.sales&honum; minys11 = 500000; minys21 = 700000; minys12 = 100000; minys22 = 400000; if sales > minys&year&std; var lastname sales; Obs LastName Sales 1 Uribe $867,159 2 Knecht $983,152 %let mink = 5; var lastname sales; where sales > &mink.00000; Obs LastName Sales 1 Smith $553,312 2 Nelson $555,827 3 Uribe $867,159 4 Larson $521,592 7 Knecht $983,152 5
/************ Ch. 10 ********************/ /* pp. 345-349: All macro language is executed before DATA step language, so to correctly define macro variable inside DATA step, need to use CALL SYMPUT('varname',value) [pp. 349-356] -or- CALL SYMPUTX [pp. 358-360] to remove leading & trailing blanks -and maybe- PUT [pp.356-358] to control conversion of num to char */ %let msg = "sample size less than 5"; %let happened = 0; %let charhap = never; data temp; set cert.sales&honum; if _n_ > 5 then do; CALL SYMPUT('msg',"sample size greater than 5"); call symput('happened',%sysfunc(today())); call symput('charhap',put(%sysfunc(today()),worddate.)); end; var lastname sales; title1 "MSG: &msg"; title2 "HAPPENED: &happened ; CHARHAP: &CharHap"; MSG: sample size greater than 5 HAPPENED: 20135 ; CHARHAP: February 16, 2015 Obs LastName Sales 1 Smith $553,312 2 Nelson $555,827 3 Uribe $867,159 4 Larson $521,592 5 Larsen $123,513 6 Rasmussen $125,321 7 Knecht $983,152 8 D'Angelou $332,512 9 Larson $381,592 6
/* pp. 360-363: CALL SYMPUT(varname1, varname2) [no quotes!] in DATA _NULL_ to simultaneously define many macro variables (names in varname1, with values in varname2) */ data temp; input v1 $ v2 $; cards; A 53 B Utah A: 53, B: Utah, C: Cal C Cal ; Obs v1 v2 data _null_; set temp; 1 A 53 call symput(v1, v2); 2 B Utah title1 "A: &A, B: &B, C: &C"; 3 C Cal /* pp. 363-369: reference macro variable indirectly using && (usually for nested macro references; when code has && in it, it is re-scanned after processing all macro variables, with && re-scanned as &) */ %let minyear1=500000; %let minyear2=700000; %let year=1; data temp; set cert.sales&honum; if sales > &&minyear&year; /* First scan: &year processed as 1 Second scan: &&minyear1 processed as &minyear1 Third scan: &minyear1 processed as 500000 */ /* pp. 369-371: SYMGET('varname') will return value of macro variable within DATA step or SQL call (p. 378); this is useful when the value to be used depends on the value of another variable (depending on row) */ %let Astate = Utah; %let Bstate = Colorado; %let Cstate = California; data temp; set cert.sales&honum; format state $10.; state = symget(left(right(division) 'state')); /* NOTE: need left() and right() to handle trailing blanks */ var lastname division state; title1; NOTE: There were 9 observations read from the data set CERT.SALES11. NOTE: The data set WORK.TEMP has 5 observations and 4 variables. Obs LastName Division state 1 Smith A Utah 2 Nelson A Utah 3 Uribe C California 4 Larson B Colorado 5 Larsen B Colorado 6 Rasmussen A Utah 7 Knecht B Colorado 8 D'Angelou A Utah 9 Larson C California 7
/* pp. 371-376: SELECT... INTO : [need colon!] to create macro variables in PROC SQL. p. 375: &SQLOBS automatic macro variable (#rows in resulting table) */ select division, avg(sales) format dollar12.2 label='division Average', count(sales) label="division Size" into :div1-:div3, :divavg1-:divavg3, :divn1-:divn3 from cert.sales&honum group by division; %let numdiv = &SQLOBS; Division Division Average Division Size A $391,743.00 4 B $542,752.33 3 C $624,375.50 2 select name, value from dictionary.macros where name contains 'DIV'; /* NOTE: SAS treats all macro names as all-uppercase */ Macro Variable Name Macro Variable Value DIV1 A DIV2 B DIV3 C DIVAVG1 $391,743.00 DIVAVG2 $542,752.33 DIVAVG3 $624,375.50 DIVN1 4 DIVN2 3 DIVN3 2 NUMDIV 3 /* p. 377: SEPARATED BY in SQL will concatenate all values in column to a single macro variable */ select distinct division into :alldivs separated by '~' from cert.sales&honum; select name, value from dictionary.macros where name="alldivs"; Macro Variable Name Macro Variable Value ALLDIVS A~B~C 8
/* p. 379: use INPUT to convert character value of macro to numeric (for checking equality, for example) */ %let check="315"; where emplid=✓ ERROR: WHERE clause operator requires compatible variables. where emplid=input(&check,3.); 1 Smith $553,312 315 A /* pp. 379-381: SCL (not used elsewhere in Certification training) */ From SAS documentation: "SAS Component Language (SCL) is a programming language designed to facilitate the development of interactive applications using the SAS System. For example, you can use SCL with other SAS software to create data entry applications, to display tables and menus, and to generate and submit SAS source code." SYMPUT & SYMGET can be used to define and retrieve macro variable values (as characters) as in non-scl code. SCL code also allows SYMPUTN & SYMGETN to define and retrieve numeric macro variable values. 9