Macro Language Reference

Below is the documentation for all macro commands in the macro package. The documentation contains the syntax, a detailed explanation, and examples for each of the available commands.

Index

Macro Comments

Syntax

#% <comment>

Where “<comment>” is any text string. The macro comment is a single line comment. If you need multiple lines, use multiple macro comments.

Details

A macro comment is like a regular R comment, but with a “#%” prefix. The macro comment can be used to document your macro statements. The macro comment will be removed during pre-processing.

Examples

Here is a macro comment and a regular R comment:


#% Here is a macro comment

# Here is a regular comment

During pre-processing, the macro comment is removed, while the regular comment is retained:


# Here is a regular comment

top

Macro Variables

Syntax

#%let <variable> <- <value>
#%let <variable> = <value>
#%let <variable>

Where “<variable>” is the name of the variable, and “<value>” is some value assigned to the variable.

Note that macro variable names must begin with an alphabetic character or dot (“.”). They can contain only alphabetic characters, numbers, dots, and underscores (“_“).

Macro variables should be named without a leading ampersend (“&”). The leading ampersand will be appended automatically when the variable is added to the symbol table.

Either the left arrow (“<-”) or equals sign (“=”) is a valid assignment operator for macro variables.

To clear a macro variable, leave it unassigned. This action will remove the macro variable from the symbol table.

Details

Macro variables are variables that have a temporary existence during pre-processing. The variables are stored in the pre-processor symbol table, and used as text replacement tokens during pre-processing.

The assigned value is treated as a string, and replaced exactly as assigned. That means if the value is assigned without quotes, it will be replaced without quotes. If the value is quoted, the quotes will be retained during replacement.

Macro variable resolution can be delayed using multiple ampersands. See example below.

Examples

Basic Assignment

Here is a basic macro assignment of numeric values:

#% Assignment using left arrow
#%let a <- 1
x <- `&a`

#% Assignment using equals sign
#%let b = 2
y <- `&b`

Here is the resolved output:

x <- 1

y <- 2

Both variables &a and &b have been replaced as assigned.

Note the use of backticks (“`”) on the macro variables. The backticks are necessary because R does not normally permit a special character to begin the name of a variable. The backticks essentially “escape” the variable name, and allow it to contain any character.

Quoted Assignment

#% Quoted Assignment
#%let a <- "One"
x <- `&a`

#% Unquoted Assignment
#%let b <- Two
y <- "&b"
z <- '&b'

The above code will resolve as follows:

x <- "One"

y <- "Two"
z <- 'Two'

Observe that when macro variables appear inside text strings, comments, or macro commands, they do not need to be escaped with backticks.

Also observe with the macro variable &b that single or double quotes have no effect on macro variable resolution. Both are resolved normally. This behavior is different from SAS.

Unquoted Assignment Errors

If you do not quote the macro variable value, and do not quote the macro variable to be replaced, it will result in an error:

#% Unquoted Assignment
#%let c <- Three

# Unquoted resolution
z <- `&c`

The error is:

Error in eval(ei, envir) : object 'Three' not found

The error occurs because the value “Three” is now left unquoted in open code. The R parser thinks it is the name of a variable or object, and will try to look it up in the environment. If it can’t find the name in the environment, the parser generates an error.

Variable Removal

If you want to remove an existing macro variable from the symbol table, leave it unassigned. Like this:

#% Macro assignment
#%let b <- 2
x <- `&b`

#% Clear assignment
#%let b
y <- `&b`

The above code will generate an error, as the y assignment runs after the &b macro variable has been cleared. Here is the error that is generated:

Error in mreplace(ln) : 
  Macro variable '&b' not valid for text replacement.

Global Macro Assignment

Macro variables can also be assigned in regular R code. In the below code, macro variable &c is assigned the value of 3, and then the template code file “temp1.R” is executed via msource():

# Assignment before msource()
`&c` <- 3

# Pre-process
msource("./temp1.R", "./temp1_mod.R")

Here is the file “temp1.R”:

# Print macro variable
z <- `&c`

When the “temp1.R” file is resolved to “temp1_mod.R”, it will look like this:

# Print macro variable
z <- 3

You can see that the macro variable &c is replaced normally. The reason is because any variable with a leading ampersand (“&”) in the parent environment will be copied to the macro symbol table when the pre-processor begins.

Also note that parent macro assignments do not need to take place in the same code file as msource(). You can assign them in a setup file if desired. As long as they exist in the parent environment, the macro variables will be copied to the symbol table.

Nested Assignment

Macro variables can be nested inside one another. Nesting means that a macro variable name can be constructed from other macro variables. This feature allows for more dynamic assignments. Here is a simple demonstration:

#%let x <- 1
#%let mvar1 <- Hello!

print("&mvar&x")

The above code outputs the string “Hello!”. Why? The reason is because the macro processor first found the variable “&x” and replaced it with the value “1”. The macro processor continued to scan for variables, found that “&mvar1” was also a macro variable, and made the replacement. The system allows up to 10 levels of nesting. This feature can be useful in some situations.

Delayed Resolution

If needed, macro variable assignment can be delayed using additional ampersands. For example, let’s say there was another macro variable mvar and we don’t want to conflict with it. In that case, you can add another ampersand and delay resolution to get the proper assignment to mvar1:

#%let x <- 1
#%let mvar <- Goodbye!
#%let mvar1 <- Hello!

print("&mvar&x")
print("&&mvar&x")

The above code resolved as follows:

print("Goodbye!1")
print("Hello!")

Notice the difference in the resolution between &mvar and &&mvar. Since &mvar was a valid macro variable, it performed the replacement when encountered. The same with &x. But for &&mvar, the double ampersand delayed resolution until after &x had been replaced. At that point, &&mvar becomes &mvar1, and the replacement is made for &mvar1. In this way, you have fine-grained control over how and when macro variables resolve.

Advanced Assignment

The examples above use R macro variables much like regular variables. However, it is important to realize that macro variables can be used anywhere, to manipulate any piece of code. Here is a taste of some advanced assignments:

#%let a <- # Subset mtcars
#%let b <- mtcars
#%let c <- , mpg > 25
#%let d <- dat <- subset(&b&c)
#%let e <- (dat)
`&a`
`&d`
print&e

Here is the generated code:

# Subset mtcars
dat <- subset(mtcars, mpg > 25)
print(dat)

And here is the console output:

>msource()
                mpg cyl  disp  hp drat    wt  qsec vs am gear carb
Fiat 128       32.4   4  78.7  66 4.08 2.200 19.47  1  1    4    1
Honda Civic    30.4   4  75.7  52 4.93 1.615 18.52  1  1    4    2
Toyota Corolla 33.9   4  71.1  65 4.22 1.835 19.90  1  1    4    1
Fiat X1-9      27.3   4  79.0  66 4.08 1.935 18.90  1  1    4    1
Porsche 914-2  26.0   4 120.3  91 4.43 2.140 16.70  0  1    5    2
Lotus Europa   30.4   4  95.1 113 3.77 1.513 16.90  1  1    5    2

Obviously, the above demonstration makes no sense. The point is to illustrate that the R macro language views all code as text. You can construct comments, lines of code, variable names, function calls, parameters, or anything else using the macro language.

Dynamic Variable Creation

You can create the names of regular R variables dynamically using macro variables. There are different ways to do it. Here are some of the different ways:

#% Assign variable name to macro
#%let x <- cyl

# Leading macro replacement
`&x._mean` <- mean(mtcars[["&x"]])

# Trailing macro replacement
mean_&x <- mean(mtcars[["&x"]])

# Middle macro replacement
my_&x_mean <- mean(mtcars[["&x"]])

# Intermediate macro replacement
#%let myvar <- &x_mean
`&myvar` <- mean(mtcars[["&x"]])
                

Here is the resolved code:


# Leading macro replacement
`cyl_mean` <- mean(mtcars[["cyl"]])

# Trailing macro replacement
mean_cyl <- mean(mtcars[["cyl"]])

# Middle macro replacement
my_cyl_mean <- mean(mtcars[["cyl"]])

# Intermediate macro replacement
cyl_mean <- mean(mtcars[["cyl"]])

Notice that you only need backtick escapes when you have a leading macro variable.

top

Macro Conditionals

Syntax

#%if <condition>
#%if (<condition>)

#%ifelse <condition>
#%ifelse (<condition>)

#%else

#%end

Where “<condition>” is an expression that evaluates to TRUE or FALSE.

A macro conditional block must begin with an #%if condition and be finalized with a #%end. The #%ifelse and #%else blocks are optional. Failure to finalize the block with an #%end will result in an error or other unexpected behavior.

Parenthesis around the conditional expressions are optional but recommended.

The syntax of R conditionals is somewhat different from SAS. One difference is that the %then %do directives in SAS have been entirely eliminated from the macro package syntax. Another difference is that the #%ifelse command is one word instead of two.
A third difference is that in SAS, each conditional section must be concluded with an %end. In R, you only need one #%end at the conclusion of the entire conditional. In general, the R syntax is simpler.

Details

Conditional expressions should be standard R expressions, using standard R syntax. For comparisons, the expression should use the double-equals (“==”) comparison operator, as in a normal R “if” statement. Likewise, all other comparison operators should follow Base R syntax.

A conditional expression may contain macro variables. These macro variables will be resolved before the expression is evaluated. Reminder that the macro variables are text replacements, and will be resolved accordingly.

The conditional expression may also contain R functions like any() and all(). In most cases, these function do not need to be wrapped in %sysfunc(). They will evaluate properly as part of the expression.

If an expression in a macro “if” block is TRUE, the code inside that block will be emitted during pre-processing. If the expression evaluates to FALSE, code inside that block will be ignored.

Macro conditions may be nested inside one another. There is no limit on the number of nested levels.

Examples

Simple Conditional

Here is a simple condition to construct a path. The source directory can change depending on the environment.

#%let dev_path <- ./dev/data
#%let prod_path <- ./prod/data
#%let env <- dev

# Path to data
#%if ("&env" == "prod")
pth <- "&prod_path/dm.sas7bdat"
#%else 
pth <- "&dev_path/dm.sas7bdat"
#%end

When run through the macro processor, the above code will generate the following:


# Path to data
pth <- "./dev/data/dm.sas7bdat"

The pre-processor selected the “dev” path as indicated, constructed the path appropriately, and removed all macro statements.

Nested Conditionals

Macro conditionals may also be nested inside one another. Nesting allows you to construct more complicated logic. Here is an example:

#% Data source SAS or RDS
#%let src <- SAS

#% Select analysis variables
#%let anal_vars <- c("AGE", "AGEG", "SEX", "RACE", "PULSE", "TEMP")

###################
# Get data
###################
#%if ("&src" == "SAS")
  library(haven)

  # Get adsl dataset
  adsl <- haven("./data/ADSL.sas7bdat")
  
  #%if (any(c("PULSE", "TEMP", "BP") %in% &anal_vars))
  # Get advs dataset
  advs <- haven("./data/ADVS.sas7bdat")
  #%end

#%else 
  
  # Get adsl dataset
  adsl <- readRDS("./data/ADSL.rds")
  
  #%if (any(c("PULSE", "TEMP", "BP") %in% &anal_vars))
  # Get advs dataset
  advs <- readRDS("./data/ADVS.rds")
  #%end

#%end

When the nested conditionals resolve, they will look like this:

######################
# Get data
######################
  library(haven)

  # Get adsl dataset
  adsl <- haven("./data/ADSL.sas7bdat")

  # Get advs dataset
  advs <- haven("./data/ADVS.sas7bdat")

In this way, you can perform complex logic in the macro pre-processor, and still produce a concise output.

top

Macro Include

Syntax

#%include <path>
#%include '<path>'
#%include "<path>"

Where “<path>” is a path to the file to include. The file path may be quoted or unquoted. If the path is quoted, single or double quotes may be used.

Details

The #%include() macro command inserts text from an external file into the generated output file. This behavior is different from the Base R source() function, which only executes the code in the external file. A macro include actually copies the code into the generated file. The included code does not have to be fully functional. It can be a code snippet that only works when combined with other snippets.

Examples

The below example includes code from the file “dat01.R”. This file contains a snippet of sample data:

# Create sample data
#%include "./templates/dat01.R"

# Print sample data
print(dat)

The resolved macro will look like this:

# Create sample data
dm <- read.table(header = TRUE, text = '
       SUBJID  ARM    SEX  RACE    AGE
       "001"   "ARM A" "F"  "ASIAN" 19
       "002"   "ARM B" "F"  "WHITE" 21
       "003"   "ARM C" "F"  "WHITE" 23
       "004"   "ARM D" "F"  "BLACK OR AFRICAN AMERICAN" 28
       "005"   "ARM A" "M"  "WHITE" 37
       "006"   "ARM B" "M"  "WHITE" 34
       "007"   "ARM C" "M"  "ASIAN" 36
       "008"   "ARM D" "M"  "WHITE" 30
       "009"   "ARM A" "F"  "WHITE" 39
       "010"   "ARM B" "F"  "WHITE" 31
       "011"   "ARM C" "F"  "BLACK OR AFRICAN AMERICAN" 33
       "012"   "ARM D" "F"  "WHITE" 38
       "013"   "ARM A" "M"  "BLACK OR AFRICAN AMERICAN" 37
       "014"   "ARM B" "M"  "WHITE" 34
       "015"   "ARM C" "M"  "WHITE" 36
       "016"   "ARM A" "M"  "WHITE" 40')

# Print sample data
print(dm)

Notice that the included code is integrated with the surrounding code from the macro-enabled program. In this way, you can collate a program from multiple code snippets into a unified result.

top

Built-in Macro Functions

The macro package currently supports four built-in macro functions: %sysfunc(), %symexist(), %symput(), and %nrstr().

Syntax

%sysfunc(<expression>, [<format>])

Where “<expression>” is an R expression, and “<format>” is an optional format code. The expression may contain R functions, operators, hard-coded values, and macro variables.

If “<format>” is supplied, the function will apply the format after the expression is resolved, and return the formatted value. The format code does not need to be quoted. If the format is unquoted, the output of the %sysfunc() function will be unquoted. If the format is quoted, the output of the %sysfunc() function will be quoted.

Formats specified on the %sysfunc() function may be any format code supported by the fapply() function from the fmtr package. See the fapply reference page and the FormattingStrings page for further information.

Note that the R %sysfunc() is a general evaluation function. It can evaluate any R expression, even those that do not resolve to a numeric value. Likewise, the optional format can be any type of format supported by fapply(), even those that take text input.

%symexist(<name>)

Where “<name>” is an unquoted name of a macro variable. If the macro variable exists, the function will return TRUE. Otherwise, the function will return FALSE.

%symput(<expression>)

Where “<expression>” is an R expression that references the execution environment. The expression can contain macro variables. The return value from %symput() is assigned to a macro variable using a #%let command. The %symput() function must exist as part of a #%let statement. It cannot be used with any other R macro command.

%nrstr(<expression>)

Where “<expression>” is any R expression, specifically one that contains a reference to a macro variable that you do not want to resolve. The %nrstr() masks the expression to prevent normal resolution.

Details

The %sysfunc(), %symexist(), %symput(), and %nrstr() functions are helper functions that are usually contained in other macro statements. Most commonly, they will exist as part of “let” or “if” statement. For this reason, the syntax does not include a leading comment symbol (“#”).

The purpose of %sysfunc() is to evaluate regular R expressions during macro processing. The evaluated result can then be assigned to a macro variable, or used in a macro condition.

The purpose of %symexist() is to check for the existence of a macro variable. This check is likewise most often performed as part of a macro conditional expression.

The purpose of %symput() is to evaluate an expression in the execution environment and assign it to a macro variable in the macro environment.

Normally, the macro commands in a program are resolved first, and then the remaining R code is executed. Sometimes, however, you want to make decisions in the macro code depending on what is going on in the regular R code. For example, if your input dataset has no rows, you may decide do something differently in the macro logic. It is for this type of situation that %symput() was designed for.

When the macro pre-processor encounters a %symput() function, it will first evaluate all emitted code. Then it will evaluate and return the expression inside the %symput().

SAS programmers will notice that the R %symput() function is quite different from the SAS version. But they have the same goal.

Similar to the SAS version, %nrstr() is a macro quoting function, and can be used to prevent resolution of a macro variable or other macro expression. This function can be used if you are trying to output a macro variable name instead of the resolved value.

Examples

%sysfunc()

Here are some examples illustrating the basic operation of %sysfunc():

#% Unevaluated assignment
#%let a <- 2 + 2
w <- `&a`

#% Evaluated assignment
#%let b <- %sysfunc(2 + 2)
x <- `&b`

#% Unevaluated replacement 
#%let c <- &a + &b
y <- `&c`

#% Evaluated replacement
#%let d <- %sysfunc(&a + &b)
z <- `&d`
  

The above code resolves as follows:

w <- 2 + 2
x <- 4
y <- 2 + 2 + 4
z <- 8

Observe the different ways each expression resolves depending on the usage %sysfunc().

Here is another example:

#%let a <- c(1.205, 4.683, 3.812, 6.281, 9.467)

#%if (%sysfunc(mean(&a.)) > 5)
x <- "> 5"
#%else 
x <- "<= 5"
#%end
print(x)
print("The mean of 'a' is %sysfunc(mean(&a.), %.2f)")

The above macro evaluates the mean of &a., compares to the hard-coded value 5, and then sets the value of x. The %sysfunc() macro function allows for immediate evaluation of the Base R mean() function.

Here is the execution of the above example:

> msource()
# ---------
# x <- "> 5"
#  print(x)
#  print("The mean of 'a' is 5.09")
# ---------
# [1] "> 5"
# [1] "The mean of 'a' is 5.09"

In the above example, %sysfunc() is used in the conditional, and inside a text string. You can see that it is resolved in both cases.

Note that you do not need multiple calls to %sysfunc() if you have multiple Base R functions in your expression. Just put your entire expression inside one call to %sysfunc(), and it will evaluate as desired.

%symexist()

Below is a simple example checking for the existence of some macro variables:

#%let a = 1

#%if (%symexist(a))

  print("Macro variable 'a' exists!")
  print("Here is the value: &a")

#%else

  print("Macro variable 'a' does not exist!")

#%end

#%if (%symexist(b))

  print("Macro variable 'b' exists!")
  print("Here is the value: &b")

#%else

  print("Macro variable 'b' does not exist!")

#%end

And here is the resolved version:


  print("Macro variable 'a' exists!")
  print("Here is the value: 1")

  print("Macro variable 'b' does not exist!")

Notice that the variable name inside the %symexist() function is not quoted. For this function, quoting is not necessary, and will in fact prevent the function from finding the specified variable name.

%symput()

Examine the following code. In this code, we are trying to get the length of a vector into a macro variable:

# Assign vector
v1 <- c("one", "two", "three")

#% Get length of vector
#%let x <- %symput(length(v1))

#% Get vector values
#%let y <- %symput(v1)

# Use macro variables
a <- `&x`
b <- `&y`

# Print to console
print(a)
print(b)

If you run the above code in msource(), you will get the following output:

> msource()
---------
# Assign vector
 v1 <- c("one", "two", "three")
 
 # Use macro variables
 a <- 3
 b <- c('one', 'two', 'three')
 
 print(a)
 print(b)
---------
[1] 3
[1] "one"   "two"   "three"

You can see the length and value of vector ‘v1’ has been pulled from normal R code into macro variables. These macro variables can then be used with other macro commands to control what happens during pre-processing.

%nrstr()

The following example shows how %nrstr() can prevent resolution of a macro variable:

#% Assign variables
#%let x <- 1
print("The value of %nrstr(&x) is &x")

The above code sends the following to the console:

> msource()
[1] "The value of &x is 1"

Why didn’t the first &x resolve? Because %nrstr() prevented it. This function is useful if you need to reference macro variable names or generate macro code.

top

Macro Do Loops

Syntax

#%do <variable> = <start> %to <end>
#%end

Where “<variable>” is a macro variable name to use for the loop counter, and “<start>” and “<end>” signify the beginning and ending of the loop sequence. The “<start>” and “<end>” values can be numbers, macro variables that resolve to numbers, or expressions that resolve to numbers.

The “#%end” command signifies the end of the “do” block. It is required.

Details

A macro “do” loop causes the pre-processor to repeat a section of code some number of times. The do variable will be incremented for each pass. This variable can be used to alter the section of code each time around. In the generated output file, the do block code will actually be copied for each iteration of the loop.

Examples

Here is a simple example of a macro do loop:

#%do x = 1 %to 3
print("Hello #&x.")
#%end

The console output from the above macro do loop is as follows:

[1] "Hello #1"
[1] "Hello #2"
[1] "Hello #3"

And the generated code is as follows:

print("Hello #1")
print("Hello #2")
print("Hello #3")

Observe that the code inside the macro do loop was copied for each iteration of the loop.

Here is a more interesting example:

#%let vars <- c("mpg", "disp", "hp", "drat")
# Get data
dat <- mtcars

#% Print mean for each variable
#%do v = 1 %to %sysfunc(length(&vars))
#%let var <- %sysfunc(&vars[&v])

# Calculate mean for &var.
mn&v. <- sprintf("%.2f", mean(dat$`&var.`))
print(paste0("Mean of '&var.' is ", mn&v.))

#%end

This example calculates the mean for several variables in the “mtcars” sample data frame. The macro loops through a vector of variables and constructs a code block for each variable.

Here is the console output:

[1] "Mean of 'mpg' is 20.09"
[1] "Mean of 'disp' is 230.72"
[1] "Mean of 'hp' is 146.69"
[1] "Mean of 'drat' is 3.60"

And here is the generated code:

# Get data
dat <- mtcars

# Calculate mean for mpg
mn1 <- sprintf("%.2f", mean(dat$mpg))
print(paste0("Mean of 'mpg' is ", mn1))

# Calculate mean for disp
mn2 <- sprintf("%.2f", mean(dat$disp))
print(paste0("Mean of 'disp' is ", mn2))

# Calculate mean for hp
mn3 <- sprintf("%.2f", mean(dat$hp))
print(paste0("Mean of 'hp' is ", mn3))

# Calculate mean for drat
mn4 <- sprintf("%.2f", mean(dat$drat))
print(paste0("Mean of 'drat' is ", mn4))

Notice that the macro functionality can be used to create variable names dynamically. The macro system can also produce comments and other statements dynamically. Because the macro language is a text replacement language, any part of the code is available for manipulation.

top

User-Defined Macro Functions

Syntax

#%macro <name>([<param1>, <param2> = <default>, ...])
#%mend

Where “<name>” is the name of the user-defined function, “<param1>”, “<param2>”, etc. are zero or more parameters, and “<default>” is an optional default value. Between the “#%macro” and “#%mend” commands are one or more statements that comprise the function.

Details

A user-defined macro function is essentially a code insertion function. Once the macro function is defined, you can call it as many times as needed. For each call, the pre-processor will replace the call with the code inside the macro function definition. This behavior is different from a normal R function, which only returns the result of the function. Keep this difference in mind, as it can influence which kind of function you choose for different contexts.

When the macro pre-processor encounters a macro function definition, it performs three steps. First, the pre-processor will parse the #%macro definition and the parameters. Then the pre-processor will scan the code until the #%mend is reached. Lastly, both the parameters and the code are stored in the symbol table for later use.

When the function is called, the pre-processor first looks up the function in the symbol table. The pre-processor then matches the called parameters to the defined parameters, and ensures basic compliance. A #%let statement is generated for each parameter to assign the macro variables internal to the function. Then the contents of the function is inserted into the output code file at the point the call was made.

Note that macro functions have a scope. Macro variables defined inside the macro function will not be available outside the function. Macro variables assigned outside the function will be available inside the function. If a macro variable inside the function is defined with the same name as a macro variable defined outside the function, a new variable is created within the scope of the macro function.

Macro functions can be created inside other macro functions. The scope of the inner function will be nested inside the outer function. Any macro variables not passed into the inner function will be looked up first in the outer function, and then in the global environment.

Examples

First, let’s declare a simple function, just to see how the macro function definitions work:

#%macro printit(txt)
print("Print the text: &txt.")
#%mend
#%printit(Hello!)

The console output for the above code is this:

> msource()
---------
print("Print the text: Hello!")
---------
[1] "Print the text: Hello!"

Note that the macro definition and call have both been removed. Only the code inside the macro function has been emitted.

Here is a more interesting example:

library(dplyr)

#%macro print_analysis(dat, byvar, var)
cat("Analysis for &var.\n")
`&dat` |>
  select(`&byvar`, `&var`) |>
  group_by(`&byvar`) |>
  summarize(Mean = mean(`&var`, rm.na = TRUE),
            SD = sd(`&var`),
            Quantile = quantile(`&var`, probs = .25, rm.na = TRUE)) |>
  as.data.frame() |> print()
cat("\n")

#%mend

#%let vars <- c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width")
#%do idx = 1 %to %sysfunc(length(&vars))
#%print_analysis(iris, Species, %sysfunc(&vars[&idx]))
#%end

The above example loops through some variables on the iris dataset and prints an analysis for each to the console. The macro function print_analysis() uses the dplyr package, and accepts parameters for the dataset name, by variable, and analysis variable.

Here is the console output:

Analysis for Sepal.Length
     Species  Mean        SD Quantile
1     setosa 5.006 0.3524897    4.800
2 versicolor 5.936 0.5161711    5.600
3  virginica 6.588 0.6358796    6.225

Analysis for Sepal.Width
     Species  Mean        SD Quantile
1     setosa 3.428 0.3790644    3.200
2 versicolor 2.770 0.3137983    2.525
3  virginica 2.974 0.3224966    2.800

Analysis for Petal.Length
     Species  Mean        SD Quantile
1     setosa 1.462 0.1736640      1.4
2 versicolor 4.260 0.4699110      4.0
3  virginica 5.552 0.5518947      5.1

Analysis for Petal.Width
     Species  Mean        SD Quantile
1     setosa 0.246 0.1053856      0.2
2 versicolor 1.326 0.1977527      1.2
3  virginica 2.026 0.2746501      1.8

And here is the generated code:

library(dplyr)

cat("Analysis for Sepal.Length\n")
iris |>
  select(Species, Sepal.Length) |>
  group_by(Species) |>
  summarize(Mean = mean(Sepal.Length, rm.na = TRUE),
            SD = sd(Sepal.Length),
            Quantile = quantile(Sepal.Length, probs = .25, rm.na = TRUE)) |>
  as.data.frame() |> print()
cat("\n")

cat("Analysis for Sepal.Width\n")
iris |>
  select(Species, Sepal.Width) |>
  group_by(Species) |>
  summarize(Mean = mean(Sepal.Width, rm.na = TRUE),
            SD = sd(Sepal.Width),
            Quantile = quantile(Sepal.Width, probs = .25, rm.na = TRUE)) |>
  as.data.frame() |> print()
cat("\n")

cat("Analysis for Petal.Length\n")
iris |>
  select(Species, Petal.Length) |>
  group_by(Species) |>
  summarize(Mean = mean(Petal.Length, rm.na = TRUE),
            SD = sd(Petal.Length),
            Quantile = quantile(Petal.Length, probs = .25, rm.na = TRUE)) |>
  as.data.frame() |> print()
cat("\n")

cat("Analysis for Petal.Width\n")
iris |>
  select(Species, Petal.Width) |>
  group_by(Species) |>
  summarize(Mean = mean(Petal.Width, rm.na = TRUE),
            SD = sd(Petal.Width),
            Quantile = quantile(Petal.Width, probs = .25, rm.na = TRUE)) |>
  as.data.frame() |> print()
cat("\n")

A macro like this is highly re-useable. You can apply it to any dataset, and any set of variables. If desired, you could put macro conditions inside the macro function to handle different scenarios, such as the absence of a grouping variable.

Note that you can use the macro capabilities to construct tidyverse statements dynamically. This feature is convenient because tidyverse statements make heavy use of non-standard evaluation, which sometimes make them difficult to use inside functions, loops, or other dynamic situations.

top

Macro Line Continuation

Syntax

#%>  <code>

Where “<code>” is a segment of macro code continued from the previous line. The line continuation is used in situations where the macro command cannot fit on a single line, or is inconvenient to do so.

Details

Sometimes it is necessary to wrap a macro call over several lines. You may have a macro variable with a vector of several values, or a user-defined macro function with several parameters. The macro line continuation allows you to spread that call over several lines in the editor, and still execute the call as expected.

Examples

The below example shows a macro assignment with a vector of name/value pairs. In such a scenario, the macro line continuation operator makes the code much more readable:

#%let trt_grps <- c("ARM A" = "Placebo",
#%>                 "ARM B" = "Drug 10mg",
#%>                 "ARM C" = "Drug 20mg",
#%>                 "ARM D" = "Competitor")

a <- `&trt_grps`
print(a)

The generated code for the above macro is as follows:

a <- c("ARM A" = "Placebo",
                 "ARM B" = "Drug 10mg",
                 "ARM C" = "Drug 20mg",
                 "ARM D" = "Competitor")
print(a)

And the printed result is this:

       ARM A        ARM B        ARM C        ARM D 
   "Placebo"  "Drug 10mg"  "Drug 20mg" "Competitor" 

As you can see, the trt_grps vector was assigned normally, replaced as assigned, and can be used as you would expect.

top

Next: Symbol Table Functions