# Complete Example 2

## Program

Here is another example of a complete program that demonstrates several of the key functions in the libr package. The example also shows how the package integrates with sassy.

    library(sassy)

    options("logr.autolog" = TRUE,
            "logr.notes" = FALSE)

    # Get temp location for log and report output
    tmp <- tempdir()

    # Open log
    lf <- log_open(file.path(tmp, "example1.log"))

    # Prepare Data ------------------------------------------------------------

    sep("Prepare Data")

    # Get path to sample data
    pkg <- system.file("extdata", package = "libr")

    # Create libname for csv data
    libname(sdtm, pkg, "csv", quiet = TRUE)

    put("Join and prepare data")

    put("Join DM to VS and keep desired columns")
    datastep(sdtm$DM, merge = sdtm$VS, merge_by = USUBJID,
             keep = v(USUBJID, VSTESTCD, VISIT, VISITNUM, VSSTRESN, ARM, VSBLFL),
             where = expression(VSTESTCD %in% c("PULSE", "RESP", "TEMP", "DIABP", "SYSBP") &
                                !(VISIT == "SCREENING" & VSBLFL != "Y")), {}) -> dm_joined

    put("Sort by variables")
    proc_sort(dm_joined, by = v(USUBJID, VSTESTCD, VISITNUM)) -> dm_sorted

    put("Differentiate baseline from treated vital signs")
    datastep(dm_sorted, by = v(USUBJID, VSTESTCD),
             retain = list(BSTRESN = 0), {

      # Combine treatment groups
      # And distinguish baseline time points
      if (ARM == "ARM A") {
        if (VSBLFL %eq% "Y") {
          GRP <- "A_BASE"
        } else {
          GRP <- "A_TRT"
        }
      } else {
        if (VSBLFL %eq% "Y") {
          GRP <- "O_BASE"
        } else {
          GRP <- "O_TRT"
        }
      }

      # Populate baseline value
      if (first.)
        BSTRESN = VSSTRESN

    }) -> prep

    put("Get population counts")
    pop_A <- subset(prep, GRP == "A_BASE", v(USUBJID, GRP)) |>
      proc_sort(options = nodupkey) |>
      proc_freq(tables = GRP,
                options = v(nocum, nonobs, nopercent),
                output = long) |>
      subset(select = "A_BASE", drop = TRUE)

    pop_O <- subset(prep, GRP == "O_BASE", v(USUBJID, GRP)) |>
      proc_sort(options = nodupkey) |>
      proc_freq(tables = GRP,
                options = v(nocum, nonobs, nopercent),
                output = long) |>
      subset(select = "O_BASE", drop = TRUE)

    # Prepare formats ---------------------------------------------------------

    sep("Prepare formats")

    put("Vital sign lookup format")
    vs_fmt <- c(PULSE = "Pulse",
                TEMP = "Temperature °C",
                RESP = "Respirations/min",
                SYSBP = "Systolic Blood Pressure",
                DIABP = "Diastolic Blood Pressure") |> put()

    put("Statistics lookup format")
    stat_fmt <- c(MEANSTD = "Mean (SD)",
                  MEDIAN = "Median",
                  Q1Q3 = "Q1 - Q3",
                  MINMAX = "Min - Max") |> put()

    put("Create format catalog")
    fc <- fcat(MEAN = "%.1f",
               STD = "(%.2f)",
               MEDIAN = "%.1f",
               Q1 = "%.1f",
               Q3 = "%.1f",
               MIN = "%.1f",
               MAX = "%.1f")

    # Prepare final data frame ------------------------------------------------

    sep("Prepare final data")

    put("Calculate statistics and prepare final data frame")
    proc_means(prep, var = VSSTRESN, class = VSTESTCD, by = GRP,
               stats = v(mean, std, median, q1, q3, min, max),
               options = v(notype, nofreq, nway)) |>
      datastep(format = fc,
               drop = v(MEAN, STD, Q1, Q3, MIN, MAX, VAR),
               rename = c("CLASS" = "VAR"),
               {
                 MEANSTD <- fapply2(MEAN, STD)
                 Q1Q3 <- fapply2(Q1, Q3, sep = " - ")
                 MINMAX <- fapply2(MIN, MAX, sep = " - ")
               }) |>
      proc_transpose(id = BY, var = v(MEANSTD, MEDIAN, Q1Q3, MINMAX),
                     by = VAR, name = "LABEL") -> final

    put("Prepare factor for sorting")
    final$VAR <- factor(final$VAR, names(vs_fmt))

    put("Final sort")
    proc_sort(final, by = VAR) -> final

    # Create Report -----------------------------------------------------------
    sep("Create Report")

    # Define table object
    tbl <- create_table(final) |>
      spanning_header(A_BASE, A_TRT, "Placebo", n = pop_A) |>
      spanning_header(O_BASE, O_TRT, "Treated", n = pop_O) |>
      column_defaults(width = 1.25, align = "center") |>
      stub(c(VAR, LABEL), width = 2.5) |>
      define(VAR, "Vital Sign", format = vs_fmt,
             blank_after = TRUE, dedupe = TRUE, label_row = TRUE) |>
      define(LABEL, indent = .25, format = stat_fmt) |>
      define(A_BASE, "Baseline") |>
      define(A_TRT, "After Treatment") |>
      define(O_BASE, "Baseline") |>
      define(O_TRT, "After Treatment")

    # Define report object
    rpt <- create_report(file.path(tmp, "./output/example2.rtf"), output_type = "RTF",
                         font = "Times", font_size = 12) |>
      page_header("Sponsor: Company", "Study: ABC") |>
      titles("Table 4.0", "Selected Vital Signs", bold = TRUE) |>
      add_content(tbl, align = "center") |>
      page_footer(Sys.time(), "CONFIDENTIAL", "Page [pg] of [tpg]")

    # Write report to file system
    res <- write_report(rpt)

    # Clean Up ----------------------------------------------------------------
    sep("Clean Up")

    # Close log
    log_close()

    # View report
    # file.show(res$file_path)

    # View log
    # file.show(lf)

## Log

Here is the log from the above program:

    =========================================================================
    Log Path: C:/Users/dbosa/AppData/Local/Temp/RtmpiipZo8/log/example1.log
    Program Path: C:/packages/Testing/Example2.R
    Working Directory: C:/packages/Testing
    User Name: dbosa
    R Version: 4.3.1 (2023-06-16 ucrt)
    Machine: SOCRATES x86-64
    Operating System: Windows 10 x64 build 22621
    Base Packages: stats graphics grDevices utils datasets methods base Other
    Packages: tidylog_1.0.2 procs_1.0.3 reporter_1.4.2 libr_1.2.8 logr_1.3.5
    fmtr_1.6.1 common_1.1.0 sassy_1.2.1
    Log Start Time: 2023-11-17 13:03:47.835721
    =========================================================================

    =========================================================================
    Prepare Data
    =========================================================================

    # library 'sdtm': 8 items
    - attributes: csv not loaded
    - path: C:/Users/dbosa/AppData/Local/R/win-library/4.3/libr/extdata
    - items:
      Name Extension Rows Cols     Size        LastModified
    1   AE       csv  150   27  88.5 Kb 2023-09-09 22:45:51
    2   DA       csv 3587   18 528.2 Kb 2023-09-09 22:45:51
    3   DM       csv   87   24  45.5 Kb 2023-09-09 22:45:51
    4   DS       csv  174    9  34.1 Kb 2023-09-09 22:45:51
    5   EX       csv   84   11  26.4 Kb 2023-09-09 22:45:51
    6   IE       csv    2   14  13.4 Kb 2023-09-09 22:45:51
    7   SV       csv  685   10  70.3 Kb 2023-09-09 22:45:51
    8   VS       csv 3358   17 467.4 Kb 2023-09-09 22:45:51

    Join and prepare data

    Join DM to VS and keep desired columns

    datastep: columns decreased from 24 to 7

    # A tibble: 2,768 × 7
       USUBJID    VSTESTCD VISIT   VISITNUM VSSTRESN ARM   VSBLFL
       <chr>      <chr>    <chr>      <dbl>    <dbl> <chr> <chr>
     1 ABC-01-049 DIABP    DAY 1          1       76 ARM D Y
     2 ABC-01-049 DIABP    WEEK 2         2       66 ARM D <NA>
     3 ABC-01-049 DIABP    WEEK 4         4       84 ARM D <NA>
     4 ABC-01-049 DIABP    WEEK 6         6       68 ARM D <NA>
     5 ABC-01-049 DIABP    WEEK 8         8       80 ARM D <NA>
     6 ABC-01-049 DIABP    WEEK 12       12       70 ARM D <NA>
     7 ABC-01-049 DIABP    WEEK 16       16       70 ARM D <NA>
     8 ABC-01-049 PULSE    DAY 1          1       84 ARM D Y
     9 ABC-01-049 PULSE    WEEK 2         2       84 ARM D <NA>
    10 ABC-01-049 PULSE    WEEK 4         4       76 ARM D <NA>
    # ℹ 2,758 more rows
    # ℹ Use `print(n = ...)` to see more rows

    Sort by variables

    proc_sort: input data set 2768 rows and 7 columns
               by: USUBJID VSTESTCD VISITNUM
               keep: USUBJID VSTESTCD VISIT VISITNUM VSSTRESN ARM VSBLFL
               order: a a a
               output data set 2768 rows and 7 columns

    # A tibble: 2,768 × 7
       USUBJID    VSTESTCD VISIT   VISITNUM VSSTRESN ARM   VSBLFL
       <chr>      <chr>    <chr>      <dbl>    <dbl> <chr> <chr>
     1 ABC-01-049 DIABP    DAY 1          1       76 ARM D Y
     2 ABC-01-049 DIABP    WEEK 2         2       66 ARM D <NA>
     3 ABC-01-049 DIABP    WEEK 4         4       84 ARM D <NA>
     4 ABC-01-049 DIABP    WEEK 6         6       68 ARM D <NA>
     5 ABC-01-049 DIABP    WEEK 8         8       80 ARM D <NA>
     6 ABC-01-049 DIABP    WEEK 12       12       70 ARM D <NA>
     7 ABC-01-049 DIABP    WEEK 16       16       70 ARM D <NA>
     8 ABC-01-049 PULSE    DAY 1          1       84 ARM D Y
     9 ABC-01-049 PULSE    WEEK 2         2       84 ARM D <NA>
    10 ABC-01-049 PULSE    WEEK 4         4       76 ARM D <NA>
    # ℹ 2,758 more rows
    # ℹ Use `print(n = ...)` to see more rows

    Differentiate baseline from treated vital signs

    datastep: columns increased from 7 to 9

    # A tibble: 2,768 × 9
       USUBJID    VSTESTCD VISIT   VISITNUM VSSTRESN ARM   VSBLFL GRP    BSTRESN
       <chr>      <chr>    <chr>      <dbl>    <dbl> <chr> <chr>  <chr>    <dbl>
     1 ABC-01-049 DIABP    DAY 1          1       76 ARM D Y      O_BASE      76
     2 ABC-01-049 DIABP    WEEK 2         2       66 ARM D <NA>   O_TRT       76
     3 ABC-01-049 DIABP    WEEK 4         4       84 ARM D <NA>   O_TRT       76
     4 ABC-01-049 DIABP    WEEK 6         6       68 ARM D <NA>   O_TRT       76
     5 ABC-01-049 DIABP    WEEK 8         8       80 ARM D <NA>   O_TRT       76
     6 ABC-01-049 DIABP    WEEK 12       12       70 ARM D <NA>   O_TRT       76
     7 ABC-01-049 DIABP    WEEK 16       16       70 ARM D <NA>   O_TRT       76
     8 ABC-01-049 PULSE    DAY 1          1       84 ARM D Y      O_BASE      84
     9 ABC-01-049 PULSE    WEEK 2         2       84 ARM D <NA>   O_TRT       84
    10 ABC-01-049 PULSE    WEEK 4         4       76 ARM D <NA>   O_TRT       84
    # ℹ 2,758 more rows
    # ℹ Use `print(n = ...)` to see more rows

    Get population counts

    proc_sort: input data set 20 rows and 2 columns
               by: USUBJID GRP
               keep: USUBJID GRP
               order: a
               options: nodupkey
               output data set 20 rows and 2 columns

    # A tibble: 20 × 2
       USUBJID    GRP
       <chr>      <chr>
     1 ABC-01-051 A_BASE
     2 ABC-01-056 A_BASE
     3 ABC-02-034 A_BASE
     4 ABC-02-038 A_BASE
     5 ABC-02-109 A_BASE
     6 ABC-03-002 A_BASE
     7 ABC-03-006 A_BASE
     8 ABC-03-091 A_BASE
     9 ABC-04-075 A_BASE
    10 ABC-04-080 A_BASE
    11 ABC-04-126 A_BASE
    12 ABC-06-068 A_BASE
    13 ABC-06-069 A_BASE
    14 ABC-07-012 A_BASE
    15 ABC-07-016 A_BASE
    16 ABC-08-104 A_BASE
    17 ABC-08-106 A_BASE
    18 ABC-09-020 A_BASE
    19 ABC-09-023 A_BASE
    20 ABC-09-137 A_BASE

    proc_freq: input data set 20 rows and 2 columns
               tables: GRP
               output: long
               view: TRUE
               output: 1 datasets

    # A tibble: 1 × 3
      VAR   STAT  A_BASE
      <chr> <chr>  <dbl>
    1 GRP   CNT       20

    proc_sort: input data set 67 rows and 2 columns
               by: USUBJID GRP
               keep: USUBJID GRP
               order: a
               options: nodupkey
               output data set 67 rows and 2 columns

    # A tibble: 67 × 2
       USUBJID    GRP
       <chr>      <chr>
     1 ABC-01-049 O_BASE
     2 ABC-01-050 O_BASE
     3 ABC-01-052 O_BASE
     4 ABC-01-053 O_BASE
     5 ABC-01-054 O_BASE
     6 ABC-01-055 O_BASE
     7 ABC-01-113 O_BASE
     8 ABC-01-114 O_BASE
     9 ABC-02-033 O_BASE
    10 ABC-02-035 O_BASE
    # ℹ 57 more rows
    # ℹ Use `print(n = ...)` to see more rows

    proc_freq: input data set 67 rows and 2 columns
               tables: GRP
               output: long
               view: TRUE
               output: 1 datasets

    # A tibble: 1 × 3
      VAR   STAT  O_BASE
      <chr> <chr>  <dbl>
    1 GRP   CNT       67

    =========================================================================
    Prepare formats
    =========================================================================

    Vital sign lookup format

    Pulse
    Temperature °C
    Respirations/min
    Systolic Blood Pressure
    Diastolic Blood Pressure

    Statistics lookup format

    Mean (SD)
    Median
    Q1 - Q3
    Min - Max

    Create format catalog

    # A format catalog: 7 formats
    - $MEAN: type S, "%.1f"
    - $STD: type S, "(%.2f)"
    - $MEDIAN: type S, "%.1f"
    - $Q1: type S, "%.1f"
    - $Q3: type S, "%.1f"
    - $MIN: type S, "%.1f"
    - $MAX: type S, "%.1f"

    =========================================================================
    Prepare final data
    =========================================================================

    Calculate statistics and prepare final data frame

    proc_means: input data set 2768 rows and 9 columns
                by: GRP
                class: VSTESTCD
                var: VSSTRESN
                stats: mean std median q1 q3 min max
                view: TRUE
                output: 1 datasets

       CLASS     BY      VAR      MEAN        STD MEDIAN    Q1    Q3   MIN   MAX
    1  DIABP A_BASE VSSTRESN  77.15000 10.6537614   78.5  70.0  83.0  54.0  96.0
    2  PULSE A_BASE VSSTRESN  72.75000 10.0518393   72.0  66.0  76.0  60.0 103.0
    3   RESP A_BASE VSSTRESN  16.50000  2.5026302   16.0  16.0  18.0  12.0  20.0
    4  SYSBP A_BASE VSSTRESN 128.15000 16.7120347  125.5 117.0 140.0  98.0 161.0
    5   TEMP A_BASE VSSTRESN  36.52105  0.4171050   36.4  36.2  36.9  35.9  37.4
    6  DIABP  A_TRT VSSTRESN  77.08547  8.1182772   78.0  72.0  82.0  50.0  98.0
    7  PULSE  A_TRT VSSTRESN  74.58120 10.2201958   74.0  67.0  80.0  54.0 102.0
    8   RESP  A_TRT VSSTRESN  15.80342  3.4372185   16.0  16.0  18.0   8.0  24.0
    9  SYSBP  A_TRT VSSTRESN 130.43590 17.6304911  126.0 118.0 140.0  95.0 184.0
    10  TEMP  A_TRT VSSTRESN  36.45431  0.2857157   36.5  36.2  36.6  35.8  37.3
    11 DIABP O_BASE VSSTRESN  77.48485  8.1415221   78.0  70.0  82.0  61.0  95.0
    12 PULSE O_BASE VSSTRESN  73.55224  9.7051745   72.0  67.0  80.0  52.0 100.0
    13  RESP O_BASE VSSTRESN  15.97015  3.1477272   16.0  15.0  18.0   8.0  22.0
    14 SYSBP O_BASE VSSTRESN 126.68182 15.4672449  123.0 116.0 138.0 100.0 164.0
    15  TEMP O_BASE VSSTRESN  36.42239  0.5376213   36.4  36.2  36.6  35.3  39.8
    16 DIABP  O_TRT VSSTRESN  76.94286  9.2058374   78.0  70.0  84.0  50.0 104.0
    17 PULSE  O_TRT VSSTRESN  74.02279  9.8592694   72.0  66.0  80.0  50.0 109.0
    18  RESP  O_TRT VSSTRESN  15.44444  3.4312291   16.0  12.0  18.0   8.0  24.0
    19 SYSBP  O_TRT VSSTRESN 125.90571 15.1715962  124.0 115.0 135.0  82.0 180.0
    20  TEMP  O_TRT VSSTRESN  36.31057  0.3903231   36.3  36.1  36.5  34.4  38.2

    datastep: columns decreased from 10 to 6

         VAR     BY MEDIAN       MEANSTD          Q1Q3        MINMAX
    1  DIABP A_BASE   78.5  77.2 (10.65)   70.0 - 83.0   54.0 - 96.0
    2  PULSE A_BASE   72.0  72.8 (10.05)   66.0 - 76.0  60.0 - 103.0
    3   RESP A_BASE   16.0   16.5 (2.50)   16.0 - 18.0   12.0 - 20.0
    4  SYSBP A_BASE  125.5 128.2 (16.71) 117.0 - 140.0  98.0 - 161.0
    5   TEMP A_BASE   36.4   36.5 (0.42)   36.2 - 36.9   35.9 - 37.4
    6  DIABP  A_TRT   78.0   77.1 (8.12)   72.0 - 82.0   50.0 - 98.0
    7  PULSE  A_TRT   74.0  74.6 (10.22)   67.0 - 80.0  54.0 - 102.0
    8   RESP  A_TRT   16.0   15.8 (3.44)   16.0 - 18.0    8.0 - 24.0
    9  SYSBP  A_TRT  126.0 130.4 (17.63) 118.0 - 140.0  95.0 - 184.0
    10  TEMP  A_TRT   36.5   36.5 (0.29)   36.2 - 36.6   35.8 - 37.3
    11 DIABP O_BASE   78.0   77.5 (8.14)   70.0 - 82.0   61.0 - 95.0
    12 PULSE O_BASE   72.0   73.6 (9.71)   67.0 - 80.0  52.0 - 100.0
    13  RESP O_BASE   16.0   16.0 (3.15)   15.0 - 18.0    8.0 - 22.0
    14 SYSBP O_BASE  123.0 126.7 (15.47) 116.0 - 138.0 100.0 - 164.0
    15  TEMP O_BASE   36.4   36.4 (0.54)   36.2 - 36.6   35.3 - 39.8
    16 DIABP  O_TRT   78.0   76.9 (9.21)   70.0 - 84.0  50.0 - 104.0
    17 PULSE  O_TRT   72.0   74.0 (9.86)   66.0 - 80.0  50.0 - 109.0
    18  RESP  O_TRT   16.0   15.4 (3.43)   12.0 - 18.0    8.0 - 24.0
    19 SYSBP  O_TRT  124.0 125.9 (15.17) 115.0 - 135.0  82.0 - 180.0
    20  TEMP  O_TRT   36.3   36.3 (0.39)   36.1 - 36.5   34.4 - 38.2

    proc_transpose: input data set 20 rows and 6 columns
                    by: VAR
                    var: MEANSTD MEDIAN Q1Q3 MINMAX
                    id: BY
                    name: LABEL
                    output dataset 20 rows and 6 columns

         VAR   LABEL        A_BASE         A_TRT        O_BASE         O_TRT
    1  DIABP MEANSTD  77.2 (10.65)   77.1 (8.12)   77.5 (8.14)   76.9 (9.21)
    2  DIABP  MEDIAN          78.5          78.0          78.0          78.0
    3  DIABP    Q1Q3   70.0 - 83.0   72.0 - 82.0   70.0 - 82.0   70.0 - 84.0
    4  DIABP  MINMAX   54.0 - 96.0   50.0 - 98.0   61.0 - 95.0  50.0 - 104.0
    5  PULSE MEANSTD  72.8 (10.05)  74.6 (10.22)   73.6 (9.71)   74.0 (9.86)
    6  PULSE  MEDIAN            72            74            72            72
    7  PULSE    Q1Q3   66.0 - 76.0   67.0 - 80.0   67.0 - 80.0   66.0 - 80.0
    8  PULSE  MINMAX  60.0 - 103.0  54.0 - 102.0  52.0 - 100.0  50.0 - 109.0
    9   RESP MEANSTD   16.5 (2.50)   15.8 (3.44)   16.0 (3.15)   15.4 (3.43)
    10  RESP  MEDIAN            16            16            16            16
    11  RESP    Q1Q3   16.0 - 18.0   16.0 - 18.0   15.0 - 18.0   12.0 - 18.0
    12  RESP  MINMAX   12.0 - 20.0    8.0 - 24.0    8.0 - 22.0    8.0 - 24.0
    13 SYSBP MEANSTD 128.2 (16.71) 130.4 (17.63) 126.7 (15.47) 125.9 (15.17)
    14 SYSBP  MEDIAN         125.5         126.0         123.0         124.0
    15 SYSBP    Q1Q3 117.0 - 140.0 118.0 - 140.0 116.0 - 138.0 115.0 - 135.0
    16 SYSBP  MINMAX  98.0 - 161.0  95.0 - 184.0 100.0 - 164.0  82.0 - 180.0
    17  TEMP MEANSTD   36.5 (0.42)   36.5 (0.29)   36.4 (0.54)   36.3 (0.39)
    18  TEMP  MEDIAN          36.4          36.5          36.4          36.3
    19  TEMP    Q1Q3   36.2 - 36.9   36.2 - 36.6   36.2 - 36.6   36.1 - 36.5
    20  TEMP  MINMAX   35.9 - 37.4   35.8 - 37.3   35.3 - 39.8   34.4 - 38.2

    Prepare factor for sorting

    Final sort

    proc_sort: input data set 20 rows and 6 columns
               by: VAR
               keep: VAR LABEL A_BASE A_TRT O_BASE O_TRT
               order: a
               output data set 20 rows and 6 columns

         VAR   LABEL        A_BASE         A_TRT        O_BASE         O_TRT
    5  PULSE MEANSTD  72.8 (10.05)  74.6 (10.22)   73.6 (9.71)   74.0 (9.86)
    6  PULSE  MEDIAN            72            74            72            72
    7  PULSE    Q1Q3   66.0 - 76.0   67.0 - 80.0   67.0 - 80.0   66.0 - 80.0
    8  PULSE  MINMAX  60.0 - 103.0  54.0 - 102.0  52.0 - 100.0  50.0 - 109.0
    17  TEMP MEANSTD   36.5 (0.42)   36.5 (0.29)   36.4 (0.54)   36.3 (0.39)
    18  TEMP  MEDIAN          36.4          36.5          36.4          36.3
    19  TEMP    Q1Q3   36.2 - 36.9   36.2 - 36.6   36.2 - 36.6   36.1 - 36.5
    20  TEMP  MINMAX   35.9 - 37.4   35.8 - 37.3   35.3 - 39.8   34.4 - 38.2
    9   RESP MEANSTD   16.5 (2.50)   15.8 (3.44)   16.0 (3.15)   15.4 (3.43)
    10  RESP  MEDIAN            16            16            16            16
    11  RESP    Q1Q3   16.0 - 18.0   16.0 - 18.0   15.0 - 18.0   12.0 - 18.0
    12  RESP  MINMAX   12.0 - 20.0    8.0 - 24.0    8.0 - 22.0    8.0 - 24.0
    13 SYSBP MEANSTD 128.2 (16.71) 130.4 (17.63) 126.7 (15.47) 125.9 (15.17)
    14 SYSBP  MEDIAN         125.5         126.0         123.0         124.0
    15 SYSBP    Q1Q3 117.0 - 140.0 118.0 - 140.0 116.0 - 138.0 115.0 - 135.0
    16 SYSBP  MINMAX  98.0 - 161.0  95.0 - 184.0 100.0 - 164.0  82.0 - 180.0
    1  DIABP MEANSTD  77.2 (10.65)   77.1 (8.12)   77.5 (8.14)   76.9 (9.21)
    2  DIABP  MEDIAN          78.5          78.0          78.0          78.0
    3  DIABP    Q1Q3   70.0 - 83.0   72.0 - 82.0   70.0 - 82.0   70.0 - 84.0
    4  DIABP  MINMAX   54.0 - 96.0   50.0 - 98.0   61.0 - 95.0  50.0 - 104.0

    =========================================================================
    Create Report
    =========================================================================

    # A report specification: 1 pages
    - file_path: 'C:\Users\dbosa\AppData\Local\Temp\RtmpiipZo8/./output/example2.rtf'
    - output_type: RTF
    - units: inches
    - orientation: landscape
    - margins: top 0.5 bottom 0.5 left 1 right 1
    - line size/count: 9/38
    - page_header: left=Sponsor: Company right=Study: ABC
    - title 1: 'Table 4.0'
    - title 2: 'Selected Vital Signs'
    - page_footer: left=2023-11-17 13:03:53.809731 center=CONFIDENTIAL right=Page [pg] of [tpg]
    - content:
    # A table specification:
    - data: data.frame 'final' 20 rows 6 cols
    - show_cols: all
    - use_attributes: all
    - spanning_header: from='A_BASE' to='A_TRT' 'Placebo' level=1
    - spanning_header: from='O_BASE' to='O_TRT' 'Treated' level=1
    - stub: VAR LABEL width=2.5 align='left'
    - define: VAR 'Vital Sign' dedupe='TRUE'
    - define: LABEL
    - define: A_BASE 'Baseline'
    - define: A_TRT 'After Treatment'
    - define: O_BASE 'Baseline'
    - define: O_TRT 'After Treatment'

    =========================================================================
    Clean Up
    =========================================================================

    =========================================================================
    Log End Time: 2023-11-17 13:03:54.012811
    Log Elapsed Time: 0 00:00:06
    =========================================================================

## Output

And here is the output: