{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# SAS MACRO Programming\n", "\n", "This lesson introduces the most commonly used features of the SAS macro language. When you write a program that will be run over and over again, you might want seriously to consider using \"**macros**\" in your code, because:\n", "\n", "* macros allow you to make a change in one location of your program, so that SAS can cascade the change throughout your program\n", "* macros allow you to write a section of code once and use it over and over again, in the same program or even different programs\n", "* macros allow you to make programs data driven, letting SAS decide what to do based on actual data values.\n", "\n", "For a good introduction with some more examples, see \n", "\n", "* SAS Macro Programming for Beginners\n", "* Chapter 7: Writing Flexible Code with the SAS Macro Facility in the Little SAS Book: A Primer, 5th ed.\n", "* Chapter 25: Introducing the SAS Macro Language from Learning SAS by Example: A Programmer's Guide, 2nd ed\n", "\n", "For our examples in this chapter, we will use the Framingham dataset which was a large longitudinal, epidemiologic study of the risks of heart disease. Be sure to download and the SAS dataset file fghm113.sas7bdat and place it in a convenient folder. Then change the LIBNAME path to point to this folder in the following code." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "SAS Connection established. Subprocess id is 2406\n", "\n" ] }, { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "SAS Output\n", "\n", "\n", "\n", "
\n", "
\n", "

The SAS System

\n", "
\n", "
\n", "

The CONTENTS Procedure

\n", "
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
Data Set NameWORK.FGHMTEMPObservations500
Member TypeDATAVariables21
EngineV9Indexes0
Created10/10/2020 19:58:41Observation Length168
Last Modified10/10/2020 19:58:41Deleted Observations0
Protection CompressedNO
Data Set Type SortedNO
Label   
Data RepresentationSOLARIS_X86_64, LINUX_X86_64, ALPHA_TRU64, LINUX_IA64  
Encodingutf-8 Unicode (UTF-8)  
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
Engine/Host Dependent Information
Data Set Page Size65536
Number of Data Set Pages2
First Data Page1
Max Obs per Page389
Obs in First Data Page359
Number of Data Set Repairs0
Filename/tmp/SAS_workA8FB00000966_localhost.localdomain/fghmtemp.sas7bdat
Release Created9.0401M6
Host CreatedLinux
Inode Number671645
Access Permissionrw-r--r--
Owner Namesasdemo
File Size192KB
File Size (bytes)196608
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
Alphabetic List of Variables and Attributes
#VariableTypeLenFormatInformatLabel
4AGENum8BEST12.F12.Age (years) at examination
9BMINum8BEST12.F12.Body Mass Index (kr/(M*M)
11BPMEDSNum8YNFMT.F12.Anti-hypertensive meds Y/N
8CIGPDAYNum8BEST12.F12.Cigarettes per day
7CURSMOKENum8YNFMT.F12.Current Cig Smoker Y/N
10DIABETESNum8YNFMT.F12.Diabetic Y/N
6DIABPNum8BEST12.F12.Diastolic BP mmHg
13GLUCOSENum8BEST12.F12.Casual Glucose mg/dL
20HDLCNum8BEST12.F12.HDL Cholesterol mg/dL
12HEARTRTENum8BEST12.F12.Ventricular Rate (beats/min)
21LDLCNum8BEST12.F12.LDL Cholesterol mg/dL
19PERIODNum8BEST12.F12.Examination cycle
15PREVAPNum8YNFMT.F12.Prevalent Angina
14PREVCHDNum8YNFMT.F12.Prevalent CHD (MI,AP,CI)
18PREVHYPNum8YNFMT.F12.Prevalent Hypertension
16PREVMINum8YNFMT.F12.Prevalent MI (Hosp,Silent)
17PREVSTRKNum8YNFMT.F12.Prevalent Stroke (Infarct,Hem)
2RANDIDNum8BEST12.F12.Random ID
1SEXNum5GNDRFMT.F12.SEX
5SYSBPNum8BEST12.F12.Systolic BP mmHg
3TOTCHOLNum8BEST12.F12.Serum Cholesterol mg/dL
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "

The SAS System

\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
ObsSEXRANDIDTOTCHOLAGESYSBPDIABPCURSMOKECIGPDAYBMIDIABETESBPMEDSHEARTRTEGLUCOSEPREVCHDPREVAPPREVMIPREVSTRKPREVHYPPERIODHDLCLDLC
1Women1126322055180106No031.17YesYes8681NoNoNoNoYes346135
2Men1636521155173123No029.11NoYes7585NoNoNoNoYes348163
3Women437702836415969No032.93YesNo70230NoNoNoNoYes345238
4Men475612905613270Yes4022.73NoNo10090NoNoNoNoYes354236
5Women559652987210960No026.2NoNo80100NoNoNoNoNo338260
\n", "
\n", "
\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "LIBNAME myData \"/folders/myfolders/SAS_Notes/data\";\n", "\n", "*Create a temporary data set, so that we don't save changes to the original data set.;\n", "DATA fghmTemp;\n", "SET myData.fghm113;\n", "RUN;\n", "\n", "/*Now let's code some variables with some more descriptive values.\n", " SEX (Gender): 1=Men \n", "\t\t\t\t2=Women\n", " Period (Examination cycle): 1=Period1 \n", "\t\t\t\t\t\t\t 2=Period2\n", "\t\t\t\t\t\t\t 3=Period3\n", " BPMEDS (Use of anti-hypertensive meds): 0=Not currently\n", "\t\t\t\t\t\t\t\t\t\t 1=Currently use\n", " CURSMOKE (Currently smoke?): 0=No\n", "\t\t\t\t\t\t\t 1=Yes\n", " DIABETES: 0=Not diabetic\n", "\t\t\t1=Diabetic\n", " PREVAP (Have angina pectoric?): 0=No\n", "\t\t\t\t\t\t\t\t 1=Yes\n", " PREVCHD (Coronary heart disease?): 0=No\n", "\t\t\t\t\t\t\t\t\t 1=Yes\n", " PREVMI (Myocardial infarction?): 0=No\n", "\t\t\t\t\t\t\t\t 1=Yes\n", " PREVSTRK (Had a stroke?): 0=No\n", "\t\t\t\t \t\t\t1=Yes\n", " PREVHYP (Hypertensive? sys bp >=140 or dyas bp >= 90): 0=no\n", "\t\t\t\t\t\t\t\t\t\t\t\t\t\t 1=yes\n", "*/\n", "\n", "PROC FORMAT;\n", "VALUE YNfmt 0=\"No\"\n", "\t\t\t1=\"Yes\";\n", "VALUE perfmt 1=\"Period 1\"\n", "\t\t\t 2=\"Period 2\"\n", "\t\t\t 3=\"Period 3\";\n", "VALUE gndrfmt 1=\"Men\"\n", "\t\t\t 2=\"Women\";\n", "RUN;\n", "\n", "DATA fghmtemp;\n", "SET fghmtemp;\n", "FORMAT prevap ynfmt.\n", "\t diabetes ynfmt.\n", "\t cursmoke ynfmt.\n", "\t bpmeds ynfmt.\n", "\t prevchd ynfmt.\n", "\t prevmi ynfmt.\n", "\t prevstrk ynfmt.\n", "\t prevhyp ynfmt.\n", "\t sex gndrfmt.;\n", "RUN;\n", "\n", "*Check to see if the formatting was done correctly;\n", "PROC CONTENTS DATA = fghmtemp;\n", "RUN;\n", "\n", "PROC PRINT DATA = fghmtemp (obs=5);\n", "RUN;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## MACRO Variables and %LET\n", "\n", "%LET is a straightforward MACRO statement that simply assignes a (text) value to a MACRO variable. Suppose that you have a program that you run once a month. Every time you have to edit this program so it will select data for the correct month and print the correct title. This is time-consuming and prone to errors. You can use %LET to create a MACRO variable. Then you can change the value of the MACRO variable in the %LET statment, and SAS will repeat the new value throughout your progmra.\n", "\n", "The general form of a %LET statement is:\n", "\n", "`%LET macro-variable-name = value;`\n", "\n", "where macro-variable-name is a name you make up following the standard rules for SAS names (32 characters or fewer in length, starting with a letter or underscore, and containing only letters, numerals or underscores). Value is the text to be substituted for the macro variable name, and can be up to 64,000 characters long. The following statements each create a macro variable.\n", "\n", "`%LET iterations = 5;`\n", "\n", "`%LET winner = Lance Armstrong;`\n", "\n", "Notice that there are no quotation marks around value even when it contains characters. Blanks at the beginning and end will be trimmed, and everything else between the equal sign and semicolon will become part of the value for the MACRO variable.\n", "\n", "To use the MACRO variable, you simply add the ampersand (&) prefix and stick the MACRO variable name where you want its value to be substituted. For example,\n", "\n", "`DO i = 1 TO &iterations;`\n", "\n", "`TITLE \"First: &winner\";`\n", "\n", "When we run a program with the previous lines of code, it will first go through the MACRO processor to substitute the values of the MACRO variable to generate the SAS code:\n", "\n", "`DO i = 1 TO 5;`\n", "\n", "`TITLE \"First: Lance Armstrong\";`\n", "\n", "and then it runs the resulting code as normal.\n", "\n", "
\n", "

Example

\n", "

The following SAS program uses three MACRO variables to specify the dataset name and the two variable names from this dataset to use to create a contingency table and chi-square test output. In this case, we produce a chi-square test and contingency table between diabetes and previous cardiovascular heart disease.

\n", "
" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "SAS Output\n", "\n", "\n", "\n", "
\n", "
\n", "

diabetes vs prevchd

\n", "
\n", "
\n", "

The FREQ Procedure

\n", "
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "
\n", "
\n", "
Frequency
\n", "
Percent
\n", "
Row Pct
\n", "
Col Pct
\n", "
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
Table of DIABETES by PREVCHD
DIABETES(Diabetic Y/N)PREVCHD(Prevalent CHD (MI,AP,CI))
NoYesTotal
No\n", "
\n", "
408
\n", "
81.60
\n", "
89.87
\n", "
92.10
\n", "
\n", "
\n", "
\n", "
46
\n", "
9.20
\n", "
10.13
\n", "
80.70
\n", "
\n", "
\n", "
\n", "
454
\n", "
90.80
\n", "
 
\n", "
 
\n", "
\n", "
Yes\n", "
\n", "
35
\n", "
7.00
\n", "
76.09
\n", "
7.90
\n", "
\n", "
\n", "
\n", "
11
\n", "
2.20
\n", "
23.91
\n", "
19.30
\n", "
\n", "
\n", "
\n", "
46
\n", "
9.20
\n", "
 
\n", "
 
\n", "
\n", "
Total\n", "
\n", "
443
\n", "
88.60
\n", "
\n", "
\n", "
\n", "
57
\n", "
11.40
\n", "
\n", "
\n", "
\n", "
500
\n", "
100.00
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "

Statistics for Table of DIABETES by PREVCHD

\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
StatisticDFValueProb
Chi-Square17.85340.0051
Likelihood Ratio Chi-Square16.38730.0115
Continuity Adj. Chi-Square16.54830.0105
Mantel-Haenszel Chi-Square17.83770.0051
Phi Coefficient 0.1253 
Contingency Coefficient 0.1244 
Cramer's V 0.1253 
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
Fisher's Exact Test
Cell (1,1) Frequency (F)408
Left-sided Pr <= F0.9972
Right-sided Pr >= F0.0090
  
Table Probability (P)0.0062
Two-sided Pr <= P0.0119
\n", "
\n", "
\n", "

Sample Size = 500

\n", "
\n", "
\n", "
\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%LET response = prevchd;\n", "%LET predictor = diabetes;\n", "%LET dataset = fghmtemp;\n", "\n", "PROC FREQ DATA = &dataset;\n", " TITLE \"&predictor vs &response\";\n", " TABLE &predictor * &response / CHISQ;\n", "RUN;\n", "\n", "TITLE;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

Note that the MACRO variables response and predictor are used to update both the TABLE statement and the TITLE statement. After running through the MACRO processor, the following SAS code is generated and run:

\n", "
\n",
    "PROC FREQ DATA = fghmtemp;\n",
    "   TITLE \"diabetes vs prevchd\";\n",
    "   TABLE diabetes * prevchd / CHISQ;\n",
    "RUN;\n",
    "\n",
    "
\n", "

This was a short program, with only a few occurrences of the MACRO variables. But imagine if you had a long program with dozens of occurrences of MACRO variables. You could save a lot of time and trouble by changing the MACRO variable only once at the beginning.

\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## MACRO Functions\n", "\n", "Anytime you find yourself repeated the same program statements over and over, you might want to consider creating a MACRO instead. MACROS are simply a group of SAS statememts that have a name. And, anytime you want to run that group of statements in your program, you use the name instead of re-typng all of the statements.\n", "\n", "The general form of a MACRO is\n", "\n", "
\n",
    "%MACRO macro-name(parameter-1=, parameter-2=,..., parameter-n= );\n",
    "   macro-text\n",
    "%MEND macro-name;\n",
    "
\n", "\n", "The %MACRO statement tells SAS that this is the beginning of the MACRO and %MEND statement signals the end of the MACRO. Macro-name is a name you make up for your MACRO, and parameter-1, parameter-2, ...,parameter-n are inputs to your MACRO function. These parameters are MACRO variables defined within your MACRO and are used just like %LET MACRO variables by prefixing with the ampersand (&).\n", "\n", "For example, if you have a MACRO names %MONTHLYREPORT that has parameters for the month and region, it might start like this:\n", "\n", "`%MACRO monthlyreport(month=, region= );`\n", "\n", "Then, when you invoke the macro, specify the values for the MACRO variables after the equal signs:\n", "\n", "`%monthlyreport(month=May, region=West);`\n", "\n", "
\n", "

Example

\n", "

The following SAS program turns our previous SAS code using %LET MACRO variables into a MACRO function to produce a chi-square test with contingency table bewteen two specified variables in a given dataset.

\n", "
" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "SAS Output\n", "\n", "\n", "\n", "
\n", "
\n", "

diabetes vs prevchd

\n", "
\n", "
\n", "

The FREQ Procedure

\n", "
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "
\n", "
\n", "
Frequency
\n", "
Percent
\n", "
Row Pct
\n", "
Col Pct
\n", "
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
Table of DIABETES by PREVCHD
DIABETES(Diabetic Y/N)PREVCHD(Prevalent CHD (MI,AP,CI))
NoYesTotal
No\n", "
\n", "
408
\n", "
81.60
\n", "
89.87
\n", "
92.10
\n", "
\n", "
\n", "
\n", "
46
\n", "
9.20
\n", "
10.13
\n", "
80.70
\n", "
\n", "
\n", "
\n", "
454
\n", "
90.80
\n", "
 
\n", "
 
\n", "
\n", "
Yes\n", "
\n", "
35
\n", "
7.00
\n", "
76.09
\n", "
7.90
\n", "
\n", "
\n", "
\n", "
11
\n", "
2.20
\n", "
23.91
\n", "
19.30
\n", "
\n", "
\n", "
\n", "
46
\n", "
9.20
\n", "
 
\n", "
 
\n", "
\n", "
Total\n", "
\n", "
443
\n", "
88.60
\n", "
\n", "
\n", "
\n", "
57
\n", "
11.40
\n", "
\n", "
\n", "
\n", "
500
\n", "
100.00
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "

Statistics for Table of DIABETES by PREVCHD

\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
StatisticDFValueProb
Chi-Square17.85340.0051
Likelihood Ratio Chi-Square16.38730.0115
Continuity Adj. Chi-Square16.54830.0105
Mantel-Haenszel Chi-Square17.83770.0051
Phi Coefficient 0.1253 
Contingency Coefficient 0.1244 
Cramer's V 0.1253 
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
Fisher's Exact Test
Cell (1,1) Frequency (F)408
Left-sided Pr <= F0.9972
Right-sided Pr >= F0.0090
  
Table Probability (P)0.0062
Two-sided Pr <= P0.0119
\n", "
\n", "
\n", "

Sample Size = 500

\n", "
\n", "
\n", "
\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%MACRO twobytwo(dataset, predictor, response);\n", "PROC FREQ DATA = &dataset;\n", " TITLE \"&predictor vs &response\";\n", " TABLE &predictor*&response / CHISQ;\n", "RUN;\n", "TITLE;\n", "%MEND twobytwo;\n", "\n", "%twobytwo(fghmtemp, diabetes, prevchd);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

Note that we get the same output as before, but now we have used a MACRO function. Also note that we did not use the parameter names. When not using the names to specify the parameter values, you must be careful to be sure that you give them in the correct order. In our example, the MACRO twobytwo expects the dataset first, then the predictor (row variable) and finally the response (column variable).

\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## MPRINT System Option\n", "\n", "We have shown you what SAS sees after the MACRO processor has resolved the program, but normally you won't see these statements. However, if you specify the MPRINT system option in your program, then SAS will print the resolved statements from MACROS in the SAS log. This can be useful for debugging purposes. To turn on the MPRINT option submit an OPTIONS statement like this:\n", "\n", "`OPTIONS MPRINT;`\n", "\n", "Here is what the SAS log looks like. (Note we have changed the MACRO by adding the NOPRINT option to PROC FREQ to suppress the output for this example.)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", " \n", " \n", " \n", "\n", "\n", "

\n", "\n", "
131  ods listing close;ods html5 (id=saspy_internal) file=stdout options(bitmap_mode='inline') device=svg style=HTMLBlue; ods
131! graphics on / outputfmt=png;
NOTE: Writing HTML5(SASPY_INTERNAL) Body file: STDOUT
132
133 %MACRO twobytwo(dataset, predictor, response);
134 PROC FREQ DATA = &dataset NOPRINT;
135 TITLE "&predictor vs &response";
136 TABLE &predictor*&response / CHISQ;
137 RUN;
138 TITLE;
139 %MEND twobytwo;
140
141 OPTIONS MPRINT;
142
143 %twobytwo(fghmtemp, diabetes, prevchd);
MPRINT(TWOBYTWO): PROC FREQ DATA = fghmtemp NOPRINT;
MPRINT(TWOBYTWO): TITLE "diabetes vs prevchd";
MPRINT(TWOBYTWO): TABLE diabetes*prevchd / CHISQ;
MPRINT(TWOBYTWO): RUN;
NOTE: Processing will terminate because there are no valid requests for displayed output or output data sets.
NOTE: PROCEDURE FREQ used (Total process time):
real time 0.00 seconds
cpu time 0.00 seconds

MPRINT(TWOBYTWO): TITLE;
144
145 OPTIONS NOMPRINT;
146
147 ods html5 (id=saspy_internal) close;ods listing;

148
\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%MACRO twobytwo(dataset, predictor, response);\n", "PROC FREQ DATA = &dataset NOPRINT;\n", " TITLE \"&predictor vs &response\";\n", " TABLE &predictor*&response / CHISQ;\n", "RUN;\n", "TITLE;\n", "%MEND twobytwo;\n", "\n", "OPTIONS MPRINT;\n", "\n", "%twobytwo(fghmtemp, diabetes, prevchd);\n", "\n", "OPTIONS NOMPRINT;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can see that SAS has inserted into the regular SAS log the MPRINT lines. The statements generated by the MACRO processor are labeled with the word MPRINT followed by the name of the MACRO that generated the statements, in this case twobytwo. By using the MPRINT system option it is easy to see the standard SAS statements your MACRO is generating.\n", "\n", "## MACRO %DO Loop\n", "\n", "DO loops are useful for simplifying repetitive code, but a regular DO loop can only be used inside a DATA step. What if we want to repeat the same procedure with minor changes such as different variables. In this case, a MACRO %DO loop is useful. The general form of this statement is:\n", "\n", "
\n",
    "%DO index-variable = start TO end;\n",
    "   SAS code;\n",
    "%END;\n",
    "\n",
    "
\n", "The index-variable serves as the counter variable for the loop and is a MACRO variable that runs from start to end. This type of loop can be used outside of PROC and DATA steps to loop over multiple of these steps.\n", "\n", "
\n", "

Example

\n", "

Suppose we want to run our two by two table code but for many different variables such as in the following SAS code.

\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "PROC FREQ DATA = fghmtemp;\n", " TITLE \"prevap vs prevchd\";\n", " TABLE prevap * prevchd / CHISQ;\n", "RUN;\n", "\n", "PROC FREQ DATA = fghmtemp;\n", " TITLE \"diabetes vs prevchd\";\n", " TABLE diabetes * prevchd / CHISQ;\n", "RUN;\n", "\n", "PROC FREQ DATA = fghmtemp;\n", " TITLE \"prevmi vs prevchd\";\n", " TABLE prevmi * prevchd / CHISQ;\n", "RUN;\n", "\n", "PROC FREQ DATA = fghmtemp;\n", " TITLE \"prevstrk vs prevchd\";\n", " TABLE prevstrk * prevchd / CHISQ;\n", "RUN;\n", "\n", "PROC FREQ DATA = fghmtemp;\n", " TITLE \"prevhyp vs prevchd\";\n", " TABLE prevhyp * prevchd / CHISQ;\n", "RUN;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

The code is all the same with just the row variables changing between each PROC FREQ call. Instead of writing this code over and over, we can instead use a %DO loop. To keep the output shorter, we will only use the first two PROC FREQ calls.

\n", "
" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "SAS Output\n", "\n", "\n", "\n", "
\n", "
\n", "

prevap vs prevchd

\n", "
\n", "
\n", "

The FREQ Procedure

\n", "
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "
\n", "
\n", "
Frequency
\n", "
Percent
\n", "
Row Pct
\n", "
Col Pct
\n", "
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
Table of PREVAP by PREVCHD
PREVAP(Prevalent Angina)PREVCHD(Prevalent CHD (MI,AP,CI))
NoYesTotal
No\n", "
\n", "
443
\n", "
88.60
\n", "
96.30
\n", "
100.00
\n", "
\n", "
\n", "
\n", "
17
\n", "
3.40
\n", "
3.70
\n", "
29.82
\n", "
\n", "
\n", "
\n", "
460
\n", "
92.00
\n", "
 
\n", "
 
\n", "
\n", "
Yes\n", "
\n", "
0
\n", "
0.00
\n", "
0.00
\n", "
0.00
\n", "
\n", "
\n", "
\n", "
40
\n", "
8.00
\n", "
100.00
\n", "
70.18
\n", "
\n", "
\n", "
\n", "
40
\n", "
8.00
\n", "
 
\n", "
 
\n", "
\n", "
Total\n", "
\n", "
443
\n", "
88.60
\n", "
\n", "
\n", "
\n", "
57
\n", "
11.40
\n", "
\n", "
\n", "
\n", "
500
\n", "
100.00
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "

Statistics for Table of PREVAP by PREVCHD

\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
StatisticDFValueProb
WARNING: 25% of the cells have expected counts less
than 5. Chi-Square may not be a valid test.
Chi-Square1337.9100<.0001
Likelihood Ratio Chi-Square1209.3011<.0001
Continuity Adj. Chi-Square1328.4425<.0001
Mantel-Haenszel Chi-Square1337.2342<.0001
Phi Coefficient 0.8221 
Contingency Coefficient 0.6350 
Cramer's V 0.8221 
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
Fisher's Exact Test
Cell (1,1) Frequency (F)443
Left-sided Pr <= F1.0000
Right-sided Pr >= F<.0001
  
Table Probability (P)<.0001
Two-sided Pr <= P<.0001
\n", "
\n", "
\n", "

Sample Size = 500

\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "

diabetes vs prevchd

\n", "
\n", "
\n", "

The FREQ Procedure

\n", "
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "
\n", "
\n", "
Frequency
\n", "
Percent
\n", "
Row Pct
\n", "
Col Pct
\n", "
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
Table of DIABETES by PREVCHD
DIABETES(Diabetic Y/N)PREVCHD(Prevalent CHD (MI,AP,CI))
NoYesTotal
No\n", "
\n", "
408
\n", "
81.60
\n", "
89.87
\n", "
92.10
\n", "
\n", "
\n", "
\n", "
46
\n", "
9.20
\n", "
10.13
\n", "
80.70
\n", "
\n", "
\n", "
\n", "
454
\n", "
90.80
\n", "
 
\n", "
 
\n", "
\n", "
Yes\n", "
\n", "
35
\n", "
7.00
\n", "
76.09
\n", "
7.90
\n", "
\n", "
\n", "
\n", "
11
\n", "
2.20
\n", "
23.91
\n", "
19.30
\n", "
\n", "
\n", "
\n", "
46
\n", "
9.20
\n", "
 
\n", "
 
\n", "
\n", "
Total\n", "
\n", "
443
\n", "
88.60
\n", "
\n", "
\n", "
\n", "
57
\n", "
11.40
\n", "
\n", "
\n", "
\n", "
500
\n", "
100.00
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "

Statistics for Table of DIABETES by PREVCHD

\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
StatisticDFValueProb
Chi-Square17.85340.0051
Likelihood Ratio Chi-Square16.38730.0115
Continuity Adj. Chi-Square16.54830.0105
Mantel-Haenszel Chi-Square17.83770.0051
Phi Coefficient 0.1253 
Contingency Coefficient 0.1244 
Cramer's V 0.1253 
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
Fisher's Exact Test
Cell (1,1) Frequency (F)408
Left-sided Pr <= F0.9972
Right-sided Pr >= F0.0090
  
Table Probability (P)0.0062
Two-sided Pr <= P0.0119
\n", "
\n", "
\n", "

Sample Size = 500

\n", "
\n", "
\n", "
\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%MACRO twobytwov2(dataset, predictor, response);\n", " %DO i=1 %TO %SYSFUNC(countw(&predictor, ' '));\n", " %LET dep = %SCAN(&predictor, &i);\n", " PROC FREQ DATA = &dataset;\n", " TITLE \"&dep vs &response\";\n", " TABLE &dep * &response / CHISQ;\n", " RUN;\n", " TITLE;\n", " %END;\n", "%MEND;\n", "\n", "*%twobytwov2(fghmtemp, prevap diabetes prevmi prevstrk prevhyp, prevchd);\n", "%twobytwov2(fghmtemp, prevap diabetes, prevchd);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

In this MACRO, we instead pass in a list of row variables to the MACRO parameter predictor separated by a space. Previously, we learned about the COUNTW and SCAN functions, but now we need to use them outside of a DATA step. To use COUNTW, we need to use the MACRO function %SYSFUNC to evaluate this function when it is outside the DATA step. Recall that COUNTW counts the number of words, in this case separated by a space, in a list. SAS already has a MACRO version of SCAN, %SCAN, that we can use to go through the list of row variables. The rest of the code is the same as before, but now we need to walk through a list of row variables in predictor using the %DO loop.

\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Conditional Logic in MACROs\n", "\n", "With MACROs and MACRO variables, you have a lot of flexibility. You can increase that flexibility still more by using conditional MACRO statements such as %IF. Here are the general forms of statements used for conditional logic in MACROS:\n", "\n", "
\n",
    "%IF condition %THEN action;\n",
    "   %ELSE %IF condition %THEN action;\n",
    "   %ELSE action;\n",
    "   \n",
    "%IF condition %THEN %DO;\n",
    "   action;\n",
    "%END;\n",
    "\n",
    "
\n", "\n", "These statements probably look familiar because there are parallel statements in standard SAS code, but don't confuse these with their standard counterparts. As with the DO loops, the regular IF/THEN/ELSE statements we learned before can only be used inside a DATA step. The %IF/%THEN/%ELSE statements can be used outside of DATA and PROC steps and inside other MACROs.\n", "\n", "
\n", "

Example

\n", "

In the following SAS program, we create a MACRO with a parameter that will take the values \"Yes\" or \"No\" to request risk difference output from PROC FREQ in our twobytwo MACRO.

\n", "
" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "SAS Output\n", "\n", "\n", "\n", "
\n", "
\n", "

diabetes vs prevchd

\n", "
\n", "
\n", "

The FREQ Procedure

\n", "
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "
\n", "
\n", "
Frequency
\n", "
Percent
\n", "
Row Pct
\n", "
Col Pct
\n", "
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
Table of DIABETES by PREVCHD
DIABETES(Diabetic Y/N)PREVCHD(Prevalent CHD (MI,AP,CI))
NoYesTotal
No\n", "
\n", "
408
\n", "
81.60
\n", "
89.87
\n", "
92.10
\n", "
\n", "
\n", "
\n", "
46
\n", "
9.20
\n", "
10.13
\n", "
80.70
\n", "
\n", "
\n", "
\n", "
454
\n", "
90.80
\n", "
 
\n", "
 
\n", "
\n", "
Yes\n", "
\n", "
35
\n", "
7.00
\n", "
76.09
\n", "
7.90
\n", "
\n", "
\n", "
\n", "
11
\n", "
2.20
\n", "
23.91
\n", "
19.30
\n", "
\n", "
\n", "
\n", "
46
\n", "
9.20
\n", "
 
\n", "
 
\n", "
\n", "
Total\n", "
\n", "
443
\n", "
88.60
\n", "
\n", "
\n", "
\n", "
57
\n", "
11.40
\n", "
\n", "
\n", "
\n", "
500
\n", "
100.00
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "

Statistics for Table of DIABETES by PREVCHD

\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
StatisticDFValueProb
Chi-Square17.85340.0051
Likelihood Ratio Chi-Square16.38730.0115
Continuity Adj. Chi-Square16.54830.0105
Mantel-Haenszel Chi-Square17.83770.0051
Phi Coefficient 0.1253 
Contingency Coefficient 0.1244 
Cramer's V 0.1253 
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
Fisher's Exact Test
Cell (1,1) Frequency (F)408
Left-sided Pr <= F0.9972
Right-sided Pr >= F0.0090
  
Table Probability (P)0.0062
Two-sided Pr <= P0.0119
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
Column 1 Risk Estimates
 RiskASE95%
Confidence Limits
Exact 95%
Confidence Limits
Difference is (Row 1 - Row 2)
Row 10.89870.01420.87090.92640.86720.9249
Row 20.76090.06290.63760.88410.61230.8741
Total0.88600.01420.85810.91390.85480.9125
Difference0.13780.06450.01150.2642  
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
Column 2 Risk Estimates
 RiskASE95%
Confidence Limits
Exact 95%
Confidence Limits
Difference is (Row 1 - Row 2)
Row 10.10130.01420.07360.12910.07510.1328
Row 20.23910.06290.11590.36240.12590.3877
Total0.11400.01420.08610.14190.08750.1452
Difference-0.13780.0645-0.2642-0.0115  
\n", "
\n", "
\n", "

Sample Size = 500

\n", "
\n", "
\n", "
\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%MACRO twobytwov2(dataset, predictor, response, rd=No);\n", " %DO i=1 %TO %SYSFUNC(countw(&predictor, ' '));\n", " %LET dep = %SCAN(&predictor, &i);\n", " %IF &rd = No %THEN %DO;\n", " PROC FREQ DATA = &dataset;\n", " TITLE \"&dep vs &response\";\n", " TABLE &dep * &response / CHISQ;\n", " RUN;\n", " TITLE;\n", " %END;\n", " %ELSE %IF &rd = Yes %THEN %DO;\n", " PROC FREQ DATA = &dataset;\n", " TITLE \"&dep vs &response\";\n", " TABLE &dep * &response / CHISQ riskdiff;\n", " RUN;\n", " TITLE;\n", " %END;\n", " %END;\n", "%MEND;\n", "\n", "%twobytwov2(dataset=fghmtemp, predictor=diabetes, response=prevchd, rd=Yes);\n", "*%twobytwov2(dataset=fghmtemp, predictor=diabetes, response=prevchd);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

The rd parameter expects the values either Yes or No with the default value of No. If No is specified (or if it is left blank), then we will only get the chi-squared output. If rd is specified to be Yes, then we get the chi-squared output and the risk difference output.

\n", "

Note that, we have specified a default value for rd in our MACRO definition. When we use a defualt value, we create positiional parameters, and in this case, when calling the MACRO, we must specify all the parameters by parameter-name=parameter-value in the MACRO call.

\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Data Driven Programs - CALL SYMPUT\n", "\n", "Using the CALL SYMPUT macro routing you can let a MACRO program look at the data and then decide for itself what to do. CALL Symput takes a value from a DATA step and assigns it to a MACRO variable which you can then use later in your program.\n", "\n", "CALL SYMPUT can take many forms, but to assign a single value to a single MACRO variable, use CALL SYMPUT with this general form:\n", "\n", "`CALL SYMPUT(\"marcro-variable\", value);`\n", "\n", "where macro-variable is the name of a macro variable, either new or old, and is enclosed in quotes. Value is the name of a variable from a DATA step whose current value you want to assigne to that MACRO variable.\n", "\n", "CALL SYMPUT is often used in IF/THEN/ELSE statements, for example\n", "\n", "
\n",
    "IF Place = 1 THEN\n",
    "   CALL SYMPUT(\"WinningTime\", Time);\n",
    "\n",
    "
\n", "This statement tells SAS to creat a MACRO variable named WinningTime and set it equal to the current value of the variable Time when the value of Place is 1.\n", "\n", "Be careful. You cannot create a MACRO variable with CALL SYMPUT and use it in the same DATA step. Here's why. When you submit MACRO code, it is resolved by the MACRO processor, and then compiled and executed. Not untile the final stage, execution, does SAS see your DATA. CALL SYMPUT takes a data value from the execution phase, and passes it back to the MACRO processor for use in a later step. That's why you must put CALL SYMPUT in one DATA step, but not use it until a later step.\n", "\n", "
\n", "

Example

\n", "

The following SAS program use the expected cell counts, calculated by PROC FREQ, to determine whether to return the chi-squared test p-value or Fisher's exact test p-value. Recall that one standard rule of thumb is that to use the chi-square test, all expected cell counts should be at least 5.

\n", "

The following code is some prelimanary code that we run to figure out how to define how MACRO.

\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "/*First, we will need to write and test our code for selecting only the output we want before\n", " we make our updated macro. We want to run a continuity adjusted chi-square test or \n", " Fisher's exact test if the expected counts are too small. We only want the table with row percentages\n", " and the result of the correct test in our output.*/\n", "\n", "*ODS TRACE ON;\n", "PROC FREQ DATA = fghmtemp;\n", " TABLE prevap*prevchd / EXPECTED chisq;\n", " ODS OUTPUT CrossTabFreqs = ct ChiSq = chi FishersExact = fet;\n", "RUN;\n", "*ODS TRACE OFF;\n", "\n", "*Lot's of missing data. We only want to use non missing values;\n", "PROC PRINT DATA = ct;\n", "RUN;\n", "\n", "/* Test how to clean the resulting datasets */\n", "/* Remove the missing values and get the cells with epxected countes less than 5 */\n", "title '/* Test how to clean the resulting datasets */';\n", "PROC PRINT DATA=ct;\n", " WHERE expected < 5 and prevap ne . and prevchd ne .;\n", "RUN;\n", "title;\n", "\n", "*Create an indicator variable that is 1 if there is at least one cell with expected count less than 5;\n", "%LET low_count = 0;\n", "DATA _NULL_; *Run through the data set without creating a new one;\n", " SET ct;\n", " IF EXPECTED < 5 and prevap ne . and prevchd ne . THEN CALL SYMPUT('low_count', 1);\n", "RUN;\n", "\n", "%PUT &=low_count; *Should show value of 1 in the log file.;\n", "\n", "/* Now, that we can check to see if the expected cell counts are less than 5,\n", " * we need to see how we can clean the chi-square or Fisher tables to print only \n", " * the test name and p-value.\n", " */\n", "\n", "PROC PRINT DATA=chi;\n", "RUN;\n", "\n", "*Select the row for the continuity adjusted chi-square test;\n", "DATA chi2;\n", " SET chi;\n", " WHERE statistic=\"Continuity Adj. Chi-Square\";\n", " DROP TABLE DF VALUE;\n", "RUN;\n", "\n", "PROC PRINT DATA = chi2;\n", "RUN;\n", "\n", "*Do the same thing for Fisher's exact test;\n", "PROC PRINT DATA = fet;\n", "RUN;\n", "\n", "DATA fet2 (RENAME = (cValue1 = Prob));\n", " SET fet;\n", " Statistic = \"Fisher's Exact Test\";\n", " WHERE NAME1 = \"XP2_FISH\";\n", " KEEP statistic cValue1;\n", "RUN;\n", "\n", "DATA fet2;\n", " RETAIN Statistic Prob;\n", " SET fet2;\n", "RUN;\n", "\n", "PROC PRINT DATA = fet2;\n", "RUN;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

The previous code is exploratory to find the ODS table names and figure out how to extract the needed data from these tables. The following code provides the final workig MACRO.

\n", "
" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "SAS Output\n", "\n", "\n", "\n", "
\n", "
\n", "
\n", "
\n", "

prevap vs prevchd

\n", "
\n", "
\n", "

The FREQ Procedure

\n", "
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "
\n", "
\n", "
Frequency
\n", "
Row Pct
\n", "
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
Table of PREVAP by PREVCHD
PREVAP(Prevalent Angina)PREVCHD(Prevalent CHD (MI,AP,CI))
NoYesTotal
No\n", "
\n", "
443
\n", "
96.30
\n", "
\n", "
\n", "
\n", "
17
\n", "
3.70
\n", "
\n", "
\n", "
\n", "
460
\n", "
 
\n", "
\n", "
Yes\n", "
\n", "
0
\n", "
0.00
\n", "
\n", "
\n", "
\n", "
40
\n", "
100.00
\n", "
\n", "
\n", "
\n", "
40
\n", "
 
\n", "
\n", "
Total\n", "
\n", "
443
\n", "
\n", "
\n", "
\n", "
57
\n", "
\n", "
\n", "
\n", "
500
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
ObsStatisticProb
1Fisher's Exact Test<.0001
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "

diabetes vs prevchd

\n", "
\n", "
\n", "

The FREQ Procedure

\n", "
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "
\n", "
\n", "
Frequency
\n", "
Row Pct
\n", "
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
Table of DIABETES by PREVCHD
DIABETES(Diabetic Y/N)PREVCHD(Prevalent CHD (MI,AP,CI))
NoYesTotal
No\n", "
\n", "
408
\n", "
89.87
\n", "
\n", "
\n", "
\n", "
46
\n", "
10.13
\n", "
\n", "
\n", "
\n", "
454
\n", "
 
\n", "
\n", "
Yes\n", "
\n", "
35
\n", "
76.09
\n", "
\n", "
\n", "
\n", "
11
\n", "
23.91
\n", "
\n", "
\n", "
\n", "
46
\n", "
 
\n", "
\n", "
Total\n", "
\n", "
443
\n", "
\n", "
\n", "
\n", "
57
\n", "
\n", "
\n", "
\n", "
500
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
ObsStatisticProb
1Continuity Adj. Chi-Square0.0105
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "

prevmi vs prevchd

\n", "
\n", "
\n", "

The FREQ Procedure

\n", "
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "
\n", "
\n", "
Frequency
\n", "
Row Pct
\n", "
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
Table of PREVMI by PREVCHD
PREVMI(Prevalent MI (Hosp,Silent))PREVCHD(Prevalent CHD (MI,AP,CI))
NoYesTotal
No\n", "
\n", "
443
\n", "
94.46
\n", "
\n", "
\n", "
\n", "
26
\n", "
5.54
\n", "
\n", "
\n", "
\n", "
469
\n", "
 
\n", "
\n", "
Yes\n", "
\n", "
0
\n", "
0.00
\n", "
\n", "
\n", "
\n", "
31
\n", "
100.00
\n", "
\n", "
\n", "
\n", "
31
\n", "
 
\n", "
\n", "
Total\n", "
\n", "
443
\n", "
\n", "
\n", "
\n", "
57
\n", "
\n", "
\n", "
\n", "
500
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
ObsStatisticProb
1Fisher's Exact Test<.0001
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "

prevstrk vs prevchd

\n", "
\n", "
\n", "

The FREQ Procedure

\n", "
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "
\n", "
\n", "
Frequency
\n", "
Row Pct
\n", "
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
Table of PREVSTRK by PREVCHD
PREVSTRK(Prevalent Stroke (Infarct,Hem))PREVCHD(Prevalent CHD (MI,AP,CI))
NoYesTotal
No\n", "
\n", "
438
\n", "
89.02
\n", "
\n", "
\n", "
\n", "
54
\n", "
10.98
\n", "
\n", "
\n", "
\n", "
492
\n", "
 
\n", "
\n", "
Yes\n", "
\n", "
5
\n", "
62.50
\n", "
\n", "
\n", "
\n", "
3
\n", "
37.50
\n", "
\n", "
\n", "
\n", "
8
\n", "
 
\n", "
\n", "
Total\n", "
\n", "
443
\n", "
\n", "
\n", "
\n", "
57
\n", "
\n", "
\n", "
\n", "
500
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
ObsStatisticProb
1Fisher's Exact Test0.0519
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "

prevhyp vs prevchd

\n", "
\n", "
\n", "

The FREQ Procedure

\n", "
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "
\n", "
\n", "
Frequency
\n", "
Row Pct
\n", "
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
Table of PREVHYP by PREVCHD
PREVHYP(Prevalent Hypertension)PREVCHD(Prevalent CHD (MI,AP,CI))
NoYesTotal
No\n", "
\n", "
191
\n", "
93.63
\n", "
\n", "
\n", "
\n", "
13
\n", "
6.37
\n", "
\n", "
\n", "
\n", "
204
\n", "
 
\n", "
\n", "
Yes\n", "
\n", "
252
\n", "
85.14
\n", "
\n", "
\n", "
\n", "
44
\n", "
14.86
\n", "
\n", "
\n", "
\n", "
296
\n", "
 
\n", "
\n", "
Total\n", "
\n", "
443
\n", "
\n", "
\n", "
\n", "
57
\n", "
\n", "
\n", "
\n", "
500
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
ObsStatisticProb
1Continuity Adj. Chi-Square0.0052
\n", "
\n", "
\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%MACRO twobytwov3(dataset, predictor, response);\n", "\t%LET n = %sysfun(countw(predictor));\n", "\t%LET i = 1;\n", "\t%LET dep = %SCAN(&predictor, &i);\n", "\t%DO %WHILE(&dep ne);\n", "\t\tPROC FREQ DATA = &dataset;\n", " TITLE;\n", " ODS SELECT NONE;\n", "\t\t\tTABLE &dep*&response / CHISQ EXPECTED;\n", "\t\t\tODS OUTPUT crosstabfreqs = ct ChiSq = chi FishersExact = fet;\n", "\t\tRUN;\n", "\n", "\t\tPROC FREQ DATA = &dataset;\n", "\t\t\tTITLE \"&dep vs &response\";\n", "\t\t\tTABLE &dep*&response / NOCOL NOPERCENT;\n", "\t\t\tODS SELECT CrossTabFreqs;\n", "\t\tRUN;\n", "\t\tTITLE;\n", "\n", "\t\t%LET low_count = 0;\n", "\t\tDATA _NULL_; *Run through the data set without creating a new one;\n", "\t\t\tSET ct;\n", "\t\t\tIF EXPECTED < 5 and &dep ne . and &response ne . THEN CALL SYMPUT('low_count', 1);\n", "\t\tRUN;\n", "\n", "\t\t%IF &low_count = 0 %THEN %DO;\n", "\t\tDATA chi;\n", "\t\t\tSET chi;\n", "\t\t\tWHERE statistic=\"Continuity Adj. Chi-Square\";\n", "\t\t\tDROP TABLE DF VALUE;\n", "\t\tRUN;\n", "\n", "\t\tPROC PRINT DATA = chi;\n", "\t\tRUN;\n", "\t\t%END;\n", "\t\t%ELSE %DO;\n", "\t\tDATA fet (RENAME = (cValue1 = Prob));\n", "\t\t\tSET fet;\n", "\t\t\tStatistic = \"Fisher's Exact Test\";\n", "\t\t\tWHERE NAME1 = \"XP2_FISH\";\n", "\t\t\tKEEP statistic cValue1;\n", "\t\tRUN;\n", "\n", "\t\tDATA fet;\n", "\t\t\tRETAIN Statistic Prob;\n", "\t\t\tSET fet;\n", "\t\tRUN;\n", "\n", "\t\tPROC PRINT DATA = fet;\n", "\t\tRUN;\n", "\t\t%END;\n", " \n", "\t\t%LET i = %EVAL(&i + 1);\n", "\t\t%LET dep = %SCAN(&predictor, &i);\n", "\t%END;\n", "%MEND;\n", "\n", "%twobytwov3(fghmtemp, prevap diabetes prevmi prevstrk prevhyp, prevchd);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

This program uses PROC FREQ to calculate the expected cell counts and extracts this table using ODS OUTPUT. If any of the cells have an expected cell count less than 5, then CALL SYMPUT sets the value of the MACRO variable lowcount to 1. Otherwise, it retains the starting value of 0. This MACRO variable is then used in a %IF statement to determine whether to save and print the p-value from a chi-square test or Fisher's exact test.

\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercises\n", "\n", "1. Write a macro that discretizes a quantitative variable into four categories based on quantiles. That is:\n", "\n", " * if X < Q1 then group = 1\n", " * if Q1 < X < M then group = 2\n", " * if M < X < Q3 then group = 3\n", " * if Q3 < X then group = 4\n", "\n", "The macro should have the following defnition - %quartilesmacro(mydata, qvar, round, out);\n", "\n", "* mydata: dataset containing the quantitative variable\n", "* qvar: name of quantitative variable\n", "* round: integer representing number of decimal places to round to\n", "* out: name of output dataset which contains the categorized variable which will have name qvar_cat, e.g. if the qvar is bmi then the output variable is bmi_cat." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "SAS", "language": "sas", "name": "sas" }, "language_info": { "codemirror_mode": "sas", "file_extension": ".sas", "mimetype": "text/x-sas", "name": "sas" } }, "nbformat": 4, "nbformat_minor": 2 }