arframe TFL Gallery
  1. Tables
  2. Vital Signs
  • Getting Started
    • Installation

  • Tables
    • Study Conduct
    • Enrollment by Country and Site

    • Study Population
    • Demographics Summary
    • Medical History
    • Prior Medication
    • Disposition Summary
    • Analysis Populations

    • Extent of Exposure
    • Concomitant Medications
    • Extent of Exposure

    • Safety
    • Adverse Events by System Organ Class and Preferred Term
    • AEs Related to Study Drug
    • Common Adverse Events
    • Adverse Events by Grade / Intensity
    • Overall Safety Summary
    • Adverse Events with Event Counts
    • Exposure-Adjusted Adverse Events
    • Adverse Events by Subgroup
    • Serious Adverse Events by SOC and PT
    • AEs Leading to Study Drug Discontinuation
    • Death Summary
    • Vital Signs
    • Laboratory Results - Chemistry
    • Laboratory Shift Table
    • Laboratory Worst Toxicity Grade
    • Laboratory Marked Abnormalities
    • Electrocardiogram Summary

    • Efficacy
    • Time to Event Summary
    • Best Overall Response

  • Listings
    • Adverse Event Listing
    • Demographic Characteristics Listing
    • Medical History Listing
    • Vital Signs Listing
    • Laboratory Test Results Listing
    • Concomitant Medications Listing

  • Figures
    • Kaplan-Meier Plot
    • Swimmer Plot
    • Waterfall Plot

On this page

  • Setup
  • Data Preparation
  • arframe Pipeline
  • Rendered Table
  1. Tables
  2. Vital Signs

Vital Signs

Values and Change from Baseline by Visit

Setup

See Prerequisites for installation instructions.

library(arframe)
library(pharmaverseadam)
library(dplyr, warn.conflicts = FALSE)
library(tidyr)
library(cards)

# Safety population vitals (baseline + post-baseline analysis visits)
advs_saf <- pharmaverseadam::advs |>
  blank_to_na() |>
  filter(
    SAFFL   == "Y",
    TRT01A  != "Screen Failure",
    PARAMCD %in% c("SYSBP", "DIABP", "PULSE", "TEMP"),
    ANL01FL == "Y" | AVISIT == "Baseline"
  )

# Arm N from ADSL
adsl_saf <- pharmaverseadam::adsl |>
  blank_to_na() |>
  filter(SAFFL == "Y", TRT01A != "Screen Failure")

arm_n      <- adsl_saf |> count(TRT01A) |> pull(n, name = TRT01A)
arm_levels <- c("Placebo", "Xanomeline Low Dose", "Xanomeline High Dose")

# Visit ordering (include Baseline)
visit_order <- c(
  "Baseline", "Week 2", "Week 4", "Week 6", "Week 8",
  "Week 12", "Week 16", "Week 20", "Week 24",
  "Week 26", "End of Treatment"
)

advs_saf <- advs_saf |>
  mutate(
    AVISIT = factor(AVISIT, levels = visit_order),
    TRT01A = factor(TRT01A, levels = arm_levels)
  ) |>
  filter(!is.na(AVISIT))

Data Preparation

  • dplyr
  • cards
# ── Wide layout: one row per Visit × Treatment ──
# Value stats: N, Mean, SD, Median, Min, Max
# Change from Baseline stats: N, Mean, SD, Median, Min, Max

fmt1 <- function(x) ifelse(is.na(x), "", sprintf("%.1f", x))
fmt0 <- function(x) ifelse(is.na(x), "", sprintf("%.0f", x))

vs_wide <- advs_saf |>
  group_by(PARAM, PARAMCD, AVISIT, TRT01A) |>
  summarise(
    val_n      = as.character(sum(!is.na(AVAL))),
    val_mean   = fmt1(mean(AVAL, na.rm = TRUE)),
    val_sd     = fmt1(sd(AVAL, na.rm = TRUE)),
    val_median = fmt1(median(AVAL, na.rm = TRUE)),
    val_min    = fmt0(min(AVAL, na.rm = TRUE)),
    val_max    = fmt0(max(AVAL, na.rm = TRUE)),
    chg_n      = if (all(is.na(CHG))) "" else as.character(sum(!is.na(CHG))),
    chg_mean   = if (all(is.na(CHG))) "" else fmt1(mean(CHG, na.rm = TRUE)),
    chg_sd     = if (all(is.na(CHG))) "" else fmt1(sd(CHG, na.rm = TRUE)),
    chg_median = if (all(is.na(CHG))) "" else fmt1(median(CHG, na.rm = TRUE)),
    chg_min    = if (all(is.na(CHG))) "" else fmt0(min(CHG, na.rm = TRUE)),
    chg_max    = if (all(is.na(CHG))) "" else fmt0(max(CHG, na.rm = TRUE)),
    .groups    = "drop"
  ) |>
  # Handle visits with no CHG data (Baseline)
 mutate(
    across(starts_with("chg_"), ~ ifelse(.x %in% c("NaN", "NA", "Inf", "-Inf"), "", .x))
  ) |>
  arrange(PARAM, AVISIT, TRT01A) |>
  mutate(across(where(is.factor), as.character))
vs_data <- advs_saf |>
  mutate(across(where(is.factor), as.character))

vs_ard <- ard_stack(
  data  = vs_data,
  .by   = c("TRT01A", "PARAM", "AVISIT"),
  ard_continuous(
    variables = c("AVAL", "CHG"),
    statistic = ~ continuous_summary_fns(c("N", "mean", "sd", "median", "min", "max"))
  ),
  .overall = FALSE
) |>
  filter(variable %in% c("AVAL", "CHG"))

# Pivot to wide: one row per Visit × Treatment
vs_cards <- vs_ard |>
  mutate(
    stat_val = as.numeric(unlist(stat)),
    stat_name = as.character(stat_name),
    var = as.character(variable),
    arm = as.character(unlist(group1_level)),
    param = as.character(unlist(group2_level)),
    visit = as.character(unlist(group3_level))
  ) |>
  mutate(
    col_name = paste0(
      ifelse(var == "AVAL", "val_", "chg_"),
      stat_name
    )
  ) |>
  select(param, visit, arm, col_name, stat_val) |>
  pivot_wider(names_from = col_name, values_from = stat_val) |>
  mutate(across(where(is.numeric), ~ ifelse(is.na(.) | is.nan(.) | is.infinite(.), NA_real_, .)))

arframe Pipeline

The rendered table below uses the dplyr data prep (vs_wide). The cards tab produces an equivalent vs_cards — swap it in to use the cards path instead.

Each row is a treatment group at a visit, with Value and Change from Baseline statistics as columns:

vs_wide |>
  fr_table() |>
  fr_titles(
    "Table 14.3.9",
    "Summary of Vital Signs: Values and Change from Baseline",
    "Safety Population"
  ) |>
  fr_cols(
    PARAMCD    = fr_col(visible = FALSE),
    PARAM      = fr_col(visible = FALSE),
    AVISIT     = fr_col(visible = FALSE),
    TRT01A     = fr_col("", width = 1.5),
    val_n      = fr_col("N",      align = "decimal", width = 0.5),
    val_mean   = fr_col("Mean",   align = "decimal", width = 0.7),
    val_sd     = fr_col("SD",     align = "decimal", width = 0.7),
    val_median = fr_col("Median", align = "decimal", width = 0.7),
    val_min    = fr_col("Min",    align = "decimal", width = 0.5),
    val_max    = fr_col("Max",    align = "decimal", width = 0.5),
    chg_n      = fr_col("N",      align = "decimal", width = 0.5),
    chg_mean   = fr_col("Mean",   align = "decimal", width = 0.7),
    chg_sd     = fr_col("SD",     align = "decimal", width = 0.7),
    chg_median = fr_col("Median", align = "decimal", width = 0.7),
    chg_min    = fr_col("Min",    align = "decimal", width = 0.5),
    chg_max    = fr_col("Max",    align = "decimal", width = 0.5)
  ) |>
  fr_spans(
    "Value" = c("val_n", "val_mean", "val_sd", "val_median", "val_min", "val_max"),
    "Change from Baseline" = c("chg_n", "chg_mean", "chg_sd", "chg_median", "chg_min", "chg_max"),
    .gap = FALSE
  ) |>
  fr_header(bold = TRUE, align = "center") |>
  fr_rows(
    group_by    = list(cols = "AVISIT", label = "TRT01A"),
    blank_after = "AVISIT",
    page_by     = "PARAM",
    group_style = list(bold = TRUE)
  ) |>
  fr_footnotes(
    "Value = observed value at visit; Change = post-baseline value minus baseline.",
    "SD = Standard Deviation.",
    "CDISCPILOT01 Safety Population."
  )

Rendered Table

Table 14.3.9
Summary of Vital Signs: Values and Change from Baseline
Safety Population
Diastolic Blood Pressure (mmHg)
ValueChange from Baseline
NMeanSDMedianMinMaxNMeanSDMedianMinMax
Baseline
Placebo340 77.110.7 77.7 40110
Xanomeline Low Dose384 76.6 9.8 76.7 48108
Xanomeline High Dose288 78.210.3 78.8 51108
Week 2
Placebo332 74.610.1 74.0 45100249 -2.4 9.2 -2.030
Xanomeline Low Dose300 76.9 9.7 76.7 55102225 -0.1 8.9 0.032
Xanomeline High Dose286 76.3 9.7 76.0 43101214 -1.910.7 0.026
Week 4
Placebo312 75.311.5 76.0 39100234 -1.8 9.9 -2.030
Xanomeline Low Dose260 75.9 9.2 76.5 50100195 -0.0 9.1 0.028
Xanomeline High Dose268 77.1 9.5 79.0 54100201 -1.311.2 -1.026
Week 6
Placebo296 74.310.0 74.0 48110222 -3.2 9.6 -2.026
Xanomeline Low Dose236 74.7 9.3 76.0 50100177 -1.1 8.6 0.034
Xanomeline High Dose224 75.5 9.6 76.3 52 98168 -3.3 9.9 -3.018
Week 8
Placebo284 75.1 9.2 76.0 49101213 -2.1 9.1 -2.024
Xanomeline Low Dose216 75.610.6 74.0 52100162 -0.410.8 0.034
Xanomeline High Dose204 77.0 9.2 78.0 54 98153 -1.6 9.7 0.020
Week 12
Placebo272 74.110.8 72.8 49104204 -3.0 9.2 -2.520
Xanomeline Low Dose164 76.111.5 78.3 50100123 -0.710.7 0.034
Xanomeline High Dose164 74.9 9.3 76.0 56 92123 -3.410.9 -2.020
Week 16
Placebo268 75.011.0 76.0 49 98201 -1.9 9.7 0.024
Xanomeline Low Dose128 75.610.3 76.0 55 98 96 -1.3 9.1 -2.520
Xanomeline High Dose132 75.8 8.6 76.0 50 92 99 -1.210.3 0.027
Week 20
Placebo248 72.810.2 72.0 48 98186 -2.9 9.4 -3.518
Xanomeline Low Dose108 73.9 9.0 74.3 52 98 81 -2.610.5 0.020
Xanomeline High Dose120 74.210.4 72.0 54 98 90 -2.911.3 -2.028
Week 24
Placebo236 73.811.7 74.0 44117177 -2.110.3 -3.041
Xanomeline Low Dose100 76.0 9.9 76.0 57 98 75 -0.6 8.0 0.018
Xanomeline High Dose112 75.410.0 76.0 50 98 84 -2.2 9.6 -4.022
Week 26
Placebo204 72.910.6 72.0 39 98153 -3.3 9.5 -4.024
Xanomeline Low Dose 84 73.310.2 72.3 56 92 63 -3.5 8.4 -2.022
Xanomeline High Dose 96 73.5 9.7 73.5 50 93 72 -3.610.3 -4.020
End of Treatment
Placebo222 74.410.7 73.5 49104222 -3.0 9.0 -2.020
Xanomeline Low Dose177 76.011.2 76.0 50100177 0.110.1 0.034
Xanomeline High Dose168 76.0 9.9 78.0 56 98168 -2.711.3 -2.020
Value = observed value at visit; Change = post-baseline value minus baseline.
SD = Standard Deviation.
CDISCPILOT01 Safety Population.
/opt/quarto/share/rmd/rmd.R 01APR2026 09:54:39
Table 14.3.9
Summary of Vital Signs: Values and Change from Baseline
Safety Population
Pulse Rate (beats/min)
ValueChange from Baseline
NMeanSDMedianMinMaxNMeanSDMedianMinMax
Baseline
Placebo340 73.511.6 72.3 51134
Xanomeline Low Dose384 72.110.8 70.0 50104
Xanomeline High Dose288 72.4 9.7 71.7 52100
Week 2
Placebo332 73.310.1 72.3 50106249 -0.111.4 0.042
Xanomeline Low Dose300 75.210.6 75.0 52116225 3.910.6 4.040
Xanomeline High Dose286 76.3 9.1 76.0 56102214 3.8 9.1 4.032
Week 4
Placebo312 74.110.7 74.7 50120234 0.5 8.9 0.030
Xanomeline Low Dose260 73.010.0 73.7 48100195 2.410.4 2.032
Xanomeline High Dose268 76.710.8 76.0 52114201 4.210.0 4.036
Week 6
Placebo296 72.710.1 72.0 53108222 -0.912.1 0.040
Xanomeline Low Dose236 72.1 9.0 72.0 52 96177 2.3 7.7 2.024
Xanomeline High Dose224 75.2 9.6 74.0 54100168 2.810.5 3.529
Week 8
Placebo284 71.9 9.1 72.0 52102213 -1.510.9 -2.030
Xanomeline Low Dose216 72.711.3 72.0 49104162 3.010.0 3.530
Xanomeline High Dose204 74.3 9.1 73.3 50104153 1.7 9.0 2.026
Week 12
Placebo272 75.111.4 74.8 51106204 1.710.9 0.036
Xanomeline Low Dose164 74.3 9.0 74.3 54 94123 3.911.2 2.028
Xanomeline High Dose164 74.1 9.7 76.0 50 98123 1.3 9.9 0.028
Week 16
Placebo268 70.6 8.8 70.2 50 90201 -2.910.0 -2.028
Xanomeline Low Dose128 69.210.1 68.7 48104 96 -1.810.6 -2.034
Xanomeline High Dose132 73.0 9.6 72.0 51 96 99 -0.412.1 0.032
Week 20
Placebo248 71.6 8.6 72.0 53 96186 -0.912.1 -2.026
Xanomeline Low Dose108 69.610.2 70.0 40 98 81 -2.011.0 0.020
Xanomeline High Dose120 76.911.2 80.0 52100 90 3.110.7 3.530
Week 24
Placebo236 71.6 9.0 72.0 50 92177 -1.110.3 0.024
Xanomeline Low Dose100 70.210.0 70.5 52 96 75 -2.4 9.2 -1.025
Xanomeline High Dose112 72.311.2 70.0 48 98 84 -2.411.9 -2.020
Week 26
Placebo204 73.410.8 74.0 50108153 0.712.1 0.044
Xanomeline Low Dose 84 69.8 8.7 68.0 53 92 63 -3.710.0 -4.024
Xanomeline High Dose 96 73.311.8 72.0 53100 72 -1.710.6 -2.024
End of Treatment
Placebo222 75.211.5 74.0 51106222 1.610.7 0.036
Xanomeline Low Dose177 74.1 9.4 75.0 50 94177 4.310.4 4.028
Xanomeline High Dose168 73.6 9.6 73.0 50 98168 1.2 8.9 0.028
Value = observed value at visit; Change = post-baseline value minus baseline.
SD = Standard Deviation.
CDISCPILOT01 Safety Population.
/opt/quarto/share/rmd/rmd.R 01APR2026 09:54:39
Table 14.3.9
Summary of Vital Signs: Values and Change from Baseline
Safety Population
Systolic Blood Pressure (mmHg)
ValueChange from Baseline
NMeanSDMedianMinMaxNMeanSDMedianMinMax
Baseline
Placebo340136.817.6136.3 80184
Xanomeline Low Dose384137.918.5138.0100194
Xanomeline High Dose288137.817.2138.0100192
Week 2
Placebo332134.117.4130.8 90173249 -3.014.4 -2.040
Xanomeline Low Dose300136.018.2133.7 96190225 -2.115.9 -2.040
Xanomeline High Dose286131.814.8131.0 96191214 -5.915.8 -6.040
Week 4
Placebo312133.717.7132.3 84202234 -3.115.9 -2.050
Xanomeline Low Dose260135.317.8134.0 95180195 -1.617.1 0.080
Xanomeline High Dose268132.615.9132.0 95181201 -5.816.1 -6.040
Week 6
Placebo296133.417.9130.0 80210222 -2.915.4 -2.048
Xanomeline Low Dose236134.018.4130.8 98195177 -2.714.1 -2.035
Xanomeline High Dose224130.516.0130.0 90174168 -8.617.4 -8.044
Week 8
Placebo284136.217.1136.5 90189213 -0.115.2 0.050
Xanomeline Low Dose216134.916.5133.5 92180162 -0.514.8 0.046
Xanomeline High Dose204134.315.4133.7 91198153 -3.716.2 -4.050
Week 12
Placebo272132.515.0132.0 78168204 -4.017.1 -3.534
Xanomeline Low Dose164131.715.9131.0 92170123 -2.414.4 -2.032
Xanomeline High Dose164129.515.3130.0100177123 -8.215.4 -8.030
Week 16
Placebo268134.418.4133.7 76190201 -1.919.3 -6.050
Xanomeline Low Dose128132.615.4130.0100168 96 -1.413.8 0.030
Xanomeline High Dose132132.514.2130.5102186 99 -5.117.2 -6.045
Week 20
Placebo248133.219.8131.5 80200186 -2.316.7 -2.058
Xanomeline Low Dose108129.815.9128.8100162 81 -3.014.1 -6.030
Xanomeline High Dose120126.715.7124.0 98185 90-10.818.6-10.545
Week 24
Placebo236134.717.7131.0 90199177 -1.915.8 -2.050
Xanomeline Low Dose100132.017.6131.5 92173 75 0.117.4 0.048
Xanomeline High Dose112131.218.7130.0 90198 84 -7.418.0 -8.042
Week 26
Placebo204129.918.0130.0 80176153 -6.217.7 -6.042
Xanomeline Low Dose 84129.918.9129.8 99173 63 -4.314.6 -4.032
Xanomeline High Dose 96122.614.7120.5 95179 72-14.221.4-18.034
End of Treatment
Placebo222132.715.4131.0 78172222 -3.617.8 -2.548
Xanomeline Low Dose177133.017.1130.0 92178177 -3.714.1 -4.032
Xanomeline High Dose168132.315.6131.0100177168 -6.715.0 -8.030
Value = observed value at visit; Change = post-baseline value minus baseline.
SD = Standard Deviation.
CDISCPILOT01 Safety Population.
/opt/quarto/share/rmd/rmd.R 01APR2026 09:54:39
Table 14.3.9
Summary of Vital Signs: Values and Change from Baseline
Safety Population
Temperature (C)
ValueChange from Baseline
NMeanSDMedianMinMaxNMeanSDMedianMinMax
Baseline
Placebo172 36.6 0.4 36.7 35 37
Xanomeline Low Dose190 36.5 0.4 36.6 35 37
Xanomeline High Dose144 36.6 0.4 36.6 36 37
Week 2
Placebo164 36.6 0.4 36.6 35 38164 -0.0 0.4 -0.0 1
Xanomeline Low Dose150 36.5 0.4 36.6 35 38150 -0.0 0.4 0.0 1
Xanomeline High Dose142 36.6 0.4 36.7 35 37142 0.0 0.4 0.0 1
Week 4
Placebo154 36.6 0.5 36.7 34 38154 0.0 0.4 0.0 1
Xanomeline Low Dose130 36.5 0.5 36.6 34 37130 -0.1 0.5 0.0 1
Xanomeline High Dose134 36.6 0.4 36.7 35 37134 0.1 0.3 0.0 1
Week 6
Placebo148 36.6 0.4 36.7 35 38148 0.0 0.4 0.0 1
Xanomeline Low Dose118 36.5 0.5 36.7 35 37118 -0.0 0.4 0.0 1
Xanomeline High Dose112 36.6 0.4 36.7 36 37112 0.0 0.4 0.0 1
Week 8
Placebo142 36.6 0.4 36.7 36 37142 0.0 0.4 0.0 1
Xanomeline Low Dose106 36.6 0.4 36.6 36 37106 0.0 0.4 0.0 1
Xanomeline High Dose102 36.6 0.4 36.7 36 37102 0.0 0.4 0.0 1
Week 12
Placebo136 36.6 0.4 36.8 35 37136 0.1 0.4 0.0 1
Xanomeline Low Dose 82 36.6 0.5 36.7 35 38 82 0.1 0.4 0.0 2
Xanomeline High Dose 82 36.7 0.4 36.8 36 37 82 0.0 0.3 -0.1 1
Week 16
Placebo134 36.7 0.3 36.7 36 37134 0.1 0.5 0.0 1
Xanomeline Low Dose 62 36.6 0.4 36.6 36 37 62 0.0 0.4 0.0 1
Xanomeline High Dose 66 36.6 0.4 36.7 36 37 66 -0.0 0.4 0.0 1
Week 20
Placebo120 36.6 0.5 36.7 35 38120 0.1 0.5 0.0 1
Xanomeline Low Dose 54 36.7 0.5 36.6 35 38 54 0.1 0.5 0.0 1
Xanomeline High Dose 60 36.7 0.5 36.7 35 38 60 0.0 0.4 0.0 1
Week 24
Placebo114 36.6 0.5 36.7 35 38114 0.1 0.5 0.1 1
Xanomeline Low Dose 48 36.6 0.4 36.8 36 37 48 0.1 0.4 0.0 1
Xanomeline High Dose 54 36.7 0.4 36.8 36 38 54 -0.0 0.5 -0.0 1
Week 26
Placebo102 36.7 0.4 36.7 35 38102 0.1 0.4 0.1 1
Xanomeline Low Dose 42 36.6 0.5 36.7 35 37 42 -0.0 0.5 0.1 1
Xanomeline High Dose 48 36.8 0.2 36.8 36 37 48 0.0 0.4 0.0 1
End of Treatment
Placebo 74 36.7 0.4 36.8 35 37 74 0.1 0.4 0.0 1
Xanomeline Low Dose 59 36.6 0.4 36.7 35 38 59 0.1 0.4 0.0 2
Xanomeline High Dose 56 36.6 0.4 36.7 36 37 56 0.1 0.4 0.0 1
Value = observed value at visit; Change = post-baseline value minus baseline.
SD = Standard Deviation.
CDISCPILOT01 Safety Population.
/opt/quarto/share/rmd/rmd.R 01APR2026 09:54:39
Source Code
---
title: "Vital Signs"
subtitle: "Values and Change from Baseline by Visit"
execute:
  echo: true
  eval: true
---


```{r}
#| label: prereqs
#| include: false
library(arframe)
fr_theme(hlines = "header", font_family = "Courier New")
blank_to_na <- function(df) {
  df[] <- lapply(df, function(x) {
    if (is.character(x)) x[x == ""] <- NA_character_
    x
  })
  df
}
```

## Setup

See [Prerequisites](../install.qmd) for installation instructions.

```{r}
#| label: setup
library(arframe)
library(pharmaverseadam)
library(dplyr, warn.conflicts = FALSE)
library(tidyr)
library(cards)

# Safety population vitals (baseline + post-baseline analysis visits)
advs_saf <- pharmaverseadam::advs |>
  blank_to_na() |>
  filter(
    SAFFL   == "Y",
    TRT01A  != "Screen Failure",
    PARAMCD %in% c("SYSBP", "DIABP", "PULSE", "TEMP"),
    ANL01FL == "Y" | AVISIT == "Baseline"
  )

# Arm N from ADSL
adsl_saf <- pharmaverseadam::adsl |>
  blank_to_na() |>
  filter(SAFFL == "Y", TRT01A != "Screen Failure")

arm_n      <- adsl_saf |> count(TRT01A) |> pull(n, name = TRT01A)
arm_levels <- c("Placebo", "Xanomeline Low Dose", "Xanomeline High Dose")

# Visit ordering (include Baseline)
visit_order <- c(
  "Baseline", "Week 2", "Week 4", "Week 6", "Week 8",
  "Week 12", "Week 16", "Week 20", "Week 24",
  "Week 26", "End of Treatment"
)

advs_saf <- advs_saf |>
  mutate(
    AVISIT = factor(AVISIT, levels = visit_order),
    TRT01A = factor(TRT01A, levels = arm_levels)
  ) |>
  filter(!is.na(AVISIT))
```


## Data Preparation

::: {.panel-tabset}

### dplyr

```{r}
#| label: dplyr-code

# ── Wide layout: one row per Visit × Treatment ──
# Value stats: N, Mean, SD, Median, Min, Max
# Change from Baseline stats: N, Mean, SD, Median, Min, Max

fmt1 <- function(x) ifelse(is.na(x), "", sprintf("%.1f", x))
fmt0 <- function(x) ifelse(is.na(x), "", sprintf("%.0f", x))

vs_wide <- advs_saf |>
  group_by(PARAM, PARAMCD, AVISIT, TRT01A) |>
  summarise(
    val_n      = as.character(sum(!is.na(AVAL))),
    val_mean   = fmt1(mean(AVAL, na.rm = TRUE)),
    val_sd     = fmt1(sd(AVAL, na.rm = TRUE)),
    val_median = fmt1(median(AVAL, na.rm = TRUE)),
    val_min    = fmt0(min(AVAL, na.rm = TRUE)),
    val_max    = fmt0(max(AVAL, na.rm = TRUE)),
    chg_n      = if (all(is.na(CHG))) "" else as.character(sum(!is.na(CHG))),
    chg_mean   = if (all(is.na(CHG))) "" else fmt1(mean(CHG, na.rm = TRUE)),
    chg_sd     = if (all(is.na(CHG))) "" else fmt1(sd(CHG, na.rm = TRUE)),
    chg_median = if (all(is.na(CHG))) "" else fmt1(median(CHG, na.rm = TRUE)),
    chg_min    = if (all(is.na(CHG))) "" else fmt0(min(CHG, na.rm = TRUE)),
    chg_max    = if (all(is.na(CHG))) "" else fmt0(max(CHG, na.rm = TRUE)),
    .groups    = "drop"
  ) |>
  # Handle visits with no CHG data (Baseline)
 mutate(
    across(starts_with("chg_"), ~ ifelse(.x %in% c("NaN", "NA", "Inf", "-Inf"), "", .x))
  ) |>
  arrange(PARAM, AVISIT, TRT01A) |>
  mutate(across(where(is.factor), as.character))
```

### cards

```{r}
#| label: cards-code

vs_data <- advs_saf |>
  mutate(across(where(is.factor), as.character))

vs_ard <- ard_stack(
  data  = vs_data,
  .by   = c("TRT01A", "PARAM", "AVISIT"),
  ard_continuous(
    variables = c("AVAL", "CHG"),
    statistic = ~ continuous_summary_fns(c("N", "mean", "sd", "median", "min", "max"))
  ),
  .overall = FALSE
) |>
  filter(variable %in% c("AVAL", "CHG"))

# Pivot to wide: one row per Visit × Treatment
vs_cards <- vs_ard |>
  mutate(
    stat_val = as.numeric(unlist(stat)),
    stat_name = as.character(stat_name),
    var = as.character(variable),
    arm = as.character(unlist(group1_level)),
    param = as.character(unlist(group2_level)),
    visit = as.character(unlist(group3_level))
  ) |>
  mutate(
    col_name = paste0(
      ifelse(var == "AVAL", "val_", "chg_"),
      stat_name
    )
  ) |>
  select(param, visit, arm, col_name, stat_val) |>
  pivot_wider(names_from = col_name, values_from = stat_val) |>
  mutate(across(where(is.numeric), ~ ifelse(is.na(.) | is.nan(.) | is.infinite(.), NA_real_, .)))
```

:::


## arframe Pipeline

The rendered table below uses the **dplyr** data prep (`vs_wide`). The cards tab produces an equivalent `vs_cards` — swap it in to use the cards path instead.

Each row is a treatment group at a visit, with Value and Change from Baseline statistics as columns:

```{r}
#| label: pipeline
#| eval: false
vs_wide |>
  fr_table() |>
  fr_titles(
    "Table 14.3.9",
    "Summary of Vital Signs: Values and Change from Baseline",
    "Safety Population"
  ) |>
  fr_cols(
    PARAMCD    = fr_col(visible = FALSE),
    PARAM      = fr_col(visible = FALSE),
    AVISIT     = fr_col(visible = FALSE),
    TRT01A     = fr_col("", width = 1.5),
    val_n      = fr_col("N",      align = "decimal", width = 0.5),
    val_mean   = fr_col("Mean",   align = "decimal", width = 0.7),
    val_sd     = fr_col("SD",     align = "decimal", width = 0.7),
    val_median = fr_col("Median", align = "decimal", width = 0.7),
    val_min    = fr_col("Min",    align = "decimal", width = 0.5),
    val_max    = fr_col("Max",    align = "decimal", width = 0.5),
    chg_n      = fr_col("N",      align = "decimal", width = 0.5),
    chg_mean   = fr_col("Mean",   align = "decimal", width = 0.7),
    chg_sd     = fr_col("SD",     align = "decimal", width = 0.7),
    chg_median = fr_col("Median", align = "decimal", width = 0.7),
    chg_min    = fr_col("Min",    align = "decimal", width = 0.5),
    chg_max    = fr_col("Max",    align = "decimal", width = 0.5)
  ) |>
  fr_spans(
    "Value" = c("val_n", "val_mean", "val_sd", "val_median", "val_min", "val_max"),
    "Change from Baseline" = c("chg_n", "chg_mean", "chg_sd", "chg_median", "chg_min", "chg_max"),
    .gap = FALSE
  ) |>
  fr_header(bold = TRUE, align = "center") |>
  fr_rows(
    group_by    = list(cols = "AVISIT", label = "TRT01A"),
    blank_after = "AVISIT",
    page_by     = "PARAM",
    group_style = list(bold = TRUE)
  ) |>
  fr_footnotes(
    "Value = observed value at visit; Change = post-baseline value minus baseline.",
    "SD = Standard Deviation.",
    "CDISCPILOT01 Safety Population."
  )
```


## Rendered Table

```{r}
#| label: rendered-table
#| echo: false
#| ref.label: pipeline
```

Open-source TFL reference collection

 

CDISC Pilot Study (CDISCPILOT01) • pharmaverseadam datasets