pharmaverse / pharmaverse/tern

[Bug]: summarize_ancova fails to return contrast rows when ref_group contains regex metacharacters such as `+`

Open
#1,471 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
R
Stars
106
Forks
34
Avg merge
8d 9h
Merged PRs (30d)
6

Description

What happened?

Hi,

I found what appears to be a bug in tern::summarize_ancova() when the treatment/reference level contains regex metacharacters, for example:

"Tirzepatide + Placebo"

Expected behavior:
summarize_ancova() should return lsmean_diff, lsmean_diff_ci, and pval for non-reference treatment columns, the same way that direct emmeans::contrast(..., method = "trt.vs.ctrl") does.

Actual behavior:
lsmean is returned, but lsmean_diff, lsmean_diff_ci, and pval are empty in the table output.

What I checked:

  • The same model fitted directly with lm() + emmeans() + contrast() returns valid estimates, confidence intervals, and p-values.
  • split_cols_by() itself appears to work correctly.
  • The failure seems to happen inside summarize_ancova() when contrast labels are matched back to treatment levels.

I believe the issue is here:
https://github.com/insightsengineering/tern/blob/v0.9.8.433/R/summarize_ancova.R#L190

My current understanding is that the reference-group label is used in pattern matching without escaping regex metacharacters, so + is interpreted as regex instead of a literal character.

sessionInfo()
R version 4.4.3 (2025-02-28)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 22.04.5 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.20.so;  LAPACK version 3.10.0

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8     LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8    LC_PAPER=en_US.UTF-8       LC_NAME=C                  LC_ADDRESS=C              
[10] LC_TELEPHONE=C             LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

time zone: Etc/UTC
tzcode source: system (glibc)

attached base packages:
[1] stats     graphics  grDevices datasets  utils     methods   base     

other attached packages:
[1] BC45538_TLG_1.0.0       tlg_ctassets_0.0.0.9000 tibble_3.2.1            purrr_1.0.2             tidyr_1.3.1             emmeans_1.10.4          dplyr_1.1.4            

loaded via a namespace (and not attached):
 [1] Rdpack_2.6.1          mmrm_0.3.14           tern.mmrm_0.3.3       remotes_2.5.0         testthat_3.2.1.1      rlang_1.1.6           magrittr_2.0.3        compiler_4.4.3        roxygen2_7.3.2        callr_3.7.6           vctrs_0.6.5          
[12] formatters_0.5.11     stringr_1.5.1         pkgconfig_2.0.3       crayon_1.5.3          fastmap_1.2.0         backports_1.5.0       utf8_1.2.4            ps_1.8.0              oceanflow_1.5.0       bit_4.5.0             xfun_0.52            
[23] jsonlite_2.0.0        styler_1.10.3         broom_1.0.8           parallel_4.4.3        R6_2.5.1              stringi_1.8.4         RColorBrewer_1.1-3    parallelly_1.45.0     pkgload_1.4.0         brio_1.1.5            lubridate_1.9.3      
[34] estimability_1.5.1    Rcpp_1.0.13           rtables_0.6.13        assertthat_0.2.1      knitr_1.50            citril.metadata_0.1.8 usethis_3.0.0         R.utils_2.12.3        Matrix_1.7-0          splines_4.4.3         R.cache_0.16.0       
[45] timechange_0.3.0      tidyselect_1.2.1      yaml_2.3.10           processx_3.8.4        rlistings_0.2.12      pkgbuild_1.4.4        lattice_0.22-6        withr_3.0.1           evaluate_1.0.1        citril_0.1.18         desc_1.4.3           
[56] survival_3.8-3        xml2_1.3.6            pillar_1.9.0          checkmate_2.3.2       renv_1.0.10           rex_1.2.1             generics_0.1.3        rprojroot_2.0.4       hms_1.1.3             ggplot2_3.5.1         scales_1.4.0         
[67] xtable_1.8-4          chevron_0.2.12        glue_1.8.0            nestcolor_0.1.3       lazyeval_0.2.2        tools_4.4.3           data.table_1.17.8     forcats_1.0.0         fs_1.6.4              mvtnorm_1.3-1         cowplot_1.1.3        
[78] grid_4.4.3            cyclocomp_1.1.1       rbibutils_2.3         lintr_3.1.2           nlme_3.1-166          cli_3.6.5             gtsummary_2.5.0       dunlin_0.1.12         tern_0.9.9            fansi_1.0.6           gt_1.3.0             
[89] admiraldev_1.4.0.9000 arrow_17.0.0.1        gtable_0.3.6          R.methodsS3_1.8.2     admiral_1.4.1         digest_0.6.37         farver_2.1.2          htmltools_0.5.8.1     R.oo_1.26.0           lifecycle_1.0.4       bit64_4.5.2
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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at R/summarize_ancova.R around line 190 and reproduce the issue with a reference level containing +. Compare the contrast-label matching there with the direct emmeans result. Done means non-reference treatment columns contain lsmean_diff, lsmean_diff_ci, and pval for such labels, with the relevant tests passing.

Written by the indexing model from the issue text.

Assessment

Tech stack
r
Domain
analytics
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.