arframe TFL Gallery
  1. Tables
  2. Laboratory Results - Chemistry
  • 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. Laboratory Results - Chemistry

Laboratory Results - Chemistry

Summary Statistics by Visit

Setup

See Prerequisites for installation instructions.

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

# Chemistry panel — 6 key parameters
param_subset <- c("ALT", "AST", "BILI", "CREAT", "GLUC", "SODIUM")

adlb_saf <- pharmaverseadam::adlb |>
  blank_to_na() |>
  filter(
    SAFFL   == "Y",
    TRT01A  != "Screen Failure",
    PARCAT1 == "CHEMISTRY",
    PARAMCD %in% param_subset,
    ANL01FL == "Y" | AVISIT == "Baseline"
  )

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

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

visit_order <- c(
  "Baseline", "Week 2", "Week 4", "Week 6", "Week 8",
  "Week 12", "Week 16", "Week 20", "Week 24", "Week 26"
)

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

Data Preparation

  • dplyr
  • cards
# ── Vertical layout: stats as rows, arms as columns ──
# Per PARAM × AVISIT × TRT01A: N, Mean, SD, Median, Min, Max
lab_stats <- adlb_saf |>
  group_by(PARAM, PARAMCD, AVISIT, TRT01A) |>
  summarise(
    n      = as.character(sum(!is.na(AVAL))),
    Mean   = sprintf("%.1f", mean(AVAL, na.rm = TRUE)),
    SD     = sprintf("%.2f", sd(AVAL, na.rm = TRUE)),
    Median = sprintf("%.1f", median(AVAL, na.rm = TRUE)),
    Min    = sprintf("%.0f", min(AVAL, na.rm = TRUE)),
    Max    = sprintf("%.0f", max(AVAL, na.rm = TRUE)),
    .groups = "drop"
  ) |>
  pivot_longer(
    cols      = c(n, Mean, SD, Median, Min, Max),
    names_to  = "stat_label",
    values_to = "value"
  ) |>
  pivot_wider(names_from = TRT01A, values_from = value) |>
  arrange(PARAM, AVISIT, match(stat_label, c("n", "Mean", "SD", "Median", "Min", "Max"))) |>
  mutate(across(where(is.factor), as.character))

# Ensure all arm columns present
for (arm in arm_levels) {
  if (!arm %in% names(lab_stats)) lab_stats[[arm]] <- ""
}

lab_wide <- lab_stats |>
  select(PARAM, PARAMCD, AVISIT, stat_label, all_of(arm_levels))

This tab showcases fr_wide_ard() with per-variable decimal precision — different lab parameters get different formatting based on their scale and clinical convention:

lab_data <- adlb_saf |>
  mutate(across(where(is.factor), as.character))

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

# Per-variable decimals: different precision per lab parameter
lab_cards <- fr_wide_ard(
  lab_ard,
  statistic = list(
    continuous = c(
      "n"      = "{N}",
      "Mean"   = "{mean}",
      "SD"     = "{sd}",
      "Median" = "{median}",
      "Min"    = "{min}",
      "Max"    = "{max}"
    )
  ),
  decimals = list(
    ALT    = c(mean = 1, sd = 2, median = 1, min = 0, max = 0),
    AST    = c(mean = 1, sd = 2, median = 1, min = 0, max = 0),
    BILI   = c(mean = 2, sd = 3, median = 2, min = 1, max = 1),
    CREAT  = c(mean = 1, sd = 2, median = 1, min = 0, max = 0),
    GLUC   = c(mean = 1, sd = 2, median = 1, min = 0, max = 0),
    SODIUM = c(mean = 1, sd = 2, median = 1, min = 0, max = 0)
  ),
  column = "TRT01A",
  label  = c(AVAL = "Value")
) |>
  mutate(across(where(is.factor), as.character))

arframe Pipeline

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

Stats as rows with treatment arms as columns, grouped by visit, one page per parameter:

lab_wide |>
  fr_table() |>
  fr_titles(
    "Table 14.3.4",
    "Laboratory Results Summary - Chemistry Panel",
    "Safety Population"
  ) |>
  fr_cols(
    PARAMCD    = fr_col(visible = FALSE),
    PARAM      = fr_col(visible = FALSE),
    AVISIT     = fr_col(visible = FALSE),
    stat_label = fr_col("", width = 1.5),
    !!!setNames(
      lapply(arm_levels, function(a) fr_col(a, align = "decimal")),
      arm_levels
    ),
    .n = arm_n
  ) |>
  fr_header(bold = TRUE, align = "center") |>
  fr_rows(
    group_by    = list(cols = "AVISIT", label = "stat_label"),
    blank_after = "AVISIT",
    page_by     = "PARAM",
    group_style = list(bold = TRUE)
  ) |>
  fr_footnotes(
    "N is the number of subjects in the Safety Population per treatment group.",
    "Statistics: n = number of non-missing observations, SD = Standard Deviation.",
    "Chemistry panel: ALT, AST, Bilirubin, Creatinine, Glucose, Sodium."
  )

Rendered Table

Table 14.3.4
Laboratory Results Summary - Chemistry Panel
Safety Population
Alanine Aminotransferase (U/L)
Placebo
(N=86)
Xanomeline Low Dose
(N=96)
Xanomeline High Dose
(N=72)
Baseline
n 86 94 72
Mean 17.6 18.1 19.2
SD 9.22 8.74 10.25
Median 15.0 16.5 16.5
Min 7 5 6
Max 69 70 64
Week 2
n 82 73 70
Mean 18.0 21.1 21.2
SD 12.59 10.74 8.87
Median 15.0 20.0 19.0
Min 6 5 8
Max104 88 49
Week 4
n 75 65 67
Mean 17.2 17.4 21.5
SD 7.93 7.83 9.78
Median 15.0 16.0 20.0
Min 6 5 8
Max 45 62 61
Week 6
n 71 58 55
Mean 17.0 17.0 21.0
SD 10.04 8.25 10.08
Median 15.0 16.0 18.0
Min 4 5 10
Max 76 57 60
Week 8
n 70 54 52
Mean 16.9 18.0 23.4
SD 9.39 8.04 18.01
Median 15.0 17.0 18.0
Min 7 4 10
Max 63 54 129
Week 12
n 66 40 42
Mean 18.1 18.7 21.0
SD 9.16 13.24 10.69
Median 16.0 15.0 19.0
Min 6 9 9
Max 64 88 70
Week 16
n 67 32 33
Mean 17.1 17.0 20.0
SD 7.44 8.28 7.89
Median 15.0 15.5 19.0
Min 5 6 7
Max 48 43 38
Week 20
n 61 26 29
Mean 16.3 16.9 19.9
SD 6.71 6.21 6.81
Median 14.0 15.0 20.0
Min 7 11 9
Max 48 37 35
Week 24
n 57 24 28
Mean 17.9 18.6 21.3
SD 15.61 9.42 8.80
Median 14.0 17.0 18.5
Min 5 7 9
Max124 48 43
Week 26
n 50 22 24
Mean 15.8 16.3 18.3
SD 6.03 6.36 6.01
Median 15.0 15.0 19.0
Min 3 7 8
Max 31 33 28
N is the number of subjects in the Safety Population per treatment group.
Statistics: n = number of non-missing observations, SD = Standard Deviation.
Chemistry panel: ALT, AST, Bilirubin, Creatinine, Glucose, Sodium.
/opt/quarto/share/rmd/rmd.R 01APR2026 09:54:00
Table 14.3.4
Laboratory Results Summary - Chemistry Panel
Safety Population
Aspartate Aminotransferase (U/L)
Placebo
(N=86)
Xanomeline Low Dose
(N=96)
Xanomeline High Dose
(N=72)
Baseline
n 86 94 72
Mean 23.2 23.3 23.2
SD 7.50 7.99 6.71
Median 22.0 21.0 21.0
Min 12 13 13
Max 54 60 52
Week 2
n 82 73 70
Mean 23.7 25.0 23.5
SD 12.40 8.31 4.93
Median 22.0 24.0 23.0
Min 12 10 15
Max118 62 37
Week 4
n 75 65 67
Mean 22.4 22.2 24.0
SD 8.08 6.64 5.98
Median 21.0 20.0 24.0
Min 12 12 12
Max 66 46 43
Week 6
n 71 58 55
Mean 22.1 22.3 24.7
SD 6.44 6.19 8.29
Median 21.0 22.0 23.0
Min 14 9 15
Max 58 48 73
Week 8
n 70 54 52
Mean 22.5 22.8 26.1
SD 7.11 6.14 13.72
Median 21.0 22.0 24.5
Min 9 14 13
Max 54 44 114
Week 12
n 66 40 42
Mean 22.9 25.0 23.6
SD 7.64 17.43 6.52
Median 22.0 21.5 23.0
Min 11 14 12
Max 62 125 50
Week 16
n 67 32 33
Mean 22.9 22.8 23.5
SD 6.45 11.57 5.61
Median 22.0 20.0 22.0
Min 13 12 15
Max 49 76 40
Week 20
n 61 26 29
Mean 22.1 20.3 24.3
SD 6.03 4.87 7.00
Median 21.0 19.0 22.0
Min 12 12 16
Max 48 33 45
Week 24
n 57 24 28
Mean 25.2 22.7 24.9
SD 21.02 11.18 7.37
Median 22.0 20.0 22.5
Min 11 11 17
Max168 68 48
Week 26
n 50 22 24
Mean 21.5 20.0 21.1
SD 7.29 4.53 5.54
Median 20.5 19.0 19.5
Min 10 14 15
Max 58 32 38
N is the number of subjects in the Safety Population per treatment group.
Statistics: n = number of non-missing observations, SD = Standard Deviation.
Chemistry panel: ALT, AST, Bilirubin, Creatinine, Glucose, Sodium.
/opt/quarto/share/rmd/rmd.R 01APR2026 09:54:00
Table 14.3.4
Laboratory Results Summary - Chemistry Panel
Safety Population
Bilirubin (umol/L)
Placebo
(N=86)
Xanomeline Low Dose
(N=96)
Xanomeline High Dose
(N=72)
Baseline
n 86 94 72
Mean 9.7 9.5 11.2
SD 3.96 3.91 5.63
Median 8.5 8.5 10.3
Min 5 5 3
Max 26 27 39
Week 2
n 82 72 70
Mean 10.8 9.6 10.5
SD 12.33 4.29 4.04
Median 8.5 8.5 10.3
Min 3 5 5
Max116 26 26
Week 4
n 75 64 67
Mean 9.7 9.0 11.1
SD 4.12 3.67 5.77
Median 8.5 8.5 10.3
Min 5 3 5
Max 22 22 34
Week 6
n 71 58 55
Mean 9.7 9.3 10.9
SD 3.80 3.91 4.98
Median 8.5 8.5 10.3
Min 3 3 5
Max 22 24 29
Week 8
n 70 54 52
Mean 9.4 9.5 10.9
SD 3.94 4.79 5.40
Median 8.5 8.5 9.4
Min 3 3 5
Max 26 26 29
Week 12
n 66 40 42
Mean 9.5 8.9 11.2
SD 3.59 4.44 6.06
Median 8.5 8.5 8.5
Min 5 3 3
Max 22 24 36
Week 16
n 67 32 33
Mean 10.0 8.6 11.5
SD 3.65 4.06 4.88
Median 8.5 6.8 10.3
Min 5 5 5
Max 22 24 26
Week 20
n 61 26 29
Mean 9.8 8.9 12.0
SD 5.10 4.17 8.95
Median 8.5 7.7 8.5
Min 3 5 5
Max 27 21 53
Week 24
n 55 24 28
Mean 9.4 10.1 12.2
SD 3.39 4.54 6.72
Median 8.5 8.5 10.3
Min 5 5 5
Max 24 22 31
Week 26
n 50 22 24
Mean 10.3 10.5 12.0
SD 4.85 6.56 7.10
Median 8.5 8.5 10.3
Min 5 5 3
Max 31 32 29
N is the number of subjects in the Safety Population per treatment group.
Statistics: n = number of non-missing observations, SD = Standard Deviation.
Chemistry panel: ALT, AST, Bilirubin, Creatinine, Glucose, Sodium.
/opt/quarto/share/rmd/rmd.R 01APR2026 09:54:00
Table 14.3.4
Laboratory Results Summary - Chemistry Panel
Safety Population
Creatinine (umol/L)
Placebo
(N=86)
Xanomeline Low Dose
(N=96)
Xanomeline High Dose
(N=72)
Baseline
n 86 94 72
Mean 97.7 103.8 103.3
SD 17.78 19.40 20.06
Median 97.2 101.7 97.2
Min 62 71 62
Max159 168 159
Week 2
n 83 73 70
Mean 99.1 105.6 106.2
SD 17.61 21.40 21.84
Median 97.2 106.1 106.1
Min 71 71 62
Max141 177 168
Week 4
n 78 65 67
Mean 98.8 104.7 105.8
SD 18.67 20.45 21.38
Median 97.2 97.2 106.1
Min 71 71 71
Max177 168 177
Week 6
n 73 60 56
Mean101.6 104.0 106.4
SD 18.70 20.29 22.17
Median 97.2 97.2 106.1
Min 71 71 71
Max150 168 177
Week 8
n 71 54 52
Mean 99.1 102.0 105.1
SD 16.29 19.10 21.63
Median 97.2 106.1 106.1
Min 71 62 71
Max141 159 177
Week 12
n 66 41 42
Mean101.4 103.9 105.4
SD 16.64 18.73 18.97
Median106.1 97.2 106.1
Min 71 71 62
Max133 168 141
Week 16
n 67 32 33
Mean100.4 97.5 102.1
SD 16.55 17.17 17.69
Median 97.2 97.2 97.2
Min 71 71 71
Max133 133 133
Week 20
n 62 27 29
Mean 99.9 97.2 102.7
SD 17.79 18.35 19.97
Median101.7 97.2 106.1
Min 62 71 71
Max133 133 141
Week 24
n 57 25 28
Mean 99.3 99.4 99.4
SD 15.85 16.79 20.08
Median 97.2 97.2 101.7
Min 71 80 62
Max141 141 133
Week 26
n 50 22 24
Mean101.3 98.8 100.2
SD 19.09 19.79 18.74
Median 97.2 97.2 106.1
Min 71 71 71
Max150 141 133
N is the number of subjects in the Safety Population per treatment group.
Statistics: n = number of non-missing observations, SD = Standard Deviation.
Chemistry panel: ALT, AST, Bilirubin, Creatinine, Glucose, Sodium.
/opt/quarto/share/rmd/rmd.R 01APR2026 09:54:00
Table 14.3.4
Laboratory Results Summary - Chemistry Panel
Safety Population
Glucose (mmol/L)
Placebo
(N=86)
Xanomeline Low Dose
(N=96)
Xanomeline High Dose
(N=72)
Baseline
n 86 94 72
Mean 5.6 5.4 5.4
SD 2.14 0.99 1.36
Median 5.2 5.2 5.1
Min 4 3 3
Max 22 9 11
Week 2
n 82 73 70
Mean 5.6 5.6 6.2
SD 1.88 1.82 3.06
Median 5.2 5.3 5.4
Min 3 4 3
Max 18 17 23
Week 4
n 75 63 67
Mean 5.6 5.5 5.8
SD 1.92 1.46 1.71
Median 5.2 5.3 5.3
Min 3 3 4
Max 18 13 14
Week 6
n 71 58 55
Mean 5.7 5.4 5.9
SD 2.25 1.38 2.87
Median 5.2 5.2 5.2
Min 3 4 4
Max 20 14 24
Week 8
n 70 54 52
Mean 5.5 5.6 5.7
SD 1.36 1.82 1.88
Median 5.2 5.2 5.4
Min 3 4 4
Max 12 15 16
Week 12
n 66 40 41
Mean 6.1 6.1 5.8
SD 1.98 3.52 2.05
Median 5.6 5.4 5.2
Min 4 3 4
Max 15 26 15
Week 16
n 67 32 33
Mean 5.5 5.4 5.4
SD 1.42 0.86 1.16
Median 5.2 5.3 5.2
Min 4 4 3
Max 12 9 8
Week 20
n 61 26 29
Mean 5.8 5.6 5.8
SD 1.54 1.68 1.66
Median 5.6 5.3 5.3
Min 4 4 4
Max 15 12 11
Week 24
n 57 24 28
Mean 5.7 5.8 6.0
SD 1.83 1.28 1.91
Median 5.3 5.3 5.4
Min 3 4 4
Max 13 10 14
Week 26
n 50 22 24
Mean 5.7 5.6 5.6
SD 1.90 1.84 1.07
Median 5.3 5.3 5.4
Min 3 3 4
Max 15 11 8
N is the number of subjects in the Safety Population per treatment group.
Statistics: n = number of non-missing observations, SD = Standard Deviation.
Chemistry panel: ALT, AST, Bilirubin, Creatinine, Glucose, Sodium.
/opt/quarto/share/rmd/rmd.R 01APR2026 09:54:00
Table 14.3.4
Laboratory Results Summary - Chemistry Panel
Safety Population
Sodium (mmol/L)
Placebo
(N=86)
Xanomeline Low Dose
(N=96)
Xanomeline High Dose
(N=72)
Baseline
n 86 94 71
Mean140.3 140.0 140.0
SD 2.74 2.66 3.13
Median140.0 140.0 140.0
Min132 132 130
Max147 145 148
Week 2
n 83 73 69
Mean140.3 139.7 139.1
SD 2.59 2.57 2.77
Median140.0 139.0 139.0
Min129 135 131
Max149 147 147
Week 4
n 78 65 67
Mean139.9 140.2 139.7
SD 2.76 2.48 2.73
Median140.0 140.0 140.0
Min132 133 134
Max146 145 147
Week 6
n 73 60 56
Mean140.3 140.6 140.1
SD 2.61 2.74 2.49
Median140.0 141.0 140.5
Min129 133 135
Max145 146 145
Week 8
n 71 54 52
Mean140.6 140.2 140.3
SD 2.47 2.42 3.24
Median140.0 141.0 140.5
Min135 134 132
Max148 146 154
Week 12
n 66 41 41
Mean140.4 140.7 140.3
SD 2.41 2.65 2.40
Median141.0 140.0 140.0
Min134 135 134
Max145 146 145
Week 16
n 67 32 33
Mean141.1 140.9 141.8
SD 2.34 2.58 2.89
Median141.0 140.5 142.0
Min134 135 138
Max145 145 148
Week 20
n 61 27 29
Mean141.2 141.4 142.0
SD 2.16 2.58 3.93
Median141.0 141.0 142.0
Min136 137 133
Max145 146 151
Week 24
n 57 25 28
Mean141.7 141.4 141.4
SD 2.23 2.18 2.96
Median142.0 142.0 141.5
Min135 136 134
Max146 145 149
Week 26
n 50 22 24
Mean142.5 142.0 142.2
SD 2.22 2.17 3.19
Median142.0 142.0 142.5
Min138 137 134
Max148 145 149
N is the number of subjects in the Safety Population per treatment group.
Statistics: n = number of non-missing observations, SD = Standard Deviation.
Chemistry panel: ALT, AST, Bilirubin, Creatinine, Glucose, Sodium.
/opt/quarto/share/rmd/rmd.R 01APR2026 09:54:00
Source Code
---
title: "Laboratory Results - Chemistry"
subtitle: "Summary Statistics 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)

# Chemistry panel — 6 key parameters
param_subset <- c("ALT", "AST", "BILI", "CREAT", "GLUC", "SODIUM")

adlb_saf <- pharmaverseadam::adlb |>
  blank_to_na() |>
  filter(
    SAFFL   == "Y",
    TRT01A  != "Screen Failure",
    PARCAT1 == "CHEMISTRY",
    PARAMCD %in% param_subset,
    ANL01FL == "Y" | AVISIT == "Baseline"
  )

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

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

visit_order <- c(
  "Baseline", "Week 2", "Week 4", "Week 6", "Week 8",
  "Week 12", "Week 16", "Week 20", "Week 24", "Week 26"
)

adlb_saf <- adlb_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

# ── Vertical layout: stats as rows, arms as columns ──
# Per PARAM × AVISIT × TRT01A: N, Mean, SD, Median, Min, Max
lab_stats <- adlb_saf |>
  group_by(PARAM, PARAMCD, AVISIT, TRT01A) |>
  summarise(
    n      = as.character(sum(!is.na(AVAL))),
    Mean   = sprintf("%.1f", mean(AVAL, na.rm = TRUE)),
    SD     = sprintf("%.2f", sd(AVAL, na.rm = TRUE)),
    Median = sprintf("%.1f", median(AVAL, na.rm = TRUE)),
    Min    = sprintf("%.0f", min(AVAL, na.rm = TRUE)),
    Max    = sprintf("%.0f", max(AVAL, na.rm = TRUE)),
    .groups = "drop"
  ) |>
  pivot_longer(
    cols      = c(n, Mean, SD, Median, Min, Max),
    names_to  = "stat_label",
    values_to = "value"
  ) |>
  pivot_wider(names_from = TRT01A, values_from = value) |>
  arrange(PARAM, AVISIT, match(stat_label, c("n", "Mean", "SD", "Median", "Min", "Max"))) |>
  mutate(across(where(is.factor), as.character))

# Ensure all arm columns present
for (arm in arm_levels) {
  if (!arm %in% names(lab_stats)) lab_stats[[arm]] <- ""
}

lab_wide <- lab_stats |>
  select(PARAM, PARAMCD, AVISIT, stat_label, all_of(arm_levels))
```

### cards

This tab showcases `fr_wide_ard()` with **per-variable decimal precision** — different lab parameters get different formatting based on their scale and clinical convention:

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

lab_data <- adlb_saf |>
  mutate(across(where(is.factor), as.character))

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

# Per-variable decimals: different precision per lab parameter
lab_cards <- fr_wide_ard(
  lab_ard,
  statistic = list(
    continuous = c(
      "n"      = "{N}",
      "Mean"   = "{mean}",
      "SD"     = "{sd}",
      "Median" = "{median}",
      "Min"    = "{min}",
      "Max"    = "{max}"
    )
  ),
  decimals = list(
    ALT    = c(mean = 1, sd = 2, median = 1, min = 0, max = 0),
    AST    = c(mean = 1, sd = 2, median = 1, min = 0, max = 0),
    BILI   = c(mean = 2, sd = 3, median = 2, min = 1, max = 1),
    CREAT  = c(mean = 1, sd = 2, median = 1, min = 0, max = 0),
    GLUC   = c(mean = 1, sd = 2, median = 1, min = 0, max = 0),
    SODIUM = c(mean = 1, sd = 2, median = 1, min = 0, max = 0)
  ),
  column = "TRT01A",
  label  = c(AVAL = "Value")
) |>
  mutate(across(where(is.factor), as.character))
```

:::


## arframe Pipeline

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

Stats as rows with treatment arms as columns, grouped by visit, one page per parameter:

```{r}
#| label: pipeline
#| eval: false
lab_wide |>
  fr_table() |>
  fr_titles(
    "Table 14.3.4",
    "Laboratory Results Summary - Chemistry Panel",
    "Safety Population"
  ) |>
  fr_cols(
    PARAMCD    = fr_col(visible = FALSE),
    PARAM      = fr_col(visible = FALSE),
    AVISIT     = fr_col(visible = FALSE),
    stat_label = fr_col("", width = 1.5),
    !!!setNames(
      lapply(arm_levels, function(a) fr_col(a, align = "decimal")),
      arm_levels
    ),
    .n = arm_n
  ) |>
  fr_header(bold = TRUE, align = "center") |>
  fr_rows(
    group_by    = list(cols = "AVISIT", label = "stat_label"),
    blank_after = "AVISIT",
    page_by     = "PARAM",
    group_style = list(bold = TRUE)
  ) |>
  fr_footnotes(
    "N is the number of subjects in the Safety Population per treatment group.",
    "Statistics: n = number of non-missing observations, SD = Standard Deviation.",
    "Chemistry panel: ALT, AST, Bilirubin, Creatinine, Glucose, Sodium."
  )
```


## Rendered Table

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

Open-source TFL reference collection

 

CDISC Pilot Study (CDISCPILOT01) • pharmaverseadam datasets