This is a continuation of my question posted here.
I am still facing some issues with usage of tbl_summary. For code below, I have forced TRT01A, PARAMCD etc. as factors. This helps me generate consistent shell data with zero counts and provides ability to control the display order in table body.
# Load data
advs <- pharmaverseadam::advs %>%
filter(SAFFL == "Y" & !is.na(ANRIND)) %>%
select(c(USUBJID, TRT01A, PARAMCD, PARAM, AVISIT, AVISITN, ADT, AVAL, ANRIND)) |>
mutate(TRT01AN = case_when(
TRT01A == 'Xanomeline High Dose' ~ 1,
TRT01A == 'Xanomeline Low Dose' ~ 2,
TRT01A == 'Placebo' ~ 3
))
# Summary prior to process
advs.smr <- advs %>%
group_by(USUBJID, TRT01A, TRT01AN, PARAMCD, PARAM) %>%
# Can use slice_max or summarize
summarise(AVL.NRIND = max(ANRIND, na.rm = TRUE), .groups = 'drop') |>
arrange(USUBJID, PARAMCD) |>
mutate(
TRT01A = factor(TRT01A, levels = unique(TRT01A[order(TRT01AN)])),
PARAMCD = factor(PARAMCD, levels = c("SYSBP", "DIABP", "TEMP")),
AVL.NRIND = factor(AVL.NRIND, levels = sort(unique(AVL.NRIND))[order(c(3,2,1))])
) |>
filter((PARAMCD == 'SYSBP' & !grepl("2$", USUBJID)) | (PARAMCD == 'DIABP' & !grepl("3$", USUBJID))|(PARAMCD == 'TEMP' & !grepl("1$", USUBJID)))
Using above source data tbl_summary with percent=adsl call have issues. From below code, tbl.smry1 can generate output successfully, whereas the tbl.smry2 fails.
tbl.smry1 <- advs.smr |>
tbl_strata2(
strata = PARAMCD,
~ .x |>
tbl_summary(
include = AVL.NRIND,
by = TRT01A,
label = list(AVL.NRIND = .y),
percent = "column"
) |>
add_overall(last = TRUE),
.combine_with = "tbl_stack",
.combine_args = list(group_header = NULL)
)
tbl.smry2 <- advs.smr |>
tbl_strata2(
strata = PARAMCD,
~ .x |>
tbl_summary(
include = AVL.NRIND,
by = TRT01A,
label = list(AVL.NRIND = .y),
percent = adsl
) |>
add_overall(last = TRUE),
.combine_with = "tbl_stack",
.combine_args = list(group_header = NULL)
)
Please let me know if I am missing something here. Output from tbl.smry1 below with zero count rows and PARAMCD, Treatment, ANRIND levels in order as forced using factor.

I think there were two issues here:
adsl was missing the pharmaverseadam::adsl prefix.adsl and a factor in advs. (I should have better messaging when there is a mis-match, however. I'll add that.)library(tidyverse)
library(gtsummary)
packageVersion("gtsummary")
#> [1] '2.4.0.9002'
advs <- pharmaverseadam::advs %>%
filter(SAFFL == "Y", !is.na(ANRIND), PARAMCD %in% c("SYSBP", "DIABP", "TEMP")) %>%
select(c(USUBJID, TRT01A, PARAMCD, PARAM, AVISIT, AVISITN, ADT, AVAL, ANRIND)) |>
mutate(
TRT01A = factor(TRT01A, levels = c('Xanomeline High Dose', 'Xanomeline Low Dose', 'Placebo'))
)
# Summary prior to process
advs.smr <- advs %>%
group_by(USUBJID, TRT01A, PARAMCD, PARAM) %>%
# Can use slice_max or summarize
summarise(AVL.NRIND = max(ANRIND, na.rm = TRUE), .groups = 'drop') |>
mutate(
PARAMCD = factor(PARAMCD, levels = c("SYSBP", "DIABP", "TEMP")),
AVL.NRIND = factor(AVL.NRIND, levels = sort(unique(AVL.NRIND))[order(c(3,2,1))])
)
advs.smr |>
tbl_strata2(
strata = PARAMCD,
~ .x |>
tbl_summary(
include = AVL.NRIND,
by = TRT01A,
label = list(AVL.NRIND = .y),
percent =
pharmaverseadam::adsl |>
mutate(
TRT01A = factor(TRT01A, levels = c('Xanomeline High Dose', 'Xanomeline Low Dose', 'Placebo'))
)
) |>
add_overall(last = TRUE),
.combine_with = "tbl_stack",
.combine_args = list(group_header = NULL)
) |>
bold_labels() |>
# convert to markdown to display on stackoverflow
as_kable()
| Characteristic | Xanomeline High Dose N = 72 | Xanomeline Low Dose N = 96 | Placebo N = 86 | Overall N = 306 |
|---|---|---|---|---|
| SYSBP | ||||
| NORMAL | 67 (93%) | 84 (88%) | 81 (94%) | 232 (76%) |
| LOW | 0 (0%) | 0 (0%) | 0 (0%) | 0 (0%) |
| HIGH | 5 (6.9%) | 12 (13%) | 5 (5.8%) | 22 (7.2%) |
| DIABP | ||||
| NORMAL | 71 (99%) | 95 (99%) | 84 (98%) | 250 (82%) |
| LOW | 0 (0%) | 0 (0%) | 1 (1.2%) | 1 (0.3%) |
| HIGH | 1 (1.4%) | 1 (1.0%) | 1 (1.2%) | 3 (1.0%) |
| TEMP | ||||
| NORMAL | 70 (97%) | 88 (92%) | 85 (99%) | 243 (79%) |
| LOW | 2 (2.8%) | 8 (8.3%) | 1 (1.2%) | 11 (3.6%) |
| HIGH | 0 (0%) | 0 (0%) | 0 (0%) | 0 (0%) |
Created on 2025-10-07 with reprex v2.1.1