Skip to contents

The flextable and gt packages have many, many options and helper functions. The tabulator, summarizor, and proc_freq functions from the flextable package are particularly powerful ways of summarizing and describing data. Functions that automate your descriptive statistics tables for you (e.g., the amazing gtsummary package) inevitably entail some level of compromise. It is unreasonable for a single function to anticipate the diversity of needs out there. Sometimes you need a table to be a particular way, and you need to do the heaving lifting yourself. After all that, the apa_style function will get a flextable or gt table close to APA style. You may need to do some additional styling with flextable or gt as well.

Here I create a table of means, standard deviations, and sample sizes for several variables across groups. The particulars are not so important here, just that flextable and gt can do most of the rest after that. Flextable has a theme_apa function that can get things very close to full APA style. The apa7 apa_style function provides a little more flexibility with respect to fonts, colors, and border widths. Getting gt to have APA style borders took me a long time to figure out because of the complex rules by which borders overwrite each other. The apa_style function saves me from having to figure it all out repeatedly when working with gt tables.

d <- iris %>% 
  pivot_longer(where(is.numeric), names_to = "Variable") %>% 
  mutate(Species = forcats::fct_relabel(Species, stringr::str_to_title),
         Variable = snakecase::to_title_case(Variable)) %>% 
  summarise(
    M = mean(value, na.rm = TRUE),
    SD = sd(value, na.rm = TRUE),
    n = n(),
    .by = c(Variable, Species)) %>% 
    # tabulator(rows = "Variable", columns = "Species") %>% 
  pivot_longer(c(M, SD,n)) %>% 
  unite(Species, Species, name) %>% 
  pivot_wider(names_from = Species) %>% 
  mutate(across(ends_with("_n"), .fns = as.integer))


# Where are the columns that end in _n?
ats <- match(colnames(d)[endsWith(colnames(d), "_n")], colnames(d))

# Insert break columns after _n variables
keys <- R.utils::insert(colnames(d), 
                        ats = ats[-length(ats)] + 1, 
                        values = paste0("break", 1:2))
# Flextable
flextable(d, col_keys = keys) %>% 
  colformat_double(digits = 2) %>% 
  separate_header() %>% 
  empty_blanks() %>% 
  align(align = "center", part = "all") %>%
  align(j = 1, align = "left", part = "all") %>% 
  italic(i = 2, part = "header") %>%
  set_table_properties(layout = "autofit", width = 1) %>% 
  apa_style()

Variable

Setosa

Versicolor

Virginica

M

SD

n

M

SD

n

M

SD

n

Sepal Length

5.01

0.35

50

5.94

0.52

50

6.59

0.64

50

Sepal Width

3.43

0.38

50

2.77

0.31

50

2.97

0.32

50

Petal Length

1.46

0.17

50

4.26

0.47

50

5.55

0.55

50

Petal Width

0.25

0.11

50

1.33

0.20

50

2.03

0.27

50

  
# gt
gt(d) %>% 
  fmt_number(decimals = 2, columns = is.double) %>%
  tab_spanner_delim(delim = "_") %>% 
  cols_align(align = "center", columns = -1) %>% 
  tab_style(cell_text(style = "italic"), locations = cells_column_labels(-Variable)) %>% 
  tab_options(table.width = pct(100)) %>% 
  apa_style()
Variable
Setosa
Versicolor
Virginica
M SD n M SD n M SD n
Sepal Length 5.01 0.35 50 5.94 0.52 50 6.59 0.64 50
Sepal Width 3.43 0.38 50 2.77 0.31 50 2.97 0.32 50
Petal Length 1.46 0.17 50 4.26 0.47 50 5.55 0.55 50
Petal Width 0.25 0.11 50 1.33 0.20 50 2.03 0.27 50