/* Examples for graduate Base SAS course: Macro language. * Dongfeng Li, Autumn 2010. */ ************************************************; /* Purpose of SAS Macros */ ************************************************; /* Automatic sas variables. */ proc print data=samp.class noobs label; title 'Listing of the CLASS dataset'; footnote1 "Created &systime. &sysday, &sysdate9."; footnote2 "on the &sysscp. System using Release &sysver."; run; /* User variables. Using the same value repeatedly. */ %let dname=samp.class; proc print data=&dname; run; proc means data=&dname; run; /* Macro definition */ %MACRO pr; proc print; run; %MEND pr; data new; x=1; y=2; run; %pr /* Macro with named parameters */ %MACRO prd(dname); proc print data=&dname; run; %MEND prd; %prd(dname=samp.class) /* Conditionally Generating SAS Code */ %macro whatstep(info= , mydata= ); %if &info=print %then %do; proc print data=&mydata; run; %end; %else %if &info=report %then %do; options nodate nonumber ps=18 ls=70; proc report data=&mydata nowd; column flight date boarded; title 'Flights Summary'; run; %end; %mend whatstep; %whatstep(info=report, mydata=samp.march) /* Generating a series of dataset names */ %MACRO dnames(name= , number= ); %DO i=1 %TO &number; &name&i %END; %MEND dnames; data new; set %dnames(name=d, number=3); run; %MACRO dnamesx(name= , number= ); %DO i=1 %TO &number; &name.x&i %END; %MEND dnames; data new; set %dnamesx(name=da, number=10); run; /* Macro functions. */ %MACRO sw(sel= ); %IF %UPCASE(&sel)=default %THEN %DO; proc print; run; %END; %ELSE %DO; proc print noobs label; run; %END; %MEND; data aa; label x="Height of the mountain" y="Air Pressure"; x = 6700; y = 0.52; run; %sw(sel=Default) ************************************************; /* SAS Macros Variables */ ************************************************; footnote "Report for &sysday, &sysdate9"; proc print data=samp.class; run; /* Display all automatic macro variables. */ %PUT _AUTOMATIC_; /* Creating macro variables. Constant string. Leading and following blanks removed */ %LET dname=class; %LET lib=samp; /* Numbers macro */ %LET num=123; %LET totstr=100+200; /* Note: not added */ %PUT &num &totstr; /* Arithmetic expression in macro variable definition */ %LET tot=%EVAL(&totstr); %PUT &tot; %LET res=%sysevalf(1.2+3.5); %PUT &res; /* Null value */ %LET emp=; %PUT ***&emp***; /* Using macro reference in definition */ %LET d=samp.&dname; /* Use %str, %nrstr ... to protect special characters */ /* Dot and double dots. */ %LET d1=samp.&dname; %LET newd=&lib..gpa; /* Using macro invocation in definition */ %MACRO dnames(name= , number= ); %DO i=1 %TO &number; &name&i %END; %MEND dnames; %LET dlist=%dnames(name=d, number=5); /* Blanks and special characters. Use %STR, %NRSTR to preserve blanks, to treat special characters as ordinary ones. */ %let state=%STR( North Carolina); %let town=%STR(Taylor%'s Pond); /*'*/ %let store=%NRSTR(Smith&Jones); %let plotit=%STR( proc plot; plot income*age; run;); /* Use datastep variable to define macro variables */ /* Put number of students over 65 inches into macro variable number */ data _null_; set samp.class end=eof; if height>65 then n+1; if eof then call symput('number', trim(left(n))); run; footnote "&number observations have height>65."; proc print data=samp.class; run; /**** Using Macro Variables ****/ %LET dname=samp.class; title "List Data from &dname"; footnote; %prd(&dname); /* Using single quotation prevents macro substitution */ title 'List Data from &dname'; %prd(&dname); /* Substitution inside text */ %LET dname=class; data new&dname; set samp.&dname; run; data &dname.1; set samp.&dname; if sex='F'; run; %LET lib=samp; proc print data=&lib..class; run; /* Options symbolgen: display macro variable substituion */ options symbolgen; %LET dname=class; proc print data=samp.&dname; run; /* Indirect refence: double ampersands. */ data aa; input i k; cards; 1 1 2 4 3 9 ; run; data _null_; set aa; call symput('var' || put(i, Z1.), k); run; %PUT &var1 &var2 &var3; %MACRO test; %DO i=1 %TO 3; %PUT Variable NO. &i: name=var&i, value=&&var&i; %END; %MEND test; %test /* Three ampersands */ %LET city6=Hangzhou; %LET var=city; %LET n=6; %PUT &&&var&n; /* Resolves from left to right, first two ampersands changed to one ampersand, then resolve &var&n as city6, left &city6 to be resolved as Hangzhou */ /* Macro functions to manipulate strings */ %LET address=123 Maple Avenue; %LET firstword=%SCAN(&address, 1); %PUT &firstword; ************************************************; /* Macros Processing */ ************************************************; /* Macros are used: - to generate text. - control struction like %IF, %DO. - can be defined with parameters. generic macros. */ %macro app(goal); %if &sysday=Friday %then %do; data thisweek; set lastweek; if totsales > &goal then bonus = .03; else bonus = 0; run; %end; %mend app; %app(10000) proc print; run; ************************************************; /* Scope of Macro Variables */ ************************************************; /* Two types of scope: global and local. Scopes can be nested. If macro A has local variable LOC1, macro B has local variable LOC2, A calls B, then in side B, both LOC1 and LOC2 can be accessed, but LOC2 cannot be accessed in A. %SYMEXIST(sym): check if an macro variable ``sym'' exists. */ /* Example of global macro variables. */ %LET county=Clark; %macro concat; data _null_; length longname $20; longname="&county"||" County"; put longname; run; %mend concat; %concat /* county is an global macro variable */ /* Global variables include: - All automatic macro variables(except SYSPBUFF). - Macro variables created outside of any macro. - Macro variables created in the %GLOBAL statements. - Most macro variables created by the CALL SYMPUT data step routine. Conflicts: - When both a global and a local exist with the same name, the local variable is used. */ /* Example of local macro variables. */ /* Local macro variables are defined inside an macro. They only exist when the macro executes. Macro parameters are always local. Use %SYMLOCAL(sym) to test if an symbol is a local variable. Macros can be invoked inside macros, so creating nested local scope. */ %MACRO holinfo(day, date); %LET holiday=Christmas; %PUT *** Inside macro: ***; %PUT *** &holiday occurs on &day, &date, 2010. ***; %MEND holinfo; %holinfo(Saturday, 12/25) %PUT *** Outside macro: ***; %PUT ***&holiday occurs on &day, &date, 2010. ***; /* Special %PUT list _ALL_ _AUTOMATIC_ _GLOBAL_ _LOCAL_ _USER_ */ %PUT _USER_; %LET sels=F; %MACRO cla(selage=); data _null_; set samp.class; where sex="&sels" and age=&selage; put name " is &selage years old."; run; %PUT _user_; %MEND cla; %cla(selage=15) /* Output list enclosing macro, variable name, variable value. */ /* Writing to outside defined macro variables. */ %LET vara=Beijing; %MACRO test; %PUT First vara is &vara; %LET vara=Shanghai; %PUT Second vara is &vara; %MEND test; %test %PUT Last vara is &vara; /* You can modify a macro variable defined outside a macro and keep the value after you exit from the macro. This is not good programming. To be safe, use %LOCAL statement to declare truly local variables. */ %LET varb=Tianjin; %MACRO test2; %PUT First varb is &varb; %LOCAL varb; %LET varb=Chongqing; %PUT Second varb is &varb; %MEND test2; %test2 %PUT Last varb is &varb; /* %GLOBAL statement. */ %MACRO test3; %GLOBAL varc; %LET varc=Kun Ming; %MEND test3; %test3 %PUT test3 variable: &varc; /* Caution for CALL SYMPUT(): If the data step is not in any macro, OK. If it is in a macro, best declare the macro variables to be created by CALL SYMPUT as local, and use them locally. If you want the created variable global, declare it to be global before the CALL SYMPUT. */ ************************************************; /* Macro Expressions */ ************************************************; /* Macro expressions: - Text expressions, like &begin, %GETLINE, &PREFIX.PART&SUFIX, %UPCASE(&answer) - Arithmetic expression, like %EVAL(3+5), %SYSEVALF(1.2+3.8) Macro arithmetic use INTEGER by default. - Logical expression, like 3 < 5, &day=Friday Text expression is resolved before arithmetic and logical. */ %LET a=2; %LET b=5; %LET operator=+; %PUT The result of &a &operator &b is %EVAL(&a &operator &b).; /* Operators: a subset of those used in data step. */ /* Arithmetic expression evaluation: Temporarily convert text to number, after calculation, revert the result back to text. */ %LET a=%EVAL(1+2); %LET b=%EVAL(10*3); %LET c=%EVAL(4/2); %LET i=%EVAL(5/3); %PUT The value of a is &a; %PUT The value of b is &b; %PUT The value of c is &c; %PUT The value of i is &i; /* Using the %SYSEVALF function to evaluate floating point expressions. */ %LET a=10.0*3.0; %LET b=10.5+20.8; %LET c=5/3; %PUT &a = %SYSEVALF(&a); %PUT &b = %SYSEVALF(&b); %PUT &c = %SYSEVALF(&c); /* Type conversion specification of %SYSEVALF. Convert float result to boolean or integer. */ %LET a=2.5; %PUT %SYSEVALF(&a, boolean); %PUT %SYSEVALF(&a, integer); %PUT %SYSEVALF(&a, ceil); %PUT %SYSEVALF(&a, floor); /* Logical expression evaluation */ /* Comparing numeric operands: integers */ %MACRO compnum(first, second); %IF &first>&second %THEN %PUT &first is greater than &second; %ELSE %IF &first=&second %THEN %PUT &first equals &second; %ELSE %PUT &first is less than &second; %MEND compnum; %compnum(1,2) %compnum(2,2) %compnum(0,-1) /* The above are compared as integer. So unexpected result: */ %compnum(10, 2.0) /* shows: 10 is less than 2.0!! Should use %SYSEVALF(&first>&second) as the %IF condition. */ /* Comparing floating point or missing numbers */ %MACRO compf(first, second); %IF %SYSEVALF(&first>&second) %THEN %PUT &first is greater than &second; %ELSE %IF %SYSEVALF(&first=&second) %THEN %PUT &first equals &second; %ELSE %PUT &first is less than &second; %MEND compf; %compf(0.1, 2) %compf(2,1.5) %compf(1.4,1.4) %compf(0, .) /* Comparing character operands */ %MACRO compchar(first, second); %IF &first>&second %THEN %PUT &first comes after &second; %ELSE %PUT &first comes before &second; %MEND compchar; %compchar(a,b) %compchar(., 1) %compchar(Z, E) ************************************************; /* Macro quoting */ ************************************************; /* In macro variable definition, include a semicolon, an ampersand, an percent sign? Use macro quoting functions %STR, %NRSTR etc. Macro quoting functions tell the macro processor to interpret special characters and mnemonics as text rather than as part of the macro language. Special symbols which need to be masked: blank ; % & ' " ( ) , ^ ~ + - * / < > = | # AND OR NOT EQ NE LE LT GE GT IN */ /* Understanding Why Macro Quoting is Necessary Problems: - Is %sign a call to the macro SIGN or a phrase "percent sign"? - Is OR the mnemonic Boolean operator or the abbreviation for Oregon? - Is the quote in O'Malley an unbalanced single quotation mark or just part of the name? - Is Boys&Girls a reference to the macro variable &GIRLS or a group of children? - Is GE the mnemonic for "greater than or equal to" or is it short for General Electric? - Which statement does a semicolon end, if we want to include a semicolon in a macro variable definition? - Does a comma separate parameters, or is it part of the value of one of the parameters? Macro quoting functions enable you to clearly indicate to the macro processor how it is to interpret special characters and mnemonics. */ /* Example of error using semicolon: */ %LET print=proc print;run;; /* ERROR: where do we end the %LET statement? Instead, use: */ %LET print=%STR(proc print; run;); /* Overview of Macro Quoting Functions Most commonly used: - %STR, %NRSTR - %BQUOTE, %NRBQUOTE - %SUPERQ The NR versions add protection against ampersands and percent signs. So macro variable and macro references are ``Not Resolved'' inside an NR version quoting function. Two phases: - Compilation phase: cause text to be protected. %STR, %NRSTR. - Execution phase: cause execution result of text expression to be protected. %BQUOTE, %NRBQUOTE. %SUPERQ take an macro variable name(without ampersand) as its argument, quote its value(treat its value as ordinary text, without resolution). */ /* Passing Parameters That Contain Special Characters and Mnemonics Macro arguments should best be protected by execution macro quoting functions during macro definition. If not, use %STR to protect special symbols in your argument during invocation of macro, like %orderX(%str(OR)). */ /* Macro quoting functions: WHEN and WHICH When: when you want to use special symbols as ordinary text. Special symbols: - + - * / < > = ^ ~ | # LE LT EQ NE GE GT AND OR NOT IN Masked by all macro quoting functions. To prevent explicit or implicit evaluation. - blank Masked by all macro quoting functions. To keep leading, trailing, isolated blanks. - ; Masked by all macro quoting functions. To prevent macro program statement from ending prematurely. Not needed in macro definition. - ,(comma) Masked by all macro quoting functions. To prevent it from indicating a new function argument. - Unmatched single quotation ', double quotation ", parenthesis (, ) Not all macro quoting functions can mask them. Precede them with a % sign in functions %STR, %NRSTR, %QUOTE, %NRQUOTE. No leading % sign is needed when using %BQUOTE, %NRBQUOTE, %SUPERQ. - %name &name Not all macro quoting functions can mask them. Use %NRSTR, %NRBQUOTE, %NRQUOTE. */ /* Using %STR, %NRSTR. Useful for masking static text, not useful if resolved text contain special symbols. */ %LET myvar=%STR(a%'); %PUT &myvar; %LET myvar=%STR(b%"); %PUT &myvar; %LET myvar=%STR(log%(12); %PUT &myvar; %LET myvar=%STR(345%)); %PUT &myvar; %LET myvar=%STR(90%%); %PUT &myvar; %LET myvar=%STR(90%%%'); %PUT &myvar; %LET printit=%STR(proc print; run;); %MACRO keepit1(size); %IF &size=big %THEN %STR(keep city _numeric_;); %ELSE %STR(keep city;); %MEND keepit1; /* Masked semicolons. */ %keepit(big) %LET innocent=%STR(I didn%'t do it!); /* Masked unmatched single quotation mark. */ %PUT &innocent; /*'*/ /* Example of masking & sign */ %MACRO example; %LOCAL myvar; %LET myvar=abc; %PUT %nrstr(The string &myvar appear in log output,); %PUT instead of the variable value, abc.; %MEND example; %example /* Example of masking & sign */ %MACRO credits(d=%NRSTR(Mary&Stacy&Joan Ltd.)); footnote "Designed by &d"; %MEND credits; %credits() /* Example of masking % sign */ %PUT This is the result of %NRSTR(%NRSTR); %PUT This is the result of %STR(%%NRSTR); /* Error! */ /* Using %BQUOTE, %NRBQUOTE Used to mask resolved result. No leading percent sign is needed. %NRBQUOTE stops the resolved text to be further resolved. Special symbols are NOT masked in the argument itself. */ /* Example: protect resolved OR */ %MACRO test; %LOCAL state; data _null_; ss='OR'; CALL SYMPUT('state', ss); run; %IF %NRQUOTE(&state)=%STR(OR) %THEN %PUT Oregon Dept. of Revenue; %MEND test; %test /* Example: protect resolved single quotation mark */ data test; store="Susan's Office Supplies"; call symput('s',store); run; %MACRO readit; %IF %BQUOTE(&s) NE %THEN %PUT *** valid ***; %ELSE %PUT *** null value ***; %MEND readit; %readit /* Refering to already quoted variable: no %QUOTE needed. */ %LET whose=%STR(John%'s); %PUT *** This coat is &whose ***; /* No quoting is needed here */ /*'*/ /* Minimalist quoting not needed: */ %LET p=%STR(proc print; run;); %LET p=proc %STR(print;) %STR(run;); %LET p=proc print%STR(;) run%STR(;); /* Using %SUPERQ. Execution time. Take an macro variable name(without &), resolve it, but prevents further resolution. Provides better protecton than %NRBQOTE. */ %MACRO a; %PUT *** This is a. ***; %MEND a; %MACRO test; %PUT *** Enter two values: ***; %INPUT; %PUT *** %SUPERQ(sysbuffr) ***; /* Note &sysbuffr is not needed */ %MEND test; %test /* If you input line: %a %x then %a is not resolved, %x is not complained in the log. Output is *** %a %x *** */ /* Example of %SUPERQ better than %NRBQUOTE */ %MACRO bad; %GLOBAL code; %LOCAL name; data _null_; CALL SYMPUT('name', 'A&A Autos'); run; %IF %NRBQUOTE(&name) NE %THEN %LET code=valid; %ELSE %LET code=invalid; %PUT *** &name is &code ***; %MEND bad; %bad /* Which gives out warnings. */ %MACRO good; %GLOBAL code; %LOCAL name; data _null_; CALL SYMPUT('name', 'A&A Autos'); run; %IF %SUPERQ(name) NE %THEN %LET code=valid; %ELSE %LET code=invalid; %PUT *** %SUPERQ(name) is &code ***; %MEND good; %good /* Which gives no warning. */ /* Unquoting Text */ /* Unquoting happens when you explicitly use %UNQUOTE, when the item is passed on to the ordinary SAS, when the item is returned from %SCAN, %SUBSTR, %UPCASE. Use %QSCAN, %QSUBSTR, %QUPCASE if you want the result keep quoted. */ ************************************************; /* Interfaces with the Macro Facility */ ************************************************; /* Macro resolves before data step execution. Use interfaces to interact with the data step. */ /* Data Step Interfaces Example usage: - Pass information from a DATA step to a subsequent step. - Invoke a macro based on information available only when the DATA step executes. - Delete a macro variable. - Pass information about a macro variable from the macro facility to the DATA step. */ /* CALL SYMPUT: Assign a value produced by a data step to a macro variable. Can transfer data from dataset to macro variables. Syntax: CALL SYMPUT(macro-variable-name-as-string, value) */ data _null_; v = 'testing'; CALL SYMPUT('new', v); run; %PUT &new; data team1; input position : $8. player : $12.; CALL SYMPUT(position,player); datalines; shortstp Ann pitcher Tom frstbase Bill ; run; %PUT &shortstp &pitcher &frstbase; data c; input holiday mmddyy.; CALL SYMPUT('holdate',trim(left(put( holiday,worddate.)))); datalines; 070497 ; run; %PUT &holdate; /* SYMGET function: Use a data step character expression as its argument, treat the value of its argument as a macro variable name and return the resolved value. Note: references in the resolved value are not further resolved. */ /* SYMGET example: using macro variable values previous assigned */ data dusty; input dept $ name $ salary @@; datalines; bedding Watlee 18000 bedding Ives 16000 bedding Parker 9000 bedding George 8000 bedding Joiner 8000 carpet Keller 20000 carpet Ray 12000 carpet Jones 9000 gifts Johnston 8000 gifts Matthew 19000 kitchen White 8000 kitchen Banks 14000 kitchen Marks 9000 kitchen Cannon 15000 tv Jones 9000 tv Smith 8000 tv Rogers 15000 tv Morse 16000 ; proc means noprint; class dept; var salary; output out=stats sum=s_sal; run; proc print data=stats; var dept s_sal; title "Summary of Salary Information"; title2 "For Dusty Department Store"; run; data _null_; set stats; if _n_=1 then CALL SYMPUT('s_tot',s_sal); else CALL SYMPUT('s'||dept,s_sal); run; %PUT _USER_; data new; set dusty; pctdept=(salary/symget('s'||dept))*100; pcttot=(salary/&s_tot)*100; run; proc print data=new split="*"; label dept ="Department" name ="Employee" pctdept="Percent of *Department* Salary" pcttot ="Percent of * Store * Salary"; format pctdept pcttot 4.1; title "Salary Profiles for Employees"; title2 "of Dusty Department Store"; run; title; /* CALL RESOLVE routine: Resolves its argument(an character expression) durint data step execution. This is different from using a macro variable reference, which resolves at data step CONSTRUCTION phase. Resolves more than CALL SYMGET routine. */ /* CALL RESOLVE example: resolving sample references */ %LET event=Holiday; %MACRO date; New Year %MEND date; data test; length var1-var3 $ 15; when='%date'; var1=resolve('&event'); /* macro variable reference */ var2=resolve('%date'); /* macro invocation */ var3=resolve(when); /* DATA step variable with macro invocation */ put var1= var2= var3=; run; /* CALL EXECUTE: Use an character expression as its argument. During data step execution, if the argument is a macro invocation, invoke immediately. If the argument is a data step statement, the statement is run after data step boundary is met. */ /* CALL EXECUTE example: executing a macro conditionally */ %MACRO overdue; proc print data=late; title "Overdue Accounts As of &sysdate"; run; %MEND overdue; data late; set sasuser.billed end=final; if datedue<=today()-30 then do; n+1; output; end; if final and n then CALL EXECUTE(¡¯%overdue¡¯); run; /* If the data step have n>=1 at last observation, then the OVERDUE macro is executed after the data step. */ /* CALL EXECUTE example: Passing data step values into a parameter list */ data dates; input date $; datalines; 10nov97 11nov97 12nov97 ; data reptdata; input date $ var1 var2; datalines; 10nov97 25 10 10nov97 50 11 11nov97 23 10 11nov97 30 29 12nov97 33 44 12nov97 75 86 ; %MACRO rept(dat,a,dsn); proc gchart data=&dsn; title "Chart for &dat"; where(date="&dat"); vbar &a; run; %MEND rept; data _null_; set dates; CALL EXECUTE('%rept(' || date || ',' || 'var1,reptdata)'); run; /* After the last data step exits, three gchart procedure run. */ /* PROC SQL Macro Interface: INTO Clause. Put the select result into macro variables. */ /* PROC SQL INTO clause example: Storing Column Values in Explicitly-Declared Macro Variables. Only the first row is stored. */ proc sql noprint; select style, sqfeet INTO :type, :size from sasuser.houses; quit; %let type=&type; %let size=&size; %put The first row contains a &type with &size square feet.; /* PROC SQL INTO clause example: Storing Row Values in a List of Macro Variables. The first 4 rows are stored. */ proc sql noprint; select style, sqfeet INTO :type1 - :type4 notrim, :size1 - :size4 from sasuser.houses; quit; %PUT _USER_; %MACRO putit; %DO i=1 %TO 4; %PUT Row&i: Type=**&&type&i** Size=**&&size&i**; %END; %MEND putit; %putit /* PROC SQL INTO clause example: Storing Values of All Rows in one Macro Variable, seperated by coma. */ proc sql; select distinct quote(style) INTO :types SEPARATED BY ', ' from sasuser.houses; quit; %put Types of houses=&types.;