Skip to content

Add gtsummary_to_reporter_clinical() with documentation, exports, and test coverage#183

Draft
dustreturn wants to merge 1 commit intoinsightsengineering:mainfrom
dustreturn:gtsummary2reporter
Draft

Add gtsummary_to_reporter_clinical() with documentation, exports, and test coverage#183
dustreturn wants to merge 1 commit intoinsightsengineering:mainfrom
dustreturn:gtsummary2reporter

Conversation

@dustreturn
Copy link

@dustreturn dustreturn commented Mar 13, 2026

Why this change
Clinical trial reporting requires strict table layout and output standards, especially for RTF and TXT deliverables.
This PR introduces gtsummary_to_reporter_clinical() to produce outputs that better align with clinical reporting style requirements, using reporter as the rendering engine.

reporter is a strong fit for this use case because it provides robust support for RTF/TXT generation and table-level formatting controls commonly needed in submission-oriented workflows. In practice, this workflow can serve as a near drop-in replacement for many traditional SAS PROC REPORT table delivery patterns.

What I changed
Added gtsummary_to_reporter_clinical() to convert gtsummary tables (or plain data frames) into clinical-style reporter tables.
Implemented support for:
RTF/TXT export
column label and width control
spanning headers
indentation and grouping behavior
pagination control
optional RDS export of processed output data and ARD
Added complete function documentation:
roxygen docs in R/gtsummary_to_reporter_clinical.r
manual page man/gtsummary_to_reporter_clinical.Rd
Updated examples to use real gtsummary input objects.
Exported the function in NAMESPACE.
Added reporter to Suggests in DESCRIPTION.
Added README documentation for the gtsummary -> reporter export workflow.
Added a NEWS entry for the new functionality.
Added test coverage to verify TXT export from a gtsummary object.
Why these implementation choices
Clinical style compliance: The function design focuses on formatting controls expected in clinical outputs.
Format fit: reporter natively supports high-quality RTF and TXT, making it well suited for table delivery pipelines.
Migration value: This setup helps teams move from SAS-style table production to an R-native workflow with minimal loss of reporting fidelity.
Package stability: Explicit namespace calls and documentation/test coverage improve maintainability and reproducibility.

Xiecheng Gu

… data.frame) into clinical-style reporter outputs, generating RTF/TXT reports with support for column label and width control, spanning headers, pagination, indentation/group formatting, auto title/footnote pickup from environment variables, and optional export of processed output data plus ARD as RDS files.
@github-actions
Copy link
Contributor


🎉 Thank you for your contribution! Before this PR can be accepted, we require that you read and agree to our Contributor License Agreement.
You can digitally sign the CLA by posting a comment on this Pull Request in the format shown below. This agreement will apply to this PR as well as all future contributions on this repository.


I have read the CLA Document and I hereby sign the CLA


Xiecheng Gu seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

group_columns = NULL,
group_blank_after = TRUE) {

strip_md_bold <- function(x) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

helper functions should be outside the main function body and with a bit of documentation

#' @export


gtsummary_to_reporter_clinical <- function(gts_obj, file_path = "Clinical_Report.rtf",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name of this function is not very intuitive. I would change it to something more readable like export_gts_to_rtf or save or tbl_to_rtf or similar

max_table_width = NULL, min_col_width = 0.6,
column_widths = NULL,
column_labels = NULL,
spanning_headers = NULL,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spanning headers can be added in gtsummary. This parameter is not needed

gtsummary_to_reporter_clinical <- function(gts_obj, file_path = "Clinical_Report.rtf",
max_table_width = NULL, min_col_width = 0.6,
column_widths = NULL,
column_labels = NULL,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also column labels can be defined in gtsummary. not needed

report_margins = NULL,
report_font_size = 9,
indent_unit = 1,
output_types = c("RTF", "TXT"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the output extension should decide this

Comment on lines +131 to +132
debug_indent = FALSE,
debug_spanning = FALSE,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these should NOT be user-facing. There are many tools to do debugging without adding special handling

Comment on lines +133 to +134
group_columns = NULL,
group_blank_after = TRUE) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

difficult to understand what these do. blank rows can be added in gtsummary

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a user’s perspective, I have a specific reason for using the reporter parameter: for instance, when multiple gtsummary objects are stacked together, manually calling add_blank_row for each one can be quite tedious. The reporter package offers an option to automatically insert blank rows based on groups without affecting the core content.

But I agree that, in principle, it might be more appropriate to handle all table-building within gtsummary itself.

Comment on lines +1 to +13
# =============================================================================
# Script: gtsummary_to_reporter_clinical
#
# Packages referenced:
# - gtsummary: table building and styling
# - reporter: RTF/TXT report generation
# - tidyverse: dplyr/tidyr helpers used in processing
#
# Function arguments (gtsummary_to_reporter_clinical):
# - gts_obj: gtsummary object or plain data.frame to export
# - file_path: output path (extension determines RTF/TXT names)
# - max_table_width: max width (inches/cm) for all columns combined
# - min_col_width: minimum width for any column
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part comes from a copy-paste and should not be here

Comment on lines +52 to +55
#' Convert a `gtsummary` object (or a plain `data.frame`) into a `reporter`
#' table and write RTF and/or TXT outputs. The function is designed for
#' clinical reporting workflows and supports column labels, spanning headers,
#' pagination, indentation handling, and optional export of intermediate data.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The concept of reporter output is a too specific. I would keep the terminology general and bound to the output format

Comment on lines +60 to +66
#' @param gts_obj A `gtsummary` object (with `table_body`/`table_styling`) or
#' a plain `data.frame` to export.
#' @param file_path Output path. The extension is ignored and output files are
#' written according to `output_types`.
#' @param max_table_width Maximum total table width. If `NULL`, derived from
#' `report_paper_size`, `report_orientation`, `report_units`, and
#' `report_margins`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong parameter doc formats

}

# A. Extract data and styling metadata
has_table_body <- is.data.frame(gts_obj) && "table_body" %in% names(gts_obj)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

input checks are MISSING

footnotes_vec <- strip_md_bold(footnotes_vec)


resolve_rows <- function(row_spec, data) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not very clear what this function does? I would suggest more comments and a better function flow

Comment on lines +66 to +68

### Export gtsummary tables to reporter RTF/TXT

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not repeat this here. Also, it is not the main output for everyone

Copy link
Contributor

@Melkiades Melkiades left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your contribution! :)

The main function has too many subfunctions added inside of it without enough comments and lacking a bit of scoping clarity. This renders everything difficult to review. Please also reformat the PR text in a clear way.

Also remember, before any coding work it is good practice to create an issue with clear goals and implementation ideas. There, for example, we could have discussed the existence of a possible better place for this PR in the {pager} package. That may be a cleaner solution to output rtf

@Melkiades Melkiades marked this pull request as draft March 16, 2026 09:28
@Melkiades
Copy link
Contributor

@dustreturn consider also adding PRs as draft if not ready to review ;)

Comment on lines +114 to +115


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

@Melkiades
Copy link
Contributor

btw have you checked if some of these work for you? I checked briefly {reporter} and seems nice but I wonder if we could go with something with a bit more user pool maybe?

image

@dustreturn
Copy link
Author

Thanks for your contribution! :)

The main function has too many subfunctions added inside of it without enough comments and lacking a bit of scoping clarity. This renders everything difficult to review. Please also reformat the PR text in a clear way.

Also remember, before any coding work it is good practice to create an issue with clear goals and implementation ideas. There, for example, we could have discussed the existence of a possible better place for this PR in the {pager} package. That may be a cleaner solution to output rtf

Thank you! I will optimize the function based on your comments above.

@dustreturn
Copy link
Author

dustreturn commented Mar 16, 2026

btw have you checked if some of these work for you? I checked briefly {reporter} and seems nice but I wonder if we could go with something with a bit more user pool maybe?

image

I originally intended to use one of them as the output method. flextable, in particular, aligns well with the required formats for clinical reports. Other options are not that good.

While flextable needs to be paired with officer, there are persistent issues like pagination. I’ve previously consulted on stackoverflow https://stackoverflow.com/questions/79774759/how-to-further-fine-tune-the-rtf-output-of-gtsummary-when-pagination-is-needed. It seemed {pager} might solve this, but upon closer review, it primarily supports .docx and lacks robust support for RTF/TXT formats.

In contrast, the {reporter} package covers almost all standard clinical reporting requirements (comparable to SAS PROC REPORT). From a clinical programmer's perspective, there’s hardly a better alternative. That’s why I tried to explore the possibility of integrating these two packages.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants