# Formatting tables

# Introduction

The visOmopResults package provides user-friendly tools for creating well-formatted tables and plots that are publication-ready. In this vignette, we focus specifically on the table formatting functionalities. The package supports three table formats: `<tibble>`, `<gt>`, and `<flextable>`. While `<tibble>` is an `<data.frame>` R object, `<gt>` and `<flextable>` are designed to create publication-ready tables that can be exported to different formats (e.g., PNG, Word, PDF, HTML) and used in ShinyApps, RMarkdown, Quarto, and more.

Although the primary aim of the package is to simplify the handling of the `<summarised_result>` class (see omopgenerics for more details), its functionalities can be applied to any `<data.frame>` if certain requirements are met.

## Types of Table Functions

There are two main categories of table functions in the package:

- Main Table Functions: Comprehensive functions like `visOmopTable()` and `visTable()` allow users to fully format tables, including specifying headers, grouping columns, and customizing styles.

- Additional Table Formatting Functions: The `format` function set provides more granular control over specific table elements, enabling advanced customization beyond the main functions.

This vignette will guide you through the usage of these functions.

# Main Functions

These functions are built on top of the `format` functions, providing a quick and straightforward way to format tables.

## visTable()

`visTable()` is a flexible function designed to format any `<data.frame>`.

Let’s demonstrate its usage with a dataset from the `palmerpenguins` package.

    library(visOmopResults)
    library(palmerpenguins)
    library(dplyr)
    library(tidyr)
    x <- penguins |>
      filter(!is.na(sex) & year == 2008) |>
      select(!"body_mass_g") |>
      summarise(across(ends_with("mm"), ~mean(.x)), .by = c("species", "island", "sex"))
    head(x)
    #> # A tibble: 6 × 6
    #>   species island    sex    bill_length_mm bill_depth_mm flipper_length_mm
    #>   <fct>   <fct>     <fct>           <dbl>         <dbl>             <dbl>
    #> 1 Adelie  Biscoe    female           36.6          17.2              187.
    #> 2 Adelie  Biscoe    male             40.8          19.0              193.
    #> 3 Adelie  Torgersen female           36.6          17.4              190
    #> 4 Adelie  Torgersen male             40.9          18.8              194.
    #> 5 Adelie  Dream     female           36.3          17.8              189
    #> 6 Adelie  Dream     male             40.1          18.9              195

We can format this data into a `<gt>` table using `visTable()` as follows:

    visTable(
      result = x,
      groupColumn = c("sex"),
      rename = c("Bill length (mm)" = "bill_length_mm",
                 "Bill depth (mm)" = "bill_depth_mm",
                 "Flipper length (mm)" = "flipper_length_mm",
                 "Body mass (g)" = "body_mass_g"),
      type = "gt",
      hide = "year"
    )

Species | Island | Bill length (mm) | Bill depth (mm) | Flipper length (mm)
--- | --- | --- | --- | ---
female
Adelie | Biscoe | 36.6444444444444 | 17.2222222222222 | 186.555555555556
| Torgersen | 36.6125 | 17.4 | 190
| Dream | 36.275 | 17.7875 | 189
Gentoo | Biscoe | 45.2954545454545 | 14.1318181818182 | 213
Chinstrap | Dream | 46 | 17.3 | 192.666666666667
male
Adelie | Biscoe | 40.7555555555556 | 19.0333333333333 | 192.555555555556
| Torgersen | 40.925 | 18.8375 | 193.5
| Dream | 40.1125 | 18.8875 | 195
Gentoo | Biscoe | 48.5391304347826 | 15.704347826087 | 222.086956521739
Chinstrap | Dream | 51.4 | 19.6 | 202.777777777778

To use the arguments `estimateName` and `header`, the `<data.frame>` must have the estimates arranged into three columns: `estimate_name`, `estimate_type`, and `estimate_value`. Let’s reshape the example dataset accordingly and demonstrate creating a `<flextable>` object:

    # Transforming the dataset to include estimate columns
    x <- x |>
      pivot_longer(
        cols = ends_with("_mm"),
        names_to = "estimate_name",
        values_to = "estimate_value"
      ) |>
      mutate(estimate_type = "numeric")

    # Creating a formatted flextable
    visTable(
      result = x,
      estimateName = c(
        "Bill length (mm)" = "<bill_length_mm>",
        "Bill depth (mm)" = "<bill_depth_mm>",
        "Flipper length (mm)" = "<flipper_length_mm>"
      ),
      header = c("species", "island"),
      groupColumn = "sex",
      type = "flextable",
      hide = c("year", "estimate_type")
    )

Sex | Estimate name | Species
--- | --- | ---
Adelie | Gentoo | Chinstrap
Island
Biscoe | Torgersen | Dream | Biscoe | Dream
female |
| Bill length (mm) | 36.64 | 36.61 | 36.27 | 45.30 | 46.00
| Bill depth (mm) | 17.22 | 17.40 | 17.79 | 14.13 | 17.30
| Flipper length (mm) | 186.56 | 190.00 | 189.00 | 213.00 | 192.67
male |
| Bill length (mm) | 40.76 | 40.92 | 40.11 | 48.54 | 51.40
| Bill depth (mm) | 19.03 | 18.84 | 18.89 | 15.70 | 19.60
| Flipper length (mm) | 192.56 | 193.50 | 195.00 | 222.09 | 202.78

## visOmopTable()

`visOmopTable()` extends the functionality of `visTable()` with additional features tailored specifically for handling `<summarised_result>` objects, making it easier to work with standardized result formats.

Let’s demonstrate `visOmopTable()` with a mock `<summarised_result>`:

    # Creating a mock summarised result
    result <- mockSummarisedResult() |>
      filter(strata_name == "age_group &&& sex")

    # Displaying the first few rows
    head(result)
    #> # A tibble: 6 × 13
    #>   result_id cdm_name group_name  group_level strata_name       strata_level
    #>       <int> <chr>    <chr>       <chr>       <chr>             <chr>
    #> 1         1 mock     cohort_name cohort1     age_group &&& sex <40 &&& Male
    #> 2         1 mock     cohort_name cohort1     age_group &&& sex >=40 &&& Male
    #> 3         1 mock     cohort_name cohort1     age_group &&& sex <40 &&& Female
    #> 4         1 mock     cohort_name cohort1     age_group &&& sex >=40 &&& Female
    #> 5         1 mock     cohort_name cohort2     age_group &&& sex <40 &&& Male
    #> 6         1 mock     cohort_name cohort2     age_group &&& sex >=40 &&& Male
    #> # ℹ 7 more variables: variable_name <chr>, variable_level <chr>,
    #> #   estimate_name <chr>, estimate_type <chr>, estimate_value <chr>,
    #> #   additional_name <chr>, additional_level <chr>

    # Creating a formatted gt table
    visOmopTable(
      result = result,
      estimateName = c(
        "N%" = "<count> (<percentage>)",
        "N" = "<count>",
        "Mean (SD)" = "<mean> (<sd>)"
      ),
      header = c("package_name", "age_group"),
      groupColumn = c("cohort_name", "sex"),
      settingsColumn = "package_name",
      type = "gt"
    )

| Package name
--- | ---
| visOmopResults
CDM name | Variable name | Variable level | Estimate name | Age group
<40 | >=40
cohort1; Male
mock | number subjects | - | N | 784,601 | 9,851,372
| age | - | Mean (SD) | 93.80 (10.00) | 64.78 (1.53)
| Medications | Amoxiciline | N% | 99,507 (68.12) | 47,175 (47.04)
|  | Ibuprofen | N% | 77,565 (41.66) | 25,835 (14.54)
cohort1; Female
mock | number subjects | - | N | 9,420,078 | 1,427,126
| age | - | Mean (SD) | 98.31 (1.77) | 22.17 (9.81)
| Medications | Amoxiciline | N% | 34,405 (81.59) | 40,824 (33.95)
|  | Ibuprofen | N% | 88,172 (78.74) | 20,623 (31.60)
cohort2; Male
mock | number subjects | - | N | 8,181,325 | 9,777,084
| age | - | Mean (SD) | 12.31 (5.06) | 87.74 (4.53)
| Medications | Amoxiciline | N% | 70,148 (19.82) | 52,132 (98.43)
|  | Ibuprofen | N% | 97,663 (44.14) | 12,863 (3.68)
cohort2; Female
mock | number subjects | - | N | 238,273 | 5,907,094
| age | - | Mean (SD) | 11.39 (4.17) | 98.27 (4.34)
| Medications | Amoxiciline | N% | 89,174 (90.96) | 51,025 (39.12)
|  | Ibuprofen | N% | 58,384 (35.15) | 63,295 (2.51)

The workflow is quite similar to `visTable()`, but it includes specific enhancements for `<summarised_result>` objects:

- Automatic splitting: The result object is always processed using the `splitAll()` function. Thereby, column names to use in other arguments must be based on the split result.

- `settingsColumn` argument: Use this argument to specify which settings should be displayed in the main table. The columns specified here can also be referenced in other arguments such as `header`, `rename`, and `groupColumn.`

- `header` argument: accepts specific `<summarised_result>` inputs, in addition to its typical usage as in `visTable()`. For example, use “strata” in the header to display all variables in `strata_name`, or use “settings” to show all settings specified in `settingsColumns.`

- Hidden columns: result_id and estimate_type columns are always hidden as they serve as helper columns for internal processes.

- Suppressed estimates: if the result object has been processed with suppress(), obscured estimates can be displayed as the default `na` value or as “<{minCellCount}” with the corresponding minimum count value used. This can be controlled using the `showMinCellCount` argument.

In the next example, `visOmopTable()` generates a `<gt>` table while displaying suppressed estimates (those with counts below 1,000,000) with the specified minimum cell count.

    result |>
      suppress(minCellCount = 1000000) |>
      visOmopTable(
        estimateName = c(
          "N%" = "<count> (<percentage>)",
          "N" = "<count>",
          "Mean (SD)" = "<mean> (<sd>)"
        ),
        header = c("My visOmopTable", "group"),
        groupColumn = c("strata"),
        hide = c("cdm_name"),
        showMinCellCount = TRUE,
        type = "gt"
      )

| My visOmopTable
--- | ---
Variable name | Variable level | Estimate name | Cohort name
cohort1 | cohort2
<40; Male
number subjects | - | N | <1,000,000 | 8,181,325
age | - | Mean (SD) | <1,000,000 (<1,000,000) | 12.31 (5.06)
Medications | Amoxiciline | N% | <1,000,000 (<1,000,000) | <1,000,000 (<1,000,000)
| Ibuprofen | N% | <1,000,000 (<1,000,000) | <1,000,000 (<1,000,000)
>=40; Male
number subjects | - | N | 9,851,372 | 9,777,084
age | - | Mean (SD) | 64.78 (1.53) | 87.74 (4.53)
Medications | Amoxiciline | N% | <1,000,000 (<1,000,000) | <1,000,000 (<1,000,000)
| Ibuprofen | N% | <1,000,000 (<1,000,000) | <1,000,000 (<1,000,000)
<40; Female
number subjects | - | N | 9,420,078 | <1,000,000
age | - | Mean (SD) | 98.31 (1.77) | <1,000,000 (<1,000,000)
Medications | Amoxiciline | N% | <1,000,000 (<1,000,000) | <1,000,000 (<1,000,000)
| Ibuprofen | N% | <1,000,000 (<1,000,000) | <1,000,000 (<1,000,000)
>=40; Female
number subjects | - | N | 1,427,126 | 5,907,094
age | - | Mean (SD) | 22.17 (9.81) | 98.27 (4.34)
Medications | Amoxiciline | N% | <1,000,000 (<1,000,000) | <1,000,000 (<1,000,000)
| Ibuprofen | N% | <1,000,000 (<1,000,000) | <1,000,000 (<1,000,000)

## Styling tables

Tables displayed in `visOmopResults()` follow a default style, but customization is possible through the `.options` argument. This argument allows users to modify various formatting aspects using options from the `format` functions (see the `format` Functions section to learn more).

The table below details which `format` function each styling option belongs to, along with a description of each option:

Argument | Description
--- | ---
formatEstimateValue()
decimals | Number of decimals per estimate type (integer, numeric, percentage, proportion), estimate name, or all estimate values (introduce the number of decimals).
decimalMark | Decimal separator mark.
bigMark | Thousand and millions separator mark.
formatEstimateName()
keepNotFormatted | Whether to keep rows not formatted.
useFormatOrder | Whether to use the order in which estimate names appear in the estimateName (TRUE), or use the order in the input dataframe (FALSE).
formatHeader()
delim | Delimiter to use to separate headers.
includeHeaderName | Whether to include the column name as header.
includeHeaderKey | Whether to include the header key (header, header_name, header_level) before each header type in the column names.
formatTable()

style

Named list that specifies how to style the different parts of the gt or flextable table generated. Accepted style entries are: title, subtitle, header, header_name, header_level, column_name, group_label, and body. Alternatively, use 'default' to get visOmopResults style, or NULL for gt/flextable style. Keep in mind that styling code is different for gt and flextable. To see the 'deafult' gt style code use gtStyle(), and flextableStyle() for flextable default code style

na | How to display missing values.
title | Title of the table, or NULL for no title.
subtitle | Subtitle of the table, or NULL for no subtitle.
caption | Caption for the table, or NULL for no caption. Text in markdown formatting style (e.g. ⁠*Your caption here*⁠ for caption in italics)
groupAsColumn | Whether to display the group labels as a column (TRUE) or rows (FALSE).
groupOrder | Order in which to display group labels.
merge | Names of the columns to merge vertically when consecutive row cells have identical values. Alternatively, use 'all_columns' to apply this merging to all columns, or use NULL to indicate no merging.

To view the default `.options` settings used in `vis` tables, use the following function:

    tableOptions()
    #> $decimals
    #>    integer percentage    numeric proportion
    #>          0          2          2          2
    #>
    #> $decimalMark
    #> [1] "."
    #>
    #> $bigMark
    #> [1] ","
    #>
    #> $keepNotFormatted
    #> [1] TRUE
    #>
    #> $useFormatOrder
    #> [1] TRUE
    #>
    #> $delim
    #> [1] "\n"
    #>
    #> $includeHeaderName
    #> [1] TRUE
    #>
    #> $includeHeaderKey
    #> [1] TRUE
    #>
    #> $style
    #> [1] "default"
    #>
    #> $na
    #> [1] "-"
    #>
    #> $title
    #> NULL
    #>
    #> $subtitle
    #> NULL
    #>
    #> $caption
    #> NULL
    #>
    #> $groupAsColumn
    #> [1] FALSE
    #>
    #> $groupOrder
    #> NULL
    #>
    #> $merge
    #> [1] "all_columns"

### Styling `<gt>` and `<flextable>`

To inspect the code for the default styles of `<gt>` and `<flextable>`, use these functions:

    tableStyle(type = "gt")
    #> list(header = list(gt::cell_fill(color = "#c8c8c8"), gt::cell_text(weight = "bold",
    #>     align = "center")), header_name = list(gt::cell_fill(color = "#d9d9d9"),
    #>     gt::cell_text(weight = "bold", align = "center")), header_level = list(gt::cell_fill(color = "#e1e1e1"),
    #>     gt::cell_text(weight = "bold", align = "center")), column_name = list(gt::cell_text(weight = "bold",
    #>     align = "center")), group_label = list(gt::cell_fill(color = "#e9e9e9"),
    #>     gt::cell_text(weight = "bold")), title = list(gt::cell_text(weight = "bold",
    #>     size = 15, align = "center")), subtitle = list(gt::cell_text(weight = "bold",
    #>     size = 12, align = "center")), body = list())

    tableStyle(type = "flextable")
    #> list(header = list(cell = officer::fp_cell(background.color = "#c8c8c8"),
    #>     text = officer::fp_text(bold = TRUE)), header_name = list(cell = officer::fp_cell(background.color = "#d9d9d9"),
    #>     text = officer::fp_text(bold = TRUE)), header_level = list(cell = officer::fp_cell(background.color = "#e1e1e1"),
    #>     text = officer::fp_text(bold = TRUE)), column_name = list(text = officer::fp_text(bold = TRUE)),
    #>     group_label = list(cell = officer::fp_cell(background.color = "#e9e9e9",
    #>         border = officer::fp_border(color = "gray")), text = officer::fp_text(bold = TRUE)),
    #>     title = list(text = officer::fp_text(bold = TRUE, font.size = 15)),
    #>     subtitle = list(text = officer::fp_text(bold = TRUE, font.size = 12)),
    #>     body = list())

# `format` Functions

The `format` set of functions can be used in a pipeline to transform and format a `<data.frame>` or a `<summarised_result>` object. Below, we’ll demonstrate how to utilize these functions in a step-by-step manner.

## 1) Format Estimates

The `formatEstimateName()` and `formatEstimateValue()` functions enable you to customize the naming and display of estimates in your table.

To illustrate their usage, we’ll continue with the `result` dataset. Let’s first take a look at some of the estimates before any formatting is applied:

    result |>
      filterGroup(cohort_name == "cohort1") |>  # visOmopResult filter function
      filterStrata(age_group == "<40", sex == "Female") |>  # visOmopResult filter function
      select(variable_name, variable_level, estimate_name, estimate_type, estimate_value)
    #> # A tibble: 7 × 5
    #>   variable_name   variable_level estimate_name estimate_type estimate_value
    #>   <chr>           <chr>          <chr>         <chr>         <chr>
    #> 1 number subjects <NA>           count         integer       9420078
    #> 2 age             <NA>           mean          numeric       98.3123447746038
    #> 3 age             <NA>           sd            numeric       1.77259688032791
    #> 4 Medications     Amoxiciline    count         integer       34405
    #> 5 Medications     Amoxiciline    percentage    percentage    81.5916808089241
    #> 6 Medications     Ibuprofen      count         integer       88172
    #> 7 Medications     Ibuprofen      percentage    percentage    78.7406872026622

### 1.1) Estimate values

The `formatEstimateValue()` function allows you to specify the number of decimals for different `estimate_types` or `estimate_names`, as well as customize decimal and thousand separators.

Let’s see how the previous estimates are updated afterwars:

    # Formatting estimate values
    result <- result |>
      formatEstimateValue(
        decimals = c(integer = 0, numeric = 4, percentage = 2),
        decimalMark = ".",
        bigMark = ","
      )

    # Displaying the formatted subset
    result |>
      filterGroup(cohort_name == "cohort1") |>
      filterStrata(age_group == "<40", sex == "Female") |>
      select(variable_name, variable_level, estimate_name, estimate_type, estimate_value)
    #> # A tibble: 7 × 5
    #>   variable_name   variable_level estimate_name estimate_type estimate_value
    #>   <chr>           <chr>          <chr>         <chr>         <chr>
    #> 1 number subjects <NA>           count         integer       9,420,078
    #> 2 age             <NA>           mean          numeric       98.3123
    #> 3 age             <NA>           sd            numeric       1.7726
    #> 4 Medications     Amoxiciline    count         integer       34,405
    #> 5 Medications     Amoxiciline    percentage    percentage    81.59
    #> 6 Medications     Ibuprofen      count         integer       88,172
    #> 7 Medications     Ibuprofen      percentage    percentage    78.74

As you can see, the estimates now reflect the specified formatting rules.

### 1.2) Estimate names

Next, we will format the estimate names using the `formatEstimateName()` function. This function allows us to combine counts and percentages as “N (%)”, among other estimate combinations

    # Formatting estimate names
    result <- result |>
      formatEstimateName(
        estimateName = c(
          "N (%)" = "<count> (<percentage>%)",
          "N" = "<count>",
          "Mean (SD)" = "<mean> (<sd>)"
        ),
        keepNotFormatted = TRUE,
        useFormatOrder = FALSE
      )

    # Displaying the formatted subset with new estimate names
    result |>
      filterGroup(cohort_name == "cohort1") |>
      filterStrata(age_group == "<40", sex == "Female") |>
      select(variable_name, variable_level, estimate_name, estimate_type, estimate_value)
    #> # A tibble: 4 × 5
    #>   variable_name   variable_level estimate_name estimate_type estimate_value
    #>   <chr>           <chr>          <chr>         <chr>         <chr>
    #> 1 number subjects <NA>           N             character     9,420,078
    #> 2 age             <NA>           Mean (SD)     character     98.3123 (1.7726)
    #> 3 Medications     Amoxiciline    N (%)         character     34,405 (81.59%)
    #> 4 Medications     Ibuprofen      N (%)         character     88,172 (78.74%)

Now, the estimate names are displayed as specified, such as “N (%)” for counts and percentages. The `keepNotFormatted` argument ensures that unformatted rows remain in the dataset, while `useFormatOrder` allows control over the display order of the estimates.

## 2) Format Header

`formatHeader()` is used to create complex multi-level headers for tables, making it easy to present grouped data clearly.

### Header levels

There are 3 different levels of headers, each identified with the following keys:

- `header`: Custom labels that do not correspond to column names or table values.

- `header_name`: Labels derived from column names. Can be omitted with `includeHeaderName = FALSE`.

- `header_level`: Labels derived from values within columns set in `header`.

These keys, together with a delimiter between header levels (`delim`) are used in `formatTable()` to format and style `gt` or `flextable` tables.

Let’s create a multi-level header for the strata columns, including all three keys. This will show how the column names are transformed:

    result |>
      mutate(across(c("strata_name", "strata_level"), ~ gsub("&&&", "and", .x))) |>
      formatHeader(
        header = c("Stratifications", "strata_name", "strata_level"),
        delim = "\n",
        includeHeaderName = TRUE,
        includeHeaderKey = TRUE
      ) |>
      colnames()
    #>  [1] "result_id"
    #>  [2] "cdm_name"
    #>  [3] "group_name"
    #>  [4] "group_level"
    #>  [5] "variable_name"
    #>  [6] "variable_level"
    #>  [7] "estimate_name"
    #>  [8] "estimate_type"
    #>  [9] "additional_name"
    #> [10] "additional_level"
    #> [11] "[header]Stratifications\n[header_name]strata_name\n[header_level]age_group and sex\n[header_name]strata_level\n[header_level]<40 and Male"
    #> [12] "[header]Stratifications\n[header_name]strata_name\n[header_level]age_group and sex\n[header_name]strata_level\n[header_level]>=40 and Male"
    #> [13] "[header]Stratifications\n[header_name]strata_name\n[header_level]age_group and sex\n[header_name]strata_level\n[header_level]<40 and Female"
    #> [14] "[header]Stratifications\n[header_name]strata_name\n[header_level]age_group and sex\n[header_name]strata_level\n[header_level]>=40 and Female"

For the table we are formatting, we won’t include the `header_name` labels. Let’s see how it looks when we exclude them:

    result <- result |>
      mutate(across(c("strata_name", "strata_level"), ~ gsub("&&&", "and", .x))) |>
      formatHeader(
        header = c("Stratifications", "strata_name", "strata_level"),
        delim = "\n",
        includeHeaderName = FALSE,
        includeHeaderKey = TRUE
      )

    colnames(result)
    #>  [1] "result_id"
    #>  [2] "cdm_name"
    #>  [3] "group_name"
    #>  [4] "group_level"
    #>  [5] "variable_name"
    #>  [6] "variable_level"
    #>  [7] "estimate_name"
    #>  [8] "estimate_type"
    #>  [9] "additional_name"
    #> [10] "additional_level"
    #> [11] "[header]Stratifications\n[header_level]age_group and sex\n[header_level]<40 and Male"
    #> [12] "[header]Stratifications\n[header_level]age_group and sex\n[header_level]>=40 and Male"
    #> [13] "[header]Stratifications\n[header_level]age_group and sex\n[header_level]<40 and Female"
    #> [14] "[header]Stratifications\n[header_level]age_group and sex\n[header_level]>=40 and Female"

## 3) Format Table

`formatTable()` function is the final step in the formatting pipeline, where the formatted `<data.frame>` is converted to either a `<gt>` or `<flextable>`.

### Prepare data

Before using `formatTable()`, we’ll tidy the `<data.frame>` by splitting the group and additional name-level columns (see vignette on tidying `<summarised_result>`), and drop some unwanted columns:

    result <- result |>
      splitGroup() |>
      splitAdditional() |>
      select(!c("result_id", "estimate_type", "cdm_name"))
    head(result)
    #> # A tibble: 6 × 8
    #>   cohort_name variable_name  variable_level estimate_name [header]Stratificati…¹
    #>   <chr>       <chr>          <chr>          <chr>         <chr>
    #> 1 cohort1     number subjec… <NA>           N             784,601
    #> 2 cohort2     number subjec… <NA>           N             8,181,325
    #> 3 cohort1     age            <NA>           Mean (SD)     93.8045 (9.9969)
    #> 4 cohort2     age            <NA>           Mean (SD)     12.3128 (5.0593)
    #> 5 cohort1     Medications    Amoxiciline    N (%)         99,507 (68.12%)
    #> 6 cohort2     Medications    Amoxiciline    N (%)         70,148 (19.82%)
    #> # ℹ abbreviated name:
    #> #   ¹​`[header]Stratifications\n[header_level]age_group and sex\n[header_level]<40 and Male`
    #> # ℹ 3 more variables:
    #> #   `[header]Stratifications\n[header_level]age_group and sex\n[header_level]>=40 and Male` <chr>,
    #> #   `[header]Stratifications\n[header_level]age_group and sex\n[header_level]<40 and Female` <chr>,
    #> #   `[header]Stratifications\n[header_level]age_group and sex\n[header_level]>=40 and Female` <chr>

### Use `formatTable()`

Now that the data is cleaned and organized, `formatTable()` can be used to create a well-structured `<gt>` or `<flextable>` object.

    result |>
      formatTable(
        type = "gt",
        delim = "\n",
        style = "default",
        na = "-",
        title = "My formatted table!",
        subtitle = "Created with the `visOmopResults` R package.",
        caption = NULL,
        groupColumn = "cohort_name",
        groupAsColumn = FALSE,
        groupOrder = c("cohort2", "cohort1"),
        merge = "variable_name"
      )

My formatted table!
Created with the `visOmopResults` R package.
| Stratifications
--- | ---
variable_name | variable_level | estimate_name | age_group and sex
<40 and Male | >=40 and Male | <40 and Female | >=40 and Female
cohort2
number subjects | - | N | 8,181,325 | 9,777,084 | 238,273 | 5,907,094
age | - | Mean (SD) | 12.3128 (5.0593) | 87.7437 (4.5332) | 11.3866 (4.1720) | 98.2669 (4.3407)
Medications | Amoxiciline | N (%) | 70,148 (19.82%) | 52,132 (98.43%) | 89,174 (90.96%) | 51,025 (39.12%)
| Ibuprofen | N (%) | 97,663 (44.14%) | 12,863 (3.68%) | 58,384 (35.15%) | 63,295 (2.51%)
cohort1
number subjects | - | N | 784,601 | 9,851,372 | 9,420,078 | 1,427,126
age | - | Mean (SD) | 93.8045 (9.9969) | 64.7834 (1.5256) | 98.3123 (1.7726) | 22.1675 (9.8054)
Medications | Amoxiciline | N (%) | 99,507 (68.12%) | 47,175 (47.04%) | 34,405 (81.59%) | 40,824 (33.95%)
| Ibuprofen | N (%) | 77,565 (41.66%) | 25,835 (14.54%) | 88,172 (78.74%) | 20,623 (31.60%)

In the examples above, we used the default style defined in the visOmopResults package (use `gtStyle()` and `flextableStyle()` to see these styles). However, it’s possible to customize the appearance of different parts of the table to better suit your needs.

### Customizing Table Styles

Let’s start by applying a custom style to a `<gt>` table:

    result |>
      formatTable(
        type = "gt",
        delim = "\n",
        style = list(
          "header" = list(gt::cell_text(weight = "bold"),
                          gt::cell_fill(color = "orange")),
          "header_level" = list(gt::cell_text(weight = "bold"),
                          gt::cell_fill(color = "yellow")),
          "column_name" = gt::cell_text(weight = "bold"),
          "group_label" = list(gt::cell_fill(color = "blue"),
                               gt::cell_text(color = "white", weight = "bold")),
          "title" = list(gt::cell_text(size = 20, weight = "bold")),
          "subtitle" = list(gt::cell_text(size = 15)),
          "body" = gt::cell_text(color = "red")
        ),
        na = "-",
        title = "My formatted table!",
        subtitle = "Created with the `visOmopResults` R package.",
        caption = NULL,
        groupColumn = "cohort_name",
        groupAsColumn = FALSE,
        groupOrder = c("cohort2", "cohort1"),
        merge = "variable_name"
      )

My formatted table!
Created with the `visOmopResults` R package.
| Stratifications
--- | ---
variable_name | variable_level | estimate_name | age_group and sex
<40 and Male | >=40 and Male | <40 and Female | >=40 and Female
cohort2
number subjects | - | N | 8,181,325 | 9,777,084 | 238,273 | 5,907,094
age | - | Mean (SD) | 12.3128 (5.0593) | 87.7437 (4.5332) | 11.3866 (4.1720) | 98.2669 (4.3407)
Medications | Amoxiciline | N (%) | 70,148 (19.82%) | 52,132 (98.43%) | 89,174 (90.96%) | 51,025 (39.12%)
| Ibuprofen | N (%) | 97,663 (44.14%) | 12,863 (3.68%) | 58,384 (35.15%) | 63,295 (2.51%)
cohort1
number subjects | - | N | 784,601 | 9,851,372 | 9,420,078 | 1,427,126
age | - | Mean (SD) | 93.8045 (9.9969) | 64.7834 (1.5256) | 98.3123 (1.7726) | 22.1675 (9.8054)
Medications | Amoxiciline | N (%) | 99,507 (68.12%) | 47,175 (47.04%) | 34,405 (81.59%) | 40,824 (33.95%)
| Ibuprofen | N (%) | 77,565 (41.66%) | 25,835 (14.54%) | 88,172 (78.74%) | 20,623 (31.60%)

For creating a similarly styled `<flextable>`, the `office` R package is required to access specific formatting functions.

    result |>
      formatTable(
        type = "flextable",
        delim = "\n",
        style = list(
          "header" = list(
            "cell" = officer::fp_cell(background.color = "orange"),
            "text" = officer::fp_text(bold = TRUE)),
          "header_level" = list(
            "cell" = officer::fp_cell(background.color = "yellow"),
            "text" = officer::fp_text(bold = TRUE)),
          "column_name" = list("text" = officer::fp_text(bold = TRUE)),
          "group_label" = list(
            "cell" = officer::fp_cell(background.color = "blue"),
            "text" = officer::fp_text(bold = TRUE, color = "white")),
          "title" = list("text" = officer::fp_text(bold = TRUE, font.size = 20)),
          "subtitle" = list("text" = officer::fp_text(font.size = 15)),
          "body" = list("text" = officer::fp_text(color = "red"))
        ),
        na = "-",
        title = "My formatted table!",
        subtitle = "Created with the `visOmopResults` R package.",
        caption = NULL,
        groupColumn = "cohort_name",
        groupAsColumn = FALSE,
        groupOrder = c("cohort2", "cohort1"),
        merge = "variable_name"
      )

My formatted table!
Created with the `visOmopResults` R package.
cohort_name | variable_name | variable_level | estimate_name | Stratifications
--- | --- | --- | --- | ---
age_group and sex
<40 and Male | >=40 and Male | <40 and Female | >=40 and Female
cohort2 |
| number subjects | - | N | 8,181,325 | 9,777,084 | 238,273 | 5,907,094
| age | - | Mean (SD) | 12.3128 (5.0593) | 87.7437 (4.5332) | 11.3866 (4.1720) | 98.2669 (4.3407)
| Medications | Amoxiciline | N (%) | 70,148 (19.82%) | 52,132 (98.43%) | 89,174 (90.96%) | 51,025 (39.12%)
|  | Ibuprofen | N (%) | 97,663 (44.14%) | 12,863 (3.68%) | 58,384 (35.15%) | 63,295 (2.51%)
cohort1 |
| number subjects | - | N | 784,601 | 9,851,372 | 9,420,078 | 1,427,126
| age | - | Mean (SD) | 93.8045 (9.9969) | 64.7834 (1.5256) | 98.3123 (1.7726) | 22.1675 (9.8054)
| Medications | Amoxiciline | N (%) | 99,507 (68.12%) | 47,175 (47.04%) | 34,405 (81.59%) | 40,824 (33.95%)
|  | Ibuprofen | N (%) | 77,565 (41.66%) | 25,835 (14.54%) | 88,172 (78.74%) | 20,623 (31.60%)