diff --git a/DESCRIPTION b/DESCRIPTION index 37b1a02..a83eb3a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -31,6 +31,7 @@ Imports: grid, methods Suggests: + dplyr, flextable (>= 0.8.0), ggplot2, gridGraphics, @@ -38,7 +39,9 @@ Suggests: gtable, knitr, magrittr, + random.cdisc.data, rmarkdown, + scales, spelling, testthat (>= 3.0.0) Collate: diff --git a/NEWS.md b/NEWS.md index 5b93988..56bd9bd 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,11 @@ # gridify 0.8.1.9000 +## Documentation + +* Added the "Posit Table Contest 2025: The Best Pharma Table" vignette + (`vignette("table_contest")`) showcasing the winning entry of the pharma + table category, which combines `gt` styling with `gridify` framing (#27). + # gridify 0.8.1 ## New features diff --git a/README.md b/README.md index cd0a8c5..df8b590 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,16 @@ For more information please visit the following vignettes: - **Transparency of gridify** `vignette("transparency", package = "gridify")` - How to extract the raw grid code to reproduce a `gridify` object. +- **Posit Table Contest 2025: The Best Pharma Table** + `vignette("table_contest", package = "gridify")` - The winning entry of the + pharma table category, combining `gt` styling with `gridify` regulatory framing. + +## Posit Table Contest 2025 + +A `gt` table framed with `gridify` won **The Best Pharma Table** category of the +[2025 Posit Table Contest](https://posit.co/blog/2025-table-contest-winners/). +See the `table_contest` vignette or the original code at +[Polkas/tgcontest2025](https://github.com/Polkas/tgcontest2025). ## Non Editable Outputs diff --git a/_pkgdown.yml b/_pkgdown.yml index 18b3009..fa9d6dd 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -16,6 +16,7 @@ articles: - multi_page_examples - create_custom_layout - transparency + - table_contest reference: - title: gridify Users diff --git a/inst/WORDLIST b/inst/WORDLIST index 6e2f2fb..64854d8 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -1,6 +1,7 @@ Abdy Acknowledgments Bleier +CDISC CMD Codecov Dallimore @@ -19,6 +20,7 @@ Pharmaverse Qmd RStudio Rmd +TLGs TransactionID Vicencio centered @@ -48,5 +50,6 @@ px rd rtables sprintf +tgcontest unitType vjust diff --git a/vignettes/table_contest.Rmd b/vignettes/table_contest.Rmd new file mode 100644 index 0000000..285df98 --- /dev/null +++ b/vignettes/table_contest.Rmd @@ -0,0 +1,206 @@ +--- +title: "Posit Table Contest 2025: The Best Pharma Table" +output: + rmarkdown::html_vignette: + toc: true + toc_depth: 4 +vignette: > + %\VignetteIndexEntry{Posit Table Contest 2025: The Best Pharma Table} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +editor_options: + markdown: + wrap: 72 +--- + +```{r, include = FALSE} +required_pkgs <- c("gt", "gtable", "dplyr", "scales", "random.cdisc.data") +has_pkgs <- all(vapply(required_pkgs, requireNamespace, logical(1), quietly = TRUE)) + +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + fig.width = 10, + fig.height = 8.7, + eval = has_pkgs +) +``` + +```{r, echo = FALSE, results = "asis", eval = !has_pkgs} +cat( + "> **Note:** The code in this vignette was not evaluated because one or more", + "of the following packages are not installed:", + paste0("`", required_pkgs, "`", collapse = ", "), "\n" +) +``` + +## Introduction + +This example won **The Best Pharma Table** category of the +[2025 Posit Table Contest](https://posit.co/blog/2025-table-contest-winners/). + +- Contest submission: + [Submission-Ready and Elegant Clinical Tables: Combining {gt} Styling with {gridify} Layout Control](https://github.com/rich-iannone/table-contest/discussions/18) +- Original code: [Polkas/tgcontest2025](https://github.com/Polkas/tgcontest2025) + +Clinical reporting requires tables that meet both statistical rigour and +regulatory standards. While pharmaverse packages handle the analysis well, +a gap remains in the consistent and reproducible framing of Tables, Listings, +and Graphs (TLGs) with the headers, footers, compliance notices, and metadata +required for submissions. + +This example combines `gt` for table styling with `gridify` for +regulatory-compliant framing. The table shows a CDISC ADSL baseline +characteristic, Biomarker 1 (`BMRKR1`), by treatment arm and geographic +region with: + +- summary statistics: n and Mean (SD), with gradient colour encoding, +- complete regulatory framing: protocol information, data cut-off, + confidentiality notice, and traceability. + +All data are synthetic, generated by the `random.cdisc.data` package. +No real patient data are used, and the company, study, and drug +information are fictional. + +## Setup + +```{r setup, message = FALSE} +library(gridify) +library(gt) +library(dplyr) + +primary_color <- "#08306b" +secondary_color <- "#6baed6" +``` + +## Data Preparation + +The ADSL dataset is summarised by treatment arm and region. +The `BMRKR1` column is stored as a factor ordered by the mean value, +so that the colour gradient applied later follows the mean. + +```{r data} +adsl <- random.cdisc.data::cadsl |> + mutate( + ARM = factor(ARM), + REGION1 = factor(REGION1) + ) + +summary_tbl <- adsl |> + group_by(ARM, REGION1) |> + summarise( + N = n(), + MEAN_BMRKR1 = round(mean(BMRKR1, na.rm = TRUE), 2), + SD_BMRKR1 = round(sd(BMRKR1, na.rm = TRUE), 2), + .groups = "drop" + ) |> + mutate( + BMRKR1 = sprintf("%0.2f (%0.2f)", MEAN_BMRKR1, SD_BMRKR1), + BMRKR1 = factor(BMRKR1, levels = unique(BMRKR1[order(MEAN_BMRKR1)])) + ) |> + select(ARM, REGION1, N, BMRKR1) + +summary_tbl +``` + +## The gt Table + +```{r gt_table} +color_palette <- scales::col_factor( + palette = c("white", secondary_color, primary_color), + domain = NULL +) + +gt_tbl <- summary_tbl |> + gt(rowname_col = NULL, groupname_col = "ARM") |> + cols_label( + REGION1 = "Region", + N = "n", + BMRKR1 = "BMRKR1" + ) |> + cols_width( + ARM ~ pct(30), + REGION1 ~ pct(30), + N ~ pct(10), + BMRKR1 ~ pct(30) + ) |> + cols_align( + align = "center", + columns = N + ) |> + tab_options( + table.width = pct(70), + table.font.size = 16, + data_row.padding = px(6), + table.font.color = "black", + table.font.weight = "400", + heading.align = "left", + table.background.color = "gray98", + heading.background.color = "gray98", + column_labels.background.color = "gray95", + row_group.font.weight = "bold", + row_group.as_column = TRUE + ) |> + opt_row_striping() |> + data_color(columns = BMRKR1, fn = color_palette) |> + data_color(columns = N, fn = color_palette) |> + tab_style( + style = cell_text(weight = "bold", color = primary_color), + locations = cells_column_labels() + ) +``` + +## Framing with gridify + +The `gt` table is passed to `gridify()` with the predefined +`pharma_layout_base()` layout. Custom margins and a monospaced font are +used, and the cells are filled with `set_cell()`. + +```{r gridify} +pharma_gt <- gridify( + gt_tbl, + layout = pharma_layout_base( + margin = grid::unit(c(t = 0.5, r = 0.5, b = 0.5, l = 0.5), "inch"), + global_gpar = list( + fontfamily = "mono", + fontsize = 14, + col = "black" + ) + ) +) |> + set_cell("header_left_1", "PharmaCorp International") |> + set_cell("header_left_2", "ONCOLOGY / Advanced NSCLC") |> + set_cell("header_left_3", "Study PC-2025-001") |> + set_cell("header_right_1", "CONFIDENTIAL") |> + set_cell("header_right_2", "Final") |> + set_cell("header_right_3", "Data Cut-off: 2025-09-30") |> + set_cell("output_num", "Table 14.1.1") |> + set_cell("title_1", "Summary of Demographics and Baseline Characteristics") |> + set_cell("title_2", "Baseline Biomarker 1 (BMRKR1) by Geographic Region") |> + set_cell("title_3", "Safety Analysis Set") |> + set_cell("note", "Note: BMRKR1 values shown as Mean (SD).") |> + set_cell("references", "Source: ADSL dataset random.cdisc.data") |> + set_cell("footer_left", "Program: table_posit.R, 2025-10-01 12:00 UTC") |> + set_cell("footer_right", "Page 1 of 1") + +pharma_gt +``` + +In a production program, the `footer_left` cell would typically contain +the run time, e.g. +`sprintf("Program: table_posit.R, %s", format(Sys.time(), "%Y-%m-%d %H:%M %Z"))`. + +## Export + +The final output can be exported to PNG or PDF with `export_to()`. + +```{r export, eval = FALSE} +pharma_gt |> + export_to("polkas_table_contest_2025.png", width = 3000, height = 2600, res = 300) + +pharma_gt |> + export_to("polkas_table_contest_2025.pdf") +``` + +To retrieve the raw `grid` code behind the output, see +`vignette("transparency", package = "gridify")`.