17 Flextable
17.6 🔵 Flextable wrappers
We can wrap flextable functions that we use repeatedly in a wrapper.
my_ft_theme <- function(ft, ...) {
# Remove vertical cell padding
ft <- padding(ft, padding.top = 0, padding.bottom = 0, part = "all")
# Change font to TNR 11
ft <- font(ft, fontname = "Times New Roman", part = "all")
ft <- fontsize(ft, part = "all", size = 11)
ft
}
And then use it like this:
mpg | cyl | disp | hp | drat | wt | qsec | vs | am | gear | carb |
---|---|---|---|---|---|---|---|---|---|---|
21.0 | 6 | 160 | 110 | 3.90 | 2.620 | 16.46 | 0 | 1 | 4 | 4 |
21.0 | 6 | 160 | 110 | 3.90 | 2.875 | 17.02 | 0 | 1 | 4 | 4 |
22.8 | 4 | 108 | 93 | 3.85 | 2.320 | 18.61 | 1 | 1 | 4 | 1 |
21.4 | 6 | 258 | 110 | 3.08 | 3.215 | 19.44 | 1 | 0 | 3 | 1 |
18.7 | 8 | 360 | 175 | 3.15 | 3.440 | 17.02 | 0 | 0 | 3 | 2 |
18.1 | 6 | 225 | 105 | 2.76 | 3.460 | 20.22 | 1 | 0 | 3 | 1 |
17.7 🔵 Adding/modifying table content
17.7.1 Add table title with bold part
From SO 2020-08-23: https://stackoverflow.com/questions/63530204/is-there-a-way-to-bold-part-of-a-character-string-being-passed-to-add-header-lin?noredirect=1#comment112346997_63530204
mtcars_ft <- flextable(head(mtcars)) %>%
# Add a blank title line to top of table
add_header_lines("") %>%
# Use compose to bold "Table #."
compose(
i = 1, part = "header",
value = as_paragraph(
as_chunk("Table 1. ", props = fp_text(bold = TRUE)),
"Here is my example mtcars ft."
),
)
mtcars_ft
Table 1. Here is my example mtcars ft. | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
mpg | cyl | disp | hp | drat | wt | qsec | vs | am | gear | carb |
21.0 | 6 | 160 | 110 | 3.90 | 2.620 | 16.46 | 0 | 1 | 4 | 4 |
21.0 | 6 | 160 | 110 | 3.90 | 2.875 | 17.02 | 0 | 1 | 4 | 4 |
22.8 | 4 | 108 | 93 | 3.85 | 2.320 | 18.61 | 1 | 1 | 4 | 1 |
21.4 | 6 | 258 | 110 | 3.08 | 3.215 | 19.44 | 1 | 0 | 3 | 1 |
18.7 | 8 | 360 | 175 | 3.15 | 3.440 | 17.02 | 0 | 0 | 3 | 2 |
18.1 | 6 | 225 | 105 | 2.76 | 3.460 | 20.22 | 1 | 0 | 3 | 1 |
17.7.2 Adding blank rows
I created a post about this on StackOverflow.
When creating tables in Word reports, I often want to add blank rows in between variables. As a trivial toy example:
## cyl n
## 1 4 11
## 2 6 7
## 3 8 14
cyl | n |
---|---|
4 | 11 |
6 | 7 |
8 | 14 |
Results in a table that looks like this: flextable_no_blank_rows.docx
I can add line breaks directly to the data frame like this:
table_breaks <- table_no_breaks %>%
mutate(
across(
everything(),
as.character
)
) %>%
add_row(cyl = NA, n = NA, .after = 1) %>%
add_row(cyl = NA, n = NA, .after = 3) %>%
add_row(cyl = NA, n = NA, .after = 5)
table_breaks
## cyl n
## 1 4 11
## 2 <NA> <NA>
## 3 6 7
## 4 <NA> <NA>
## 5 8 14
## 6 <NA> <NA>
cyl | n |
---|---|
4 | 11 |
6 | 7 |
8 | 14 |
Which results in the Word table that I want: flextable_blank_rows.docx
17.7.2.1 Using padding instead of adding blank rows
I can also use padding to create space between rows. This method works especially well when All categories of categorical variables are collapsed into a single row.
padding_example <- tribble(
~var, ~formatted_stats, ~Control,
"Sex\n Female\n Male", "\n10 (20%)\n40 (80%)", "\n5 (21%)\n19 (79%)",
"Married\n No\n Yes", "\n21 (42%)\n29 (58%)", "\n12 (50%)\n4 (50%)"
)
flextable(padding_example) %>%
autofit() %>%
padding(padding.bottom = 10, part = "body")
var | formatted_stats | Control |
---|---|---|
Sex |
|
|
Married |
|
|
17.7.3 Change column header text
Example from Tables chapter, which is from L2C smartphone paper.
Compose chapter in flextable book
# Simulate data
table <- tribble(
~var, ~No, ~Yes,
"age", "34.89 (30.19 - 39.58)", "35.38 (30.58 - 40.19)",
"", "", "",
"age_group", "", "",
" Younger than 30", "58.49 (44.63 - 71.12)", "61.70 (46.88 - 74.63)",
" 30 and Older", "41.51 (28.88 - 55.37)", "38.30 (25.37 - 53.12)"
)
flextable(table) %>%
width(width = c(3, 2, 2)) %>%
# Center the final two columns
align(j = c(2, 3), align = "center", part = "all") %>%
# Change header names -- add subgroup Ns to headers
set_header_labels(
var = "Characteristic",
No = paste0("No\n(n=", n_outcome["No"], ")"),
Yes = paste0("Yes\n(n=", n_outcome["Yes"], ")")
) %>%
# Bold column headers
bold(part = "header")
Characteristic | No | Yes |
---|---|---|
age | 34.89 (30.19 - 39.58) | 35.38 (30.58 - 40.19) |
age_group | ||
Younger than 30 | 58.49 (44.63 - 71.12) | 61.70 (46.88 - 74.63) |
30 and Older | 41.51 (28.88 - 55.37) | 38.30 (25.37 - 53.12) |
17.7.4 Change row header text
Example from Tables chapter, which is from L2C smartphone paper.
Compose chapter in flextable book
# Simulate data
table <- tribble(
~var, ~No, ~Yes,
"age", "34.89 (30.19 - 39.58)", "35.38 (30.58 - 40.19)",
"", "", "",
"age_group", "", "",
" Younger than 30", "58.49 (44.63 - 71.12)", "61.70 (46.88 - 74.63)",
" 30 and Older", "41.51 (28.88 - 55.37)", "38.30 (25.37 - 53.12)"
)
flextable(table) %>%
width(width = c(3, 2, 2)) %>%
# Change text by location
compose(i = 1, j = 1, value = as_paragraph("Age, mean (95% CI)")) %>%
# Change text conditionally
compose(i = ~ var == "age_group", j = 1, value = as_paragraph("Age group, row percent (95% CI)"))
var | No | Yes |
---|---|---|
Age, mean (95% CI) | 34.89 (30.19 - 39.58) | 35.38 (30.58 - 40.19) |
Age group, row percent (95% CI) | ||
Younger than 30 | 58.49 (44.63 - 71.12) | 61.70 (46.88 - 74.63) |
30 and Older | 41.51 (28.88 - 55.37) | 38.30 (25.37 - 53.12) |
17.7.5 Add footnote
Example from Tables chapter, which is from L2C smartphone paper.
# Simulate data
table <- tribble(
~var, ~No, ~Yes,
"age", "34.89 (30.19 - 39.58)", "35.38 (30.58 - 40.19)",
"", "", "",
"age_group", "", "",
" Younger than 30", "58.49 (44.63 - 71.12)", "61.70 (46.88 - 74.63)",
" 30 and Older", "41.51 (28.88 - 55.37)", "38.30 (25.37 - 53.12)"
)
Add a superscript “1” behind age and a numbered footnote at the bottom of the table.
flextable(table) %>%
width(width = c(3, 2, 2)) %>%
footnote(i = 1, j = 1, value = as_paragraph("Test Footnote"), ref_symbols = "1")
var | No | Yes |
---|---|---|
age1 | 34.89 (30.19 - 39.58) | 35.38 (30.58 - 40.19) |
age_group | ||
Younger than 30 | 58.49 (44.63 - 71.12) | 61.70 (46.88 - 74.63) |
30 and Older | 41.51 (28.88 - 55.37) | 38.30 (25.37 - 53.12) |
1Test Footnote |
Or more than one at at a time:
flextable(table) %>%
width(width = c(3, 2, 2)) %>%
footnote(
i = c(1, 3), j = 1,
value = as_paragraph(
c("Age in years.", "Age grouped above and below median.")
),
ref_symbols = c("1", "2")
)
var | No | Yes |
---|---|---|
age1 | 34.89 (30.19 - 39.58) | 35.38 (30.58 - 40.19) |
age_group2 | ||
Younger than 30 | 58.49 (44.63 - 71.12) | 61.70 (46.88 - 74.63) |
30 and Older | 41.51 (28.88 - 55.37) | 38.30 (25.37 - 53.12) |
1Age in years. | ||
2Age grouped above and below median. |
17.8 🔵 Formatting
17.8.1 Change font to TNR
mpg | cyl | disp | hp | drat | wt | qsec | vs | am | gear | carb |
---|---|---|---|---|---|---|---|---|---|---|
21.0 | 6 | 160 | 110 | 3.90 | 2.620 | 16.46 | 0 | 1 | 4 | 4 |
21.0 | 6 | 160 | 110 | 3.90 | 2.875 | 17.02 | 0 | 1 | 4 | 4 |
22.8 | 4 | 108 | 93 | 3.85 | 2.320 | 18.61 | 1 | 1 | 4 | 1 |
21.4 | 6 | 258 | 110 | 3.08 | 3.215 | 19.44 | 1 | 0 | 3 | 1 |
18.7 | 8 | 360 | 175 | 3.15 | 3.440 | 17.02 | 0 | 0 | 3 | 2 |
18.1 | 6 | 225 | 105 | 2.76 | 3.460 | 20.22 | 1 | 0 | 3 | 1 |
17.8.2 Conditional formatting
flextable(head(mtcars)) %>%
# If cyl is 4 turn all text blue
color(i = ~ cyl == 4, color = "blue") %>%
# mpg is greater than average mpg then format the color to red
color(i = ~ mpg > mean(mpg), j = "mpg", color = "red")
mpg | cyl | disp | hp | drat | wt | qsec | vs | am | gear | carb |
---|---|---|---|---|---|---|---|---|---|---|
21.0 | 6 | 160 | 110 | 3.90 | 2.620 | 16.46 | 0 | 1 | 4 | 4 |
21.0 | 6 | 160 | 110 | 3.90 | 2.875 | 17.02 | 0 | 1 | 4 | 4 |
22.8 | 4 | 108 | 93 | 3.85 | 2.320 | 18.61 | 1 | 1 | 4 | 1 |
21.4 | 6 | 258 | 110 | 3.08 | 3.215 | 19.44 | 1 | 0 | 3 | 1 |
18.7 | 8 | 360 | 175 | 3.15 | 3.440 | 17.02 | 0 | 0 | 3 | 2 |
18.1 | 6 | 225 | 105 | 2.76 | 3.460 | 20.22 | 1 | 0 | 3 | 1 |
17.9 🔵Layout
17.9.1 Autofit to contents
mpg | cyl | disp | hp | drat | wt | qsec | vs | am | gear | carb |
---|---|---|---|---|---|---|---|---|---|---|
21.0 | 6 | 160 | 110 | 3.90 | 2.620 | 16.46 | 0 | 1 | 4 | 4 |
21.0 | 6 | 160 | 110 | 3.90 | 2.875 | 17.02 | 0 | 1 | 4 | 4 |
22.8 | 4 | 108 | 93 | 3.85 | 2.320 | 18.61 | 1 | 1 | 4 | 1 |
21.4 | 6 | 258 | 110 | 3.08 | 3.215 | 19.44 | 1 | 0 | 3 | 1 |
18.7 | 8 | 360 | 175 | 3.15 | 3.440 | 17.02 | 0 | 0 | 3 | 2 |
18.1 | 6 | 225 | 105 | 2.76 | 3.460 | 20.22 | 1 | 0 | 3 | 1 |
17.9.2 All categories of categorical variables are collapsed into a single row
Merge doesn’t work. Merge collapses identical values down. It won’t collapse cells with non-identical values.
Instead, you have to use paste and \n
to collapse the text from multiple rows into a single character string.
# All categories in one row
# Space between categories
collapse_example <- tribble(
~Characteristic, ~Overall, ~Controls,
"Sex\n Female\n Male", "\n10 (20%)\n40 (80%)", "\n5 (21%)\n19 (79%)",
"Married\n No\n Yes", "\n21 (42%)\n29 (58%)", "\n12 (50%)\n4 (50%)"
)
flextable(collapse_example) %>%
autofit() %>%
# Create vertical space between variables
padding(padding.bottom = 10, part = "body") %>%
# Center column headings
align(align = "center", part = "header") %>%
# Center body text
align(j = 2:3, align = "center", part = "body")
Characteristic | Overall | Controls |
---|---|---|
Sex |
|
|
Married |
|
|
How do we collapse the categories into character strings? Let’s say we are starting with a data frame of formatted results like this.
collapse_example <- tribble(
~Characteristic, ~Overall, ~Controls,
"Sex", NA, NA,
" Female", "10 (20%)", "5 (21%)",
" Male", "40 (80%)", "19 (79%)"
)
collapse_one_row <- function(x) {
# Use paste to collapse the values into a string
x <- paste(x, collapse = "\n")
# Remove leading NA
x <- stringr::str_remove(x, "^NA")
x
}
collapse_example <- collapse_example %>%
mutate(across(.fns = collapse_one_row)) %>%
# All rows identical now. Only keep the first one.
slice(1)
## Warning: There was 1 warning in `mutate()`.
## ℹ In argument: `across(.fns = collapse_one_row)`.
## Caused by warning:
## ! Using `across()` without supplying `.cols` was deprecated in dplyr 1.1.0.
## ℹ Please supply `.cols` instead.
# Format flextable
flextable(collapse_example) %>%
autofit() %>%
# Create vertical space between variables
padding(padding.bottom = 10, part = "body") %>%
# Center column headings
align(align = "center", part = "header") %>%
# Center body text
align(j = 2:3, align = "center", part = "body")
Characteristic | Overall | Controls |
---|---|---|
Sex |
|
|
17.10 🔵 Examples
Other good examples to check out:
- Sun Study report.
- stroke study -> table_characteristics_by_network.Rmd.
- L2C quarterly reports.
- L2C paper_smartphone_app
17.10.1 LEAD panel summarize votes
Simulate data
summary_agreement_ad_abuse_type <- tibble(
CaseID = c("1001", "1002", "1003", "1004", "1005", "1006"),
physical = rep("Agree", 6),
sexual = rep("Agree", 6),
emotional = c("Agree", "Disagree", "Agree", "Agree", "Agree", "Agree"),
neglect = rep("Agree", 6),
abandonment = c("Agree", "Agree", NA, "Agree", "Agree", "Agree"),
financial = rep("Agree", 6),
selfneglect = rep("Agree", 6),
TotalAgreement = c(TRUE, FALSE, FALSE, TRUE, TRUE, TRUE),
AnyDisagreement = c(FALSE, TRUE, TRUE, FALSE, FALSE, FALSE)
)
Make table
summary_agreement_ad_abuse_type_ft <- flextable(
# Remove unneeded columns
summary_agreement_ad_abuse_type %>%
select(
CaseID, physical, sexual, emotional, neglect,
abandonment, financial, selfneglect
)
) %>%
# Column width: Trial and error
# Make a table and play with properties
width(
j = c(1:8),
width = c(0.98, 0.66, 0.56, 0.78, 0.71, 1.01, 0.71, 0.90)
) %>%
# Improve readability of column headers
set_header_labels(CaseID = "Case Number", selfneglect = "Self Neglect") %>%
# Add title to top of table
# Add a blank title line to top of table
add_header_lines("") %>%
# Use compose to bold "Table #."
compose(
i = 1, part = "header",
value = as_paragraph(
as_chunk("Table 2. ", props = fp_text(bold = TRUE)),
"Presence/absence of unanimous agreement for each abuse type by case number."
),
) %>%
# Change font to times new roman
font(fontname = "Times New Roman", part = "all") %>%
# Change background color of first column
bg(j = 1, bg = "#E5E8E8", part = "body") %>%
# Center column headings
align(i = 2, align = "center", part = "header") %>%
# Center body text
align(align = "center", part = "body") %>%
# Conditionally format disagree to red
color(i = ~ physical == "Disagree", j = c("CaseID", "physical"), color = "red") %>%
color(i = ~ sexual == "Disagree", j = c("CaseID", "sexual"), color = "red") %>%
color(i = ~ emotional == "Disagree", j = c("CaseID", "emotional"), color = "red") %>%
color(i = ~ neglect == "Disagree", j = c("CaseID", "neglect"), color = "red") %>%
color(i = ~ abandonment == "Disagree", j = c("CaseID", "abandonment"), color = "red") %>%
color(i = ~ financial == "Disagree", j = c("CaseID", "financial"), color = "red") %>%
color(i = ~ selfneglect == "Disagree", j = c("CaseID", "selfneglect"), color = "red")
# For checking
summary_agreement_ad_abuse_type_ft
Table 2. Presence/absence of unanimous agreement for each abuse type by case number. | |||||||
---|---|---|---|---|---|---|---|
Case Number | physical | sexual | emotional | neglect | abandonment | financial | Self Neglect |
1001 | Agree | Agree | Agree | Agree | Agree | Agree | Agree |
1002 | Agree | Agree | Disagree | Agree | Agree | Agree | Agree |
1003 | Agree | Agree | Agree | Agree | Agree | Agree | |
1004 | Agree | Agree | Agree | Agree | Agree | Agree | Agree |
1005 | Agree | Agree | Agree | Agree | Agree | Agree | Agree |
1006 | Agree | Agree | Agree | Agree | Agree | Agree | Agree |