Dan, I followed your approach to re-build the table as I was trying to do this for multiple PARAMCDs as well. Below is my updated code.
## Load all required packages
packages <- c("cards", "tidyverse", "gtsummary", "labelled", "gt")
invisible(lapply(packages, library, character.only = TRUE))
# Apply gtsummary theme
theme_gtsummary_compact()
# Load data
advs <- pharmaverseadam::advs %>%
filter(SAFFL == "Y" & VSTESTCD %in% c('SYSBP', "DIABP") & !is.na(AVISIT)) %>%
select(c(USUBJID, TRT01A, PARAMCD, PARAM, AVISIT, AVISITN, ADT, AVAL, CHG, PCHG, VSPOS, VSTPT))
# Summary mean prior to process
advs.smr <- advs %>%
group_by(USUBJID, TRT01A, PARAMCD, PARAM, AVISIT, AVISITN, ADT) %>%
summarise(AVAL.MEAN = mean(AVAL, na.rm = TRUE),
CHG.MEAN = mean(CHG, na.rm = TRUE),
.groups = 'drop') %>%
mutate(visit_id = paste("Vis", sprintf("%03d", AVISITN), AVISIT, sep = "_")) %>%
arrange(USUBJID, PARAMCD, AVISITN) %>%
filter(AVISITN <= 4)
# Wide to Long
advs.smr.l <- advs.smr %>%
pivot_longer(cols = c(AVAL.MEAN, CHG.MEAN),
names_to = "anls_var",
values_to = "Value") %>%
filter(!is.nan(Value)) %>%
mutate(anls_var = if_else(grepl("AVAL", anls_var), "Actual Value", "Change From Baseline"))
# Long to Wide
advs.smr.w <- advs.smr.l %>%
select(-c(AVISITN, AVISIT, ADT)) %>%
pivot_wider(names_from = visit_id,
values_from = Value)
# Upcase column names
colnames(advs.smr.w) <- toupper(colnames(advs.smr.w))
# Create List of visit names
alvis <- unique(colnames(advs.smr.w)[grep("^VIS", colnames(advs.smr.w), ignore.case = TRUE)])
vis.nam <- setNames(as.list(sub(".*_", "", alvis)), alvis)
# Table for AVAL by Visit
rpt_body <- function(res.typ) {
# Filter for AVAL/CHG
tmp.dat <- advs.smr.w %>%
filter(grepl(res.typ, ANLS_VAR)) %>%
select(where(~ !all(is.na(.))))
# Create table body
tbl.body <- tmp.dat %>%
tbl_strata_nested_stack(
strata = PARAM,
~ .x %>%
tbl_summary(
by = TRT01A,
include = c(starts_with("VIS")),
type = all_continuous() ~ "continuous2",
statistic = all_continuous() ~ c("{N_nonmiss}", "{mean} ({sd})",
"{median}", "{min}, {max}"),
digits = all_continuous() ~ c(N_nonmiss = 0, mean = 2, sd = 2,
median = 2, min = 2, max = 2),
label = vis.nam,
missing = "no") %>%
# Update Stat Labels
add_stat_label(
label = list(all_continuous() ~ c("n", "MEAN (SD)", "MEDIAN", "MIN, MAX")))
)
return(tbl.body)
}
# Create table summary
tbl.aval <- rpt_body(res.typ = "Actual")
tbl.chg <- rpt_body(res.typ = "Change")
# Merge tables together and apply styling
vs.tbl <- list(tbl.aval, tbl.chg) %>%
tbl_merge(tab_spanner = FALSE,
merge_vars = c("tbl_id1", "variable", "row_type", "var_label", "label")) %>%
# Update spanning header with TRT (level from tbl_summary by)
modify_spanning_header(all_stat_cols() ~ "**{level}** \n(N = {n})") %>%
# Update header
modify_header(
label ~ "*Vital Signs Parameter* \n\U0A0\U0A0\U0A0\U0A0**Visit**",
all_stat_cols() & ends_with("_1") ~ "**Actual Value**",
all_stat_cols() & ends_with("_2") ~ "***Change from Baseline***") %>%
# Update header
modify_table_body(
~ .x %>%
dplyr::mutate(tbl_id1_1 = tbl_id1,
tbl_id1_2 = tbl_id1) %>%
dplyr::relocate(
c(starts_with("stat_1"), starts_with("stat_2"), starts_with("stat_3")),
.after = "label")
)
There is one small issue. The header counts on spanning header for AVAL & CHG are different (screenshot below). I want to display the counts from AVAL, but not too sure how to achieve this.