Skip to content

[Bug]: tm_missing_data() "Grouped by Subject" fails with dplyr ≥ 1.1: "must be size 1" #1013

Description

@wangben718

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 dplyr1.1.0 should reproduce)

Relevant log output

Code of Conduct

  • I agree to follow this project's Code of Conduct.

Contribution Guidelines

  • I agree to follow this project's Contribution Guidelines.

Security Policy

  • I agree to follow this project's Security Policy.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions