7  Patient reports with sections

You can download the RDS files by clicking on the following links:

Code
LIST_TBLS <- readRDS("data/LIST_TBLS.RDS")
LABELS <- readRDS("data/LABELS.RDS")

This chapter demonstrates how to create patient reports with custom sections in Word documents using the officer package. We’ll explore two approaches: using a single default section with page numbering, and using even and odd footers for more sophisticated layouts.

7.1 A single default section

In this first example, we’ll create patient reports with a consistent section layout throughout the document. The section will have custom margins and a footer displaying page numbers.

7.1.1 Setting up paths

First, we define the template file and output directory:

Code
path_to_template <- here("template", "template-02.docx")
sub_path <- here("output", "patient-profile", "default-section")
dir.create(sub_path, showWarnings = FALSE, recursive = TRUE)

7.1.2 Defining section properties

We create two section properties objects:

  • sect_properties_1: A portrait section with 1-inch margins and a footer displaying page numbers (e.g., “1 on 5 pages”)
  • sect_properties_2: A landscape section with 1-inch margins, useful for wide tables
Code
# Portrait section with page numbering footer
sect_properties_1 <- prop_section(
  page_margins = page_mar(top = 1, bottom = 1, left = 1, right = 1),
  type = "oddPage",
  footer_default = block_list(
    fpar(
      run_word_field(field = "PAGE \\* MERGEFORMAT"),
      " on ",
      run_word_field(field = "NumPages \\* MERGEFORMAT"),
      " pages"
    )
  )
)

# Landscape section for wide content
sect_properties_2 <- prop_section(
  page_size = page_size(orient = "landscape"),
  page_margins = page_mar(top = 1, bottom = 1, left = 1, right = 1),
  type = "oddPage"
)

The type = "oddPage" argument ensures that each section starts on a new page.

7.1.3 Generating individual patient reports

This loop creates one Word document per patient, with the following structure:

  1. Table of contents
  2. Subject-level analysis table (portrait)
  3. Vital signs graph (portrait)
  4. Vital signs table (landscape)
Code
for (i in seq_len(nrow(LIST_TBLS))) {
  # Create output filename for this patient
  USUBJID_STR <- LIST_TBLS[["USUBJID"]][i]
  file_basename <- paste0(USUBJID_STR, ".docx")
  fileout <- file.path(sub_path, file_basename)

  # Initialize document with table of contents
  doc <- read_docx(path = path_to_template)
  doc <- body_add_toc(doc)
  doc <- body_add_break(doc)

  # Add subject-level analysis table
  SL_TBL <- LIST_TBLS[["SL"]][[i]]
  tab1 <- as_flextable(SL_TBL, show_coltype = FALSE) |>
    labelizor(
      labels = pharmaverseadam::adsl |>
        labelled::get_variable_labels() |>
        unlist()
    ) |>
    padding(padding = 6)

  doc <- body_add_par(doc, value = "Subject Level Analysis", style = "heading 1")
  doc <- body_add_flextable(doc, tab1, align = "center")

  # Prepare metabolic data and create visualization
  METABOLIC_TBL <- LIST_TBLS[["METABOLIC"]][[i]] |>
    mutate(ADY = as.Date(ADY))

  gg <- ggplot(METABOLIC_TBL, aes(ADY, AVAL)) +
    geom_path() +
    scale_x_date(date_breaks = "4 weeks", date_labels = "%W") +
    facet_wrap(~PARAMCD, scales = "free_y") +
    labs(x = "Week", y = "Value")

  doc <- body_add_par(doc, value = "Vital Signs Analysis for Metabolic", style = "heading 1")
  doc <- body_add_par(doc, value = "Graphic", style = "heading 2")
  doc <- body_add_gg(doc, gg, style = "Graphic")

  # Create summary table for metabolic data
  mb_ft <- METABOLIC_TBL |>
    pivot_wider(
      id_cols = AVISIT,
      names_from = PARAMCD,
      values_from = AVAL
    ) |>
    flextable() |>
    theme_vanilla() |>
    colformat_double(digits = 2) |>
    labelizor(labels = LABELS)

  # End portrait section and start landscape section for table
  doc <- body_end_block_section(doc, value = block_section(property = sect_properties_1))
  doc <- body_add_par(doc, value = "Table", style = "heading 2")
  doc <- body_add_flextable(doc, mb_ft, align = "center")
  doc <- body_end_block_section(doc, value = block_section(property = sect_properties_2))

  # Set default section and document settings
  doc <- body_set_default_section(doc, sect_properties_1)
  doc <- docx_set_settings(doc, even_and_odd_headers = FALSE)

  # Save the document
  print(doc, target = fileout)
}

Click to download output/patient-profile/default-section/01-701-1015.docx.

It appears you don't have a PDF plugin for this browser.

7.2 Even and odd footers

This example demonstrates a more sophisticated approach with different footers for even and odd pages. This is commonly used in pharmaceutical reports to create a book-like layout where page numbers or other information appear differently on left and right pages.

7.2.1 Setting up paths

Code
path_to_template <- here("template", "template-02.docx")
sub_path <- file.path("output", "patient-profile", "even-odd-section")
dir.create(sub_path, showWarnings = FALSE, recursive = TRUE)

7.2.2 Defining section properties with even and odd footers

In this configuration:

  • Odd pages (right side) display: “1 on 5 pages”
  • Even pages (left side) display: “page 2”

This creates a mirrored effect typical of printed books or pharmaceutical reports.

Code
# Section with different footers for even and odd pages
sect_properties_1 <- prop_section(
  page_margins = page_mar(top = 1, bottom = 1, left = 1, right = 1),
  type = "oddPage",
  footer_default = block_list(
    fpar(
      run_word_field(field = "PAGE \\* MERGEFORMAT"),
      " on ",
      run_word_field(field = "NumPages \\* MERGEFORMAT"),
      " pages"
    )
  ),
  footer_even = block_list(
    fpar(
      "page ",
      run_word_field(field = "PAGE \\* MERGEFORMAT")
    )
  )
)

# Landscape section (reuses definition from previous example)
sect_properties_2 <- prop_section(
  page_size = page_size(orient = "landscape"),
  page_margins = page_mar(top = 1, bottom = 1, left = 1, right = 1),
  type = "oddPage"
)

7.2.3 Generating individual patient reports

The code is nearly identical to the first example, with one critical difference: we set even_and_odd_headers = TRUE in docx_set_settings(). This activates the different footers for even and odd pages.

Code
for (i in seq_len(nrow(LIST_TBLS))) {
  # Create output filename for this patient
  USUBJID_STR <- LIST_TBLS[["USUBJID"]][i]
  file_basename <- paste0(USUBJID_STR, ".docx")
  fileout <- file.path(sub_path, file_basename)

  # Initialize document with table of contents
  doc <- read_docx(path = path_to_template)
  doc <- body_add_toc(doc)
  doc <- body_add_break(doc)

  # Add subject-level analysis table
  SL_TBL <- LIST_TBLS[["SL"]][[i]]
  tab1 <- as_flextable(SL_TBL, show_coltype = FALSE) |>
    labelizor(
      labels = pharmaverseadam::adsl |>
        labelled::get_variable_labels() |>
        unlist()
    ) |>
    padding(padding = 6)

  doc <- body_add_par(doc, value = "Subject Level Analysis", style = "heading 1")
  doc <- body_add_flextable(doc, tab1, align = "center")

  # Prepare metabolic data and create visualization
  METABOLIC_TBL <- LIST_TBLS[["METABOLIC"]][[i]] |>
    mutate(ADY = as.Date(ADY))

  gg <- ggplot(METABOLIC_TBL, aes(ADY, AVAL)) +
    geom_path() +
    scale_x_date(date_breaks = "4 weeks", date_labels = "%W") +
    facet_wrap(~PARAMCD, scales = "free_y") +
    labs(x = "Week", y = "Value")

  doc <- body_add_par(doc, value = "Vital Signs Analysis for Metabolic", style = "heading 1")
  doc <- body_add_par(doc, value = "Graphic", style = "heading 2")
  doc <- body_add_gg(doc, gg, style = "Graphic")

  # Create summary table for metabolic data
  mb_ft <- METABOLIC_TBL |>
    pivot_wider(
      id_cols = AVISIT,
      names_from = PARAMCD,
      values_from = AVAL
    ) |>
    flextable() |>
    theme_vanilla() |>
    colformat_double(digits = 2) |>
    labelizor(labels = LABELS)

  # End portrait section and start landscape section for table
  doc <- body_end_block_section(doc, value = block_section(property = sect_properties_1))
  doc <- body_add_par(doc, value = "Table", style = "heading 2")
  doc <- body_add_flextable(doc, mb_ft, align = "center")
  doc <- body_end_block_section(doc, value = block_section(property = sect_properties_2))

  # Set default section and enable even/odd footers
  doc <- body_set_default_section(doc, sect_properties_1)
  doc <- docx_set_settings(doc, even_and_odd_headers = TRUE)

  # Save the document
  print(doc, target = fileout)
}

Click to download output/patient-profile/even-odd-section/01-701-1015.docx.

It appears you don't have a PDF plugin for this browser.

7.3 Reminder

7.3.1 Section management in officer

The officer package provides powerful tools for managing sections in Word documents:

  • prop_section(): Defines section properties including page size, margins, orientation, and headers/footers
  • body_end_block_section(): Ends the current section and applies new section properties
  • body_set_default_section(): Sets the default section properties for the entire document
  • docx_set_settings(): Configures document-wide settings like even/odd headers

7.3.2 Headers and footers

Section properties can include three types of headers and footers:

  • header_default / footer_default: Applied to odd pages (or all pages if even/odd is disabled)
  • header_even / footer_even: Applied to even pages when even_and_odd_headers = TRUE
  • header_first / footer_first: Applied to the first page of a section (when enabled)

These are defined using block_list() which can contain:

  • fpar(): Formatted paragraphs
  • run_word_field(): Word fields like PAGE, NumPages, DATE, etc.
  • Other text content