Advanced Clinical Reporting with officer and flextable
Create sophisticated Word reports programmatically with R
Overview
Intermediate Clinical Reporting Word Documents
Master the creation of sophisticated clinical reports in Word format using R with a reproducible approach. This workshop covers the complete journey from clinical data to pharmaceutical-grade report generation, focusing on advanced document structure management, complex table creation, and integration of ggplot2 visualizations.
What Youβll Learn
- π Document structure management in Word
- π Complex tables following pharmaceutical standards
- π ggplot2 integration for publication-ready figures
- π¨ Styling and formatting for professional outputs
- π Cross-references and automated numbering
- β Complete end-to-end CSR generation
Prerequisites
Required Knowledge:
- Intermediate R programming
- Basic understanding of clinical trial data
- Familiarity with data manipulation (dplyr)
Recommended:
- Experience with ggplot2
- Knowledge of clinical study report structure
Key Packages & Tools
{officer}
{flextable}
{ggplot2}
{officedown}
Microsoft Word
Workshop Materials
Workshop Website: https://ardata.fr/r-in-pharma-2025/
GitHub Code: https://github.com/ardata-fr/r-in-pharma-2025-codes
Full Tutorial: https://github.com/ardata-fr/r-in-pharma-reporting-with-officer-flextable
Part 1: Fundamentals
Introduction to {officer}
{officer} allows you to create and manipulate Word documents programmatically:
library(officer)
# Create a new document
doc <- read_docx()
# Add content
doc <- doc %>%
body_add_par("Clinical Study Report", style = "heading 1") %>%
body_add_par("Protocol ABC-123", style = "heading 2") %>%
body_add_par("This report summarizes the results of...",
style = "Normal")
# Save
print(doc, target = "clinical_report.docx")Introduction to {flextable}
{flextable} creates publication-ready tables with fine-grained control:
library(flextable)
library(dplyr)
# Create a demographic table
demographics %>%
flextable() %>%
set_header_labels(
age = "Age (years)",
sex = "Sex, n (%)",
race = "Race, n (%)"
) %>%
theme_vanilla() %>%
autofit()Part 2: Advanced Clinical Reporting
Document Structure Management
Creating a Structured CSR:
library(officer)
# Start with a template
doc <- read_docx("csr_template.docx")
# Add title page
doc <- doc %>%
body_add_par("CLINICAL STUDY REPORT", style = "Title") %>%
body_add_par(sprintf("Protocol: %s", protocol_id),
style = "Subtitle") %>%
body_add_break()
# Table of Contents (auto-updated)
doc <- doc %>%
body_add_toc(level = 3)
# Section structure
doc <- doc %>%
body_add_par("1. Synopsis", style = "heading 1") %>%
body_add_par("1.1 Study Objectives", style = "heading 2")Complex Table Creation
Pharmaceutical-Standard Tables:
library(flextable)
# Adverse events table with nested headers
create_ae_table <- function(data) {
data %>%
flextable() %>%
# Multi-level headers
add_header_row(
values = c("", "Treatment A", "Treatment B"),
colwidths = c(1, 2, 2)
) %>%
add_header_row(
values = c("System Organ Class", "n", "%", "n", "%"),
top = FALSE
) %>%
# Merge cells for SOC
merge_v(j = 1) %>%
# Styling
theme_booktabs() %>%
bold(part = "header") %>%
align(align = "center", part = "header") %>%
align(j = 1, align = "left", part = "body") %>%
# Borders
hline(i = ~ SOC_change, border = fp_border()) %>%
# Conditional formatting
bg(i = ~ rate > 10, bg = "#FFCCCC") %>%
# Footnotes
add_footer_lines("SOC = System Organ Class; n = number of subjects") %>%
autofit()
}Cross-References
Automated Table/Figure Numbering:
# Add bookmarked table
doc <- doc %>%
body_add_par("Demographic Characteristics",
style = "Table Caption") %>%
body_bookmark("table_demographics") %>%
body_add_flextable(demographics_table)
# Reference it elsewhere
doc <- doc %>%
body_add_par(
fpar(
ftext("As shown in Table "),
run_reference("table_demographics"),
ftext(", the treatment groups were well balanced...")
)
)Integrating ggplot2 Visualizations
Publication-Ready Figures:
library(ggplot2)
# Create plot
survival_plot <- ggplot(survival_data,
aes(x = time, y = surv, color = arm)) +
geom_step(linewidth = 1) +
geom_ribbon(aes(ymin = lower, ymax = upper, fill = arm),
alpha = 0.2) +
scale_color_manual(values = c("#0072B2", "#D55E00")) +
scale_fill_manual(values = c("#0072B2", "#D55E00")) +
labs(
title = "Kaplan-Meier Survival Curves",
x = "Time (months)",
y = "Survival Probability",
color = "Treatment Arm",
fill = "Treatment Arm"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5, face = "bold"),
legend.position = "bottom"
)
# Add to document
doc <- doc %>%
body_add_par("Figure 1: Overall Survival",
style = "Figure Caption") %>%
body_add_gg(value = survival_plot,
width = 6, height = 4) %>%
body_add_par("Shaded areas represent 95% confidence intervals.")Workshop Structure
Session 1: Basics (90 minutes)
- {officer} fundamentals
- {flextable} basics
- Simple document creation
- Basic styling
Session 2: Advanced Techniques (90 minutes)
- Complex table structures
- Section management
- Headers/footers customization
- Cross-reference handling
Session 3: Complete CSR Example (120 minutes)
- End-to-end workflow
- Template-based approach
- Automated report generation
- QC considerations
Practical Examples
Example 1: Demographics Table
create_demographics <- function(data) {
summary_stats <- data %>%
group_by(ARM) %>%
summarise(
N = n(),
Age_mean = mean(AGE, na.rm = TRUE),
Age_sd = sd(AGE, na.rm = TRUE),
Sex_M = sum(SEX == "M"),
Sex_F = sum(SEX == "F")
) %>%
mutate(
Age = sprintf("%.1f (%.1f)", Age_mean, Age_sd),
`Male, n (%)` = sprintf("%d (%.1f%%)",
Sex_M, 100 * Sex_M / N),
`Female, n (%)` = sprintf("%d (%.1f%%)",
Sex_F, 100 * Sex_F / N)
) %>%
select(ARM, N, Age, `Male, n (%)`, `Female, n (%)`)
summary_stats %>%
flextable() %>%
set_header_labels(ARM = "Treatment Arm") %>%
align(j = 2:5, align = "center", part = "all") %>%
bold(part = "header") %>%
theme_booktabs() %>%
autofit()
}Example 2: Adverse Events Summary
create_ae_summary <- function(ae_data) {
ae_summary <- ae_data %>%
group_by(AESOC, AEDECOD, TRTA) %>%
summarise(n = n_distinct(USUBJID)) %>%
ungroup() %>%
pivot_wider(names_from = TRTA, values_from = n,
values_fill = 0)
ae_summary %>%
flextable() %>%
set_header_labels(
AESOC = "System Organ Class",
AEDECOD = "Preferred Term"
) %>%
merge_v(j = "AESOC") %>%
valign(valign = "top") %>%
bold(j = "AESOC") %>%
theme_booktabs() %>%
autofit() %>%
add_footer_lines(
"Table includes treatment-emergent adverse events"
)
}Example 3: Complete Report Template
generate_csr <- function(data_list, output_path) {
doc <- read_docx("templates/csr_template.docx")
# Title page
doc <- doc %>%
body_add_par("CLINICAL STUDY REPORT", style = "Title") %>%
body_add_par(data_list$protocol_id, style = "Subtitle")
# ToC
doc <- doc %>%
body_add_break() %>%
body_add_toc()
# Demographics
doc <- doc %>%
body_add_break() %>%
body_add_par("Demographics", style = "heading 1") %>%
body_add_flextable(create_demographics(data_list$adsl))
# Efficacy
doc <- doc %>%
body_add_break() %>%
body_add_par("Efficacy Results", style = "heading 1") %>%
body_add_gg(create_efficacy_plot(data_list$adtte))
# Safety
doc <- doc %>%
body_add_break() %>%
body_add_par("Safety Results", style = "heading 1") %>%
body_add_flextable(create_ae_summary(data_list$adae))
# Save
print(doc, target = output_path)
}Best Practices
β Doβs
- Use templates for consistency
- Separate data processing from formatting
- Document your styling choices
- Test cross-references thoroughly
- Version control your templates
β Donβts
- Hard-code numbers in text
- Mix styling approaches
- Forget page breaks
- Ignore Word template styles
- Skip QC on final document
Advanced Features
Conditional Formatting
table %>%
flextable() %>%
bg(i = ~ p_value < 0.05, j = "p_value", bg = "yellow") %>%
bold(i = ~ significant == TRUE) %>%
color(i = ~ trend == "increasing", j = "value", color = "red")Custom Themes
my_pharma_theme <- function(ft) {
ft %>%
border_remove() %>%
hline_top(border = fp_border(width = 2)) %>%
hline_bottom(border = fp_border(width = 2)) %>%
font(fontname = "Arial", part = "all") %>%
fontsize(size = 10, part = "body") %>%
fontsize(size = 11, part = "header")
}Learning Outcomes
By the end of this workshop, you will be able to:
β
Create professional Word documents programmatically
β
Build complex, pharmaceutical-standard tables
β
Integrate ggplot2 visualizations seamlessly
β
Manage document structure with sections and breaks
β
Implement automated cross-referencing
β
Generate complete CSRs from R scripts
β
Apply best practices for reproducible reporting
Troubleshooting
Common Issues:
- Tables not fitting page width β Use
autofit()or manualwidth()settings - Plots appear pixelated β Increase DPI with
body_add_gg(width, height, res = 300) - Cross-references not updating β Right-click β βUpdate Fieldβ in Word
- Styles not applying β Ensure template has required style definitions
Integration with Other Tools
- {officedown}: R Markdown β Word via officer
- {gtsummary}: Statistical tables β flextable
- {officer} + {mschart}: Native Office charts
- {pharmaRTF}: Alternative for RTF output
Next Steps
After this workshop:
- Explore {officedown} for R Markdown integration
- Try {mschart} for editable Office charts
- Investigate {pharmaRTF} for RTF submissions
- Join ardata community for support
Similar Workshops
- Cardinal: Clinical Reporting - Standardized TLG templates
- Branded Documents with Quarto - Alternative approach
Next Steps
- For tables: See Cardinal workshop
- For multi-format: Try Quarto workshop
- Industry adoption: Automation trends
Last updated: November 2025 | R/Pharma 2025 Conference