This example illustrates the use of the macro package. The program produces a change and percent of change from baseline table for a treatment response score. The table includes t and p values to determine whether or not that change is significant. The table layout is interesting in that the same statistics are generated for different population groups, and the spanning header text is different for each page of the report. These requirements make this report a perfect use case for the SASSY macro language.
Note the following about this example:
create_table() function is called twice inside a
macro “%do” loop. The header values are different for each page. Both
pages are added to the report.To execute this report, save the code to a file and run it using the
msource() function from the macro package,
like this: msource(<mypath>). See the macro package
documentation for further details.
library(sassy)
#% Assign macro variables
#%let basename <- %sysfunc(gsub("\\\\", "/", tempdir()))
#%let Protocol <- Study A
# Get path to sample data
pkg <- system.file("extdata", package = "sassy")
# Macros ------------------------------------------------------------------
#% A Macro to sort several data sets by the same sort variables
#% data sets must reside in the same library
#%macro sortdsn(libname=, datasets=, sortvars=);
#%loop through all the data sets and sort them by common sort variables
#%do x = 1 %to %sysfunc(length(&datasets.))
#%let dsn <- %sysfunc(&datasets[&x])
# Sort &dsn. dataset
#%if (&libname == "")
#%let ldsn <- &dsn
#%else
#%let ldsn <- &libname.$&dsn
#%end
proc_sort(data=`&ldsn.`,
by = `&sortvars`) -> `&dsn.`
#%end
#%mend sortdsn
# Program -----------------------------------------------------------------
options("logr.autolog" = TRUE,
"logr.notes" = FALSE,
"procs.print" = FALSE)
# Open log
lf <- log_open()
put("Load data")
libname(madam, pkg, "RData")
sep("Create formats")
put("Create format catalog")
fc <- fcat(N = '%2.f',
MEAN = '%8.2f',
STDERR = '%8.3f',
MEDIAN = '%8.2f',
MIN = '%8.1f',
MAX = '%8.1f')
sep("BEGIN SECTION TO OBTAIN COUNTS AND PERCENTS AND FORMAT ACCORDINGLY")
#% macro to obtain the counts and p-values by each subset
#%macro statfreq(pop=, poplab=, locf=);
put("POP = &pop.")
put("LOCF = &locf.")
put("subset the source data set")
proc_sort(data=madam$adaapasi,
where = expression(`&pop.` == 'Y' & DTYPE %in% c(NA, "&locf.") &
PARAMCD == 'PASISCOR' & AVISITN == 12 & ANL01FL == 'Y'),
by = TRTA) -> adaapasi
put("obtain the summary stats for change and percent change from baseline")
proc_means(data=adaapasi,
stats = c("n", "mean", "stderr", "median", "min", "max"),
options = c("nway", "notype", "nofreq"),
class = c("TRTA", "TRTAN"),
var = c("CHG", "PCHG"),
where = expression(!is.na(TRTA))) -> means&pop&locf
put("Format the summary stats for output")
datastep(means&pop&locf, format = fc,
rename = c("CLASS1" = "TRTA", "CLASS2" = "source"),
{
POP = "&pop."
if (VAR == "CHG")
label <- "Change from BL"
else
label <- "% Change from BL"
}) -> means2&pop&locf
put("Sort data for analysis")
proc_sort(data=adaapasi,
by = "TRTA",
order = "descending") -> adaapasi
put("loop through all comparator treatments to calculate the Student T Test" %p%
"want ttest and CI based on TREAT X - TREAT A")
#%do i = 2 %to 4
put("Run ttest #&i")
proc_ttest(data = adaapasi,
order = "data",
plots = TRUE,
class = "TRTA",
var = c("CHG", "PCHG"),
where = expression(TRTAN %in% c(1, `&i.`))) -> tt_&pop&locf.&i
put("Format Confidence Limits #&i")
datastep(tt_&pop&locf.&i$ConfLimits,
subset = expression(METHOD == "Pooled" & CLASS == "Diff (1-2)"),
keep = c("METHOD", "CLASS", "VAR", "source", "LCLM", "UCLM", "ci_95"),
{
source <- `&i`
ci_95 <- paste0("(", fapply(LCLM, "%.2f"), "; ",
fapply(UCLM, "%.2f"), ")")
}) -> ci&pop&locf&i
put("Format t-test and p-value #&i")
datastep(tt_&pop&locf.&i$TTests, subset = expression(METHOD == "Pooled"),
keep = c("METHOD", "VAR", "source", "TVAL", "PVAL"),
{
source <- `&i`
TVAL <- fapply(`T`, "%.2f")
PVAL <- fapply(PROBT, "%.3f")
}) -> tt&pop&locf&i
#%end
# Bind everything together
ci&pop&locf <- rbind(ci&pop&locf2, ci&pop&locf3, ci&pop&locf4)
tt&pop&locf <- rbind(tt&pop&locf2, tt&pop&locf3, tt&pop&locf4)
#%sortdsn(libname="", datasets=c("ci&pop&locf", "tt&pop&locf"),
#%> sortvars=c("VAR", "source"))
put("combine the 95% CI and Ttest")
datastep(ci&pop&locf, merge = tt&pop&locf[, c("TVAL", "PVAL")],
{
if (source == "2")
trtcomp <- "ARM B-ARM A"
else if (source == "3")
trtcomp <- "ARM C-ARM A"
else if (source == "4")
trtcomp <- "ARM D-ARM A"
TRTA <- substr(trtcomp, 1, 5)
if (VAR == "CHG")
label <- "Change from BL"
else
label <- "% Change from BL"
}) -> ci_tt&pop&locf
#% Sort means2 dataset
#%sortdsn(libname="", datasets=c("means2&pop&locf", "ci_tt&pop&locf"),
#%> sortvars=c("TRTA", "label"))
put("combine all into one data set")
datastep(means2&pop&locf, merge = ci_tt&pop&locf,
merge_by = c("VAR", "source", "TRTA", "label"),
{}) -> all&pop&locf
#%sortdsn(libname="", datasets="all&pop&locf", sortvars=c("VAR", "source"))
#%mend statfreq;
put("obtain treatment counts for per protocol and modified intent-to-treat")
proc_freq(madam$adsl,
where = expression(PPROTFL == "Y"),
tables = "TRT01A*PPROTFL") -> pprot
proc_freq(madam$adsl,
where = expression(MITTFL == "Y"),
tables = "TRT01A*MITTFL") -> mitt
#% Calculate statistics for each population
#%statfreq(pop=PPROTFL, poplab=Per Protocol Population)
#%statfreq(pop=MITTFL, poplab=Modified Intent-to-Treat)
#%statfreq(pop=MITTFL, poplab=Modified Intent-to-Treat, locf=LOCF)
put("set up flags")
allPPROTFL$inds <- "a"
allMITTFL$inds <- "b"
allMITTFLLOCF$inds <- "c"
put("Combine all the population results into one data set")
datastep(allPPROTFL, set = list(allMITTFL, allMITTFLLOCF),
keep = c("label", "VAR", "POP", "TRTA", "TRTAN", "N", "MEAN", "STDERR",
"MEDIAN", "MIN", "MAX", "trtcomp",
"TVAL", "PVAL", "ci_95",
"inds", "order", "page"),
{
if (inds %in% c("a", "c")) {
order <- 1
} else {
order <- 2
}
if (inds %in% c("a", "b")) {
page <- 1
} else {
page <- 2
}
}) -> all
put("Put data in correct order for display")
#%sortdsn(libname="", datasets="all", sortvars=c("page", "VAR", "order", "TRTA"))
put("Manually blank out pop value")
datastep(all, by = c("VAR", "inds"), sort_check = FALSE,
drop = c("VAR", "inds", "order"),
{
if (!first.VAR & !first.inds) {
POP <- ""
}
}) -> final
sep("END SECTION TO OBTAIN COUNTS AND PERCENTS AND FORMAT ACCORDINGLY")
sep("BEGIN SECTION TO PRODUCE OUTPUT")
put("Create format for population")
popfmt <- value(condition(x == "PPROTFL", "Per Protocol Population"),
condition(x == "MITTFL", "Modified Intent to Treat"),
condition(x == "MITTFL1", "Modified Intent to Treat"),
condition(TRUE, ""))
# write.csv(final, file = "./Mentor/data/final.csv")
# This is a two-page report, where the headers on the pages are not the same.
# So we create two tables, one for each page, and add them as two pieces of
# content. Spanning header is changed dynamically in the macro loop.
put("Create table for reporting")
#%do page = 1 %to 2
tbl&page. <- create_table(final[final$page == `&page`, ], borders = "all") |>
#%if (&page == 1)
spanning_header(from = label, to = MAX, label = "Based on Observed Cases") |>
#%else
spanning_header(from = label, to = MAX, label = "Based on LOCF") |>
#%end
spanning_header(from = trtcomp, to = ci_95, label = "Student's T-Test") |>
define(label, align = "left", width = 1, label = "Variable", dedupe = TRUE) |>
define(POP, align = "left", width = 1.2, label = "Population", format = popfmt) |>
define(TRTA, align = "left", width = .8, label = "Treatment") |>
define(N, align = "right", width = .3, label = "N") |>
define(MEAN, align = "right", width = .5, label = "Mean") |>
define(STDERR, align = "right", width = .5, label = "SEM") |>
define(MEDIAN, align = "right", width = .6, label = "Median") |>
define(MIN, align = "right", width = .5, label = "Min") |>
define(MAX, align = "right", width = .5, label = "Max") |>
define(trtcomp, align = "left", width = 1, label = "Treatment\nComparison") |>
define(TVAL, align = "right", width = .6, label = "T value") |>
define(PVAL, align = "right", width = .7, label = "P-value") |>
define(ci_95, align = "center", width = .8, label = "95% CI") |>
define(page, visible = FALSE, page_break = TRUE)
#%end
put("Create report object")
rpt <- create_report("&basename/output/example17.rtf", font = "Times",
font_size = 8, output_type = "RTF") |>
set_margins(top = 1, bottom = .8, left = .8, right = .8) |>
add_content(tbl1) |>
add_content(tbl2) |>
titles("Table 3.2.1",
"Analysis of Change and Percent Change from Baseline to Week 12 in AAPASI *",
"&Protocol", header = TRUE) |>
footnotes("", '(Page [pg] of [tpg])', columns = 3, footer = TRUE) |>
footnotes("Date Produced: %sysfunc(Sys.Date(),date7.) %sysfunc(Sys.time(), time5.); SAS Program: Table3_2_1.sas",
paste("* AAPASI is a modification from the traditional PASI including",
"the study drug treated psoriatic lesions only. The potential AAPASI scores range",
"from 0 (no psoriasis) to 72 (max severity)"), columns = 1, footer = TRUE)
put("Write out the report")
res <- write_report(rpt)
sep("END SECTION TO PRODUCE OUTPUT")
# Clean up ----------------------------------------------------------------
# Close log
log_close()
# Uncomment to view files
# file.show(res$path)
# file.show(lf)