What happened?
tm_missing_data() "Grouped by Subject" fails with dplyr ≥ 1.1: "must be size 1" (masked as "Data passed has errors.")
Summary
Opening the "Grouped by Subject" view of tm_missing_data() errors for every dataset. The user only sees the generic decorator message:
Data passed has errors.
The real error is masked. It comes from an ungrouped dplyr::summarise() in the by-subject plot code whose expressions each return one value per column (N values, not 1). This was tolerated by dplyr ≤ 1.0 but is an error under dplyr ≥ 1.1:
`column` must be size 1, not 98.
ℹ To return more or less than 1 row per group, use `reframe()`.
Because the offending code is shared by all datasets, the failure is universal (not data-specific).
Location
R/tm_missing_data.R, in by_subject_plot_q — the ordered_columns step (around line 1325 on the current main / 0.8.0.9000):
# order columns by decreasing percent of missing values
ordered_columns <- summary_plot_patients %>%
dplyr::select(-"id", -dplyr::all_of(parent_keys)) %>%
dplyr::summarise(
column = create_cols_labels(colnames(.)), # length == n columns, not 1
na_count = apply(., MARGIN = 2, FUN = sum), # length == n columns, not 1
na_percent = na_count / nrow(.) * 100
) %>%
dplyr::arrange(na_percent, dplyr::desc(column))
summary_plot_patients is ungrouped at this point (a dplyr::ungroup() precedes it), so each summarise() expression must return a single value. They instead return one value per column, which dplyr ≥ 1.1 rejects.
This is the only occurrence of the multi-row summarise() pattern in the package — every other summarise()/summarize() call is grouped and returns size-1 (verified across all R/*.R). reframe() is not used anywhere in the package.
Why the user sees "Data passed has errors." instead of the real error
The by-subject plot is wrapped by a decorator via teal::srv_transform_teal_data(). In teal/R/module_transform_data.R:
data_out <- try(data(), silent = TRUE)
if (inherits(data_out, "qenv.error")) {
validate("Data passed has errors.") # generic message; underlying error hidden
}
Since by_subject_plot_q() returns a qenv.error, the wrapper reports the generic string and the actual summarise() error never reaches the UI.
Reproducible example (in-app)
Uses only data shipped with {teal.data}:
library(teal.modules.general)
data <- teal_data()
data <- within(data, {
ADSL <- teal.data::rADSL
ADLB <- teal.data::rADLB
})
join_keys(data) <- teal.data::default_cdisc_join_keys[c("ADSL", "ADLB")]
app <- init(
data = data,
modules = modules(
tm_missing_data("Missing Data") # parent_dataname defaults to "ADSL"
)
)
shinyApp(app$ui, app$server)
Steps: open the app → Missing Data → select the ADLB (or ADSL) dataset → click "Grouped by Subject" → the panel shows "Data passed has errors."
Minimal isolated reproduction (no shiny)
This mirrors the failing ordered_columns step directly:
library(dplyr)
ANL <- as.data.frame(teal.data::rADLB)
parent_keys <- c("STUDYID", "USUBJID")
analysis_vars <- setdiff(colnames(ANL), c("STUDYID", "USUBJID", "PARAMCD", "AVISIT"))
summary_plot_patients <- ANL[, c(parent_keys, analysis_vars)] |>
dplyr::group_by_at(parent_keys) |>
dplyr::mutate(id = dplyr::cur_group_id()) |>
dplyr::ungroup() |>
dplyr::group_by_at(c(parent_keys, "id")) |>
dplyr::summarise_all(anyNA) |>
dplyr::ungroup()
# Fails under dplyr >= 1.1:
summary_plot_patients |>
dplyr::select(-"id", -dplyr::all_of(parent_keys)) |>
dplyr::summarise(
column = colnames(dplyr::pick(dplyr::everything())),
na_count = colSums(dplyr::pick(dplyr::everything())),
na_percent = na_count / dplyr::n() * 100
)
#> Error: `column` must be size 1, not 98.
#> ℹ To return more or less than 1 row per group, use `reframe()`.
Expected vs. actual
- Expected: "Grouped by Subject" renders the missingness-by-subject raster plot.
- Actual: every dataset's "Grouped by Subject" panel shows "Data passed has errors."
Suggested fix
Replace the multi-row summarise() with reframe() (introduced for exactly this case in dplyr 1.1), or build ordered_columns without summarise(). For example:
ordered_columns <- summary_plot_patients %>%
dplyr::select(-"id", -dplyr::all_of(parent_keys)) %>%
dplyr::reframe(
column = create_cols_labels(colnames(dplyr::pick(dplyr::everything()))),
na_count = apply(dplyr::pick(dplyr::everything()), 2, sum),
na_percent = na_count / dplyr::n() * 100
) %>%
dplyr::arrange(na_percent, dplyr::desc(column))
(Any equivalent that yields the per-column column/na_count/na_percent table works; the key is not to return N rows from summarise().)
While here, it would also help end users to surface the underlying error rather than the generic "Data passed has errors." from the decorator wrapper, so failures like this are diagnosable from the UI.
Discovered in the teal.gallery exploratory app
sessionInfo()
- `teal.modules.general`: 0.8.0.9000
- `dplyr`: 1.2.1 (any dplyr ≥ 1.1.0 should reproduce)
Relevant log output
Code of Conduct
Contribution Guidelines
Security Policy
What happened?
tm_missing_data()"Grouped by Subject" fails with dplyr ≥ 1.1: "must be size 1" (masked as "Data passed has errors.")Summary
Opening the "Grouped by Subject" view of
tm_missing_data()errors for every dataset. The user only sees the generic decorator message:The real error is masked. It comes from an ungrouped
dplyr::summarise()in the by-subject plot code whose expressions each return one value per column (N values, not 1). This was tolerated by dplyr ≤ 1.0 but is an error under dplyr ≥ 1.1:Because the offending code is shared by all datasets, the failure is universal (not data-specific).
Location
R/tm_missing_data.R, inby_subject_plot_q— theordered_columnsstep (around line 1325 on the currentmain/ 0.8.0.9000):summary_plot_patientsis ungrouped at this point (adplyr::ungroup()precedes it), so eachsummarise()expression must return a single value. They instead return one value per column, which dplyr ≥ 1.1 rejects.This is the only occurrence of the multi-row
summarise()pattern in the package — every othersummarise()/summarize()call is grouped and returns size-1 (verified across allR/*.R).reframe()is not used anywhere in the package.Why the user sees "Data passed has errors." instead of the real error
The by-subject plot is wrapped by a decorator via
teal::srv_transform_teal_data(). Inteal/R/module_transform_data.R:Since
by_subject_plot_q()returns aqenv.error, the wrapper reports the generic string and the actualsummarise()error never reaches the UI.Reproducible example (in-app)
Uses only data shipped with
{teal.data}:Steps: open the app → Missing Data → select the ADLB (or ADSL) dataset → click "Grouped by Subject" → the panel shows "Data passed has errors."
Minimal isolated reproduction (no shiny)
This mirrors the failing
ordered_columnsstep directly:Expected vs. actual
Suggested fix
Replace the multi-row
summarise()withreframe()(introduced for exactly this case in dplyr 1.1), or buildordered_columnswithoutsummarise(). For example:(Any equivalent that yields the per-column
column/na_count/na_percenttable works; the key is not to return N rows fromsummarise().)While here, it would also help end users to surface the underlying error rather than the generic "Data passed has errors." from the decorator wrapper, so failures like this are diagnosable from the UI.
sessionInfo()
Relevant log output
Code of Conduct
Contribution Guidelines
Security Policy