# Clear environment and console
rm(list = ls())Creating a Publication-Ready Table in R with flextable
Step 1: Load Required Libraries
# **Load required libraries**
if (!requireNamespace("pacman", quietly = TRUE)) install.packages("pacman")
pacman::p_load(
lubridate, # Simplifies the manipulation of dates and times in R (e.g., formatting, extracting components)
dplyr, # Provides tools for data manipulation, helpful for filtering and summarizing date-based data
stringr, # Simplifies string manipulation
tidyr, # Reshaping and tidying data
zoo, # For working with time series data, including rolling calculations and handling missing data
tsibble, # Provides tools for handling time series data, including date-time indexes and features for forecasting
readr, # For reading date-time data from CSV
haven, # Data from other statistical software formats (SPSS, SAS, Stata)
forecast, # Useful for time series forecasting, especially when working with seasonal or trend-based data
ggplot2, # For visualizing date-time trends in data (e.g., time series plots)
janitor, # Tabulation, cleaning column names, adding totals and proportions
plotly, # Interactive visualizations
# Documentation/reporting
prettydoc, # Pretty document templates
flexdashboard, # Interactive dashboards
quarto, # For rendering and publishing documents with the Quarto framework
yaml, # YAML document processing
# Tabulation
flextable, # For creating styled, publication-ready tables
gt, # Grammar of tables
reactable, # Interactive tables
scales, # For number formatting (e.g., commas)
officer, # For exporting tables/reports to Word or PowerPoint
tidyverse,
DT,
knitr,
kableExtra
)setwd("D:/OneDrive/ncid/GitHub/table_automation")
load("Vaccination_uptake.RDATA")vacc_1524 <- vacc_data |>
mutate(across(
c(
age_group,
gender,
marital_status,
vacc_service_year,
vacc_quarter,
vacc_weekday
),
as.character
))
kable(head(vacc_1524, 200),
caption = "First 200 Rows of vacc_1524 (Ages 15-24 Vaccination Data)",
format = "markdown") |> # Or "html", "latex"
kable_styling(bootstrap_options = c("striped", "hover")) |>
scroll_box(height = "400px") # Scroll for 200 rows| category | age_group | gender | marital_status | vacc_service_year | vacc_quarter | vacc_weekday |
|---|---|---|---|---|---|---|
| Respiratory vaccines | 0-19 | Male | Married | 2019 | 1 | Sunday |
| Respiratory vaccines | 50-59 | Female | NA | 2019 | 4 | Wednesday |
| Respiratory vaccines | 0-19 | Female | Married | 2024 | 1 | Sunday |
| Respiratory vaccines | 0-19 | Male | Married | 2024 | 1 | Monday |
| Respiratory vaccines | 20-29 | Male | Married | 2021 | 3 | Saturday |
| Respiratory vaccines | 40-49 | Male | Married | 2021 | 4 | Sunday |
| Respiratory vaccines | 20-29 | Female | Single/widowed | 2019 | 3 | Sunday |
| Respiratory vaccines | 20-29 | Female | Married | 2017 | 4 | Wednesday |
| Respiratory vaccines | 0-19 | Male | Married | 2019 | 3 | Wednesday |
| Respiratory vaccines | 70+ | Male | Married | 2016 | 3 | Saturday |
| Respiratory vaccines | 70+ | Female | Single/widowed | 2016 | 4 | Thursday |
| Respiratory vaccines | 40-49 | Male | Married | 2017 | 3 | Tuesday |
| Respiratory vaccines | 0-19 | Male | Single/widowed | 2019 | 1 | Tuesday |
| Respiratory vaccines | 50-59 | Male | Single/widowed | 2022 | 4 | Tuesday |
| Respiratory vaccines | 60-69 | Female | Married | 2017 | 1 | Thursday |
| Respiratory vaccines | 40-49 | Male | Single/widowed | 2021 | 1 | Monday |
| Respiratory vaccines | 20-29 | Female | Married | 2023 | 4 | Wednesday |
| Respiratory vaccines | 20-29 | Male | Single/widowed | 2016 | 3 | Friday |
| Respiratory vaccines | 70+ | Female | Divorced | 2024 | 4 | Thursday |
| Respiratory vaccines | 30-39 | Male | Married | 2019 | 1 | Wednesday |
| Respiratory vaccines | 0-19 | Female | Divorced | 2018 | 1 | Monday |
| Respiratory vaccines | 0-19 | Male | Single/widowed | 2024 | 3 | Sunday |
| Respiratory vaccines | 30-39 | Male | Divorced | 2017 | 4 | Thursday |
| Respiratory vaccines | 40-49 | Female | Divorced | 2023 | 4 | Wednesday |
| Respiratory vaccines | 50-59 | Male | Single/widowed | 2016 | 2 | Wednesday |
| Respiratory vaccines | 70+ | Male | Single/widowed | 2019 | 1 | Friday |
| Respiratory vaccines | 50-59 | Male | Married | 2023 | 4 | Tuesday |
| Respiratory vaccines | 50-59 | Male | Divorced | 2016 | 3 | Monday |
| Respiratory vaccines | 40-49 | Female | Single/widowed | 2016 | 4 | Saturday |
| Respiratory vaccines | 20-29 | Female | Single/widowed | 2019 | 4 | Saturday |
| Respiratory vaccines | 40-49 | Male | Single/widowed | 2020 | 3 | Thursday |
| Respiratory vaccines | 30-39 | Female | Married | 2024 | 2 | Friday |
| Respiratory vaccines | 20-29 | Female | Divorced | 2020 | 2 | Tuesday |
| Respiratory vaccines | 0-19 | Male | Single/widowed | 2017 | 3 | Wednesday |
| Respiratory vaccines | 20-29 | Male | Married | 2023 | 2 | Sunday |
| Respiratory vaccines | 60-69 | Female | Married | 2019 | 2 | Tuesday |
| Respiratory vaccines | 30-39 | Male | Married | 2024 | 2 | Sunday |
| Respiratory vaccines | 70+ | Male | Single/widowed | 2023 | 3 | Sunday |
| Respiratory vaccines | 60-69 | Female | Divorced | 2023 | 4 | Thursday |
| Respiratory vaccines | 20-29 | Female | Married | 2021 | 4 | Monday |
| Respiratory vaccines | 40-49 | Female | Married | 2024 | 3 | Thursday |
| Respiratory vaccines | 40-49 | Female | NA | 2020 | 2 | Friday |
| Respiratory vaccines | 60-69 | Female | Married | 2016 | 1 | Wednesday |
| Respiratory vaccines | 20-29 | Female | Single/widowed | 2019 | 2 | Thursday |
| Respiratory vaccines | 50-59 | Female | Single/widowed | 2016 | 4 | Thursday |
| Respiratory vaccines | 40-49 | Male | NA | 2016 | 4 | Saturday |
| Respiratory vaccines | 50-59 | Female | Married | 2021 | 3 | Wednesday |
| Respiratory vaccines | 40-49 | Female | Divorced | 2021 | 4 | Friday |
| Respiratory vaccines | 20-29 | Male | Married | 2024 | 1 | Monday |
| Respiratory vaccines | 20-29 | Male | Married | 2019 | 2 | Friday |
| Respiratory vaccines | 30-39 | Female | Married | 2016 | 1 | Thursday |
| Respiratory vaccines | 70+ | Male | Married | 2017 | 1 | Tuesday |
| Respiratory vaccines | 0-19 | Male | Divorced | 2019 | 3 | Thursday |
| Respiratory vaccines | 50-59 | Male | Married | 2022 | 3 | Sunday |
| Respiratory vaccines | 20-29 | Female | Married | 2017 | 1 | Monday |
| Respiratory vaccines | 20-29 | Male | Married | 2019 | 1 | Wednesday |
| Respiratory vaccines | 60-69 | Female | Divorced | 2015 | 3 | Monday |
| Respiratory vaccines | 60-69 | Female | Divorced | 2020 | 1 | Tuesday |
| Respiratory vaccines | 20-29 | Female | NA | 2024 | 4 | Wednesday |
| Respiratory vaccines | 40-49 | Male | Married | 2019 | 3 | Tuesday |
| Respiratory vaccines | 30-39 | Male | Married | 2023 | 1 | Friday |
| Respiratory vaccines | 60-69 | Female | Married | 2016 | 4 | Monday |
| Respiratory vaccines | 50-59 | Male | Married | 2024 | 2 | Friday |
| Respiratory vaccines | 20-29 | Male | Single/widowed | 2016 | 2 | Wednesday |
| Respiratory vaccines | 70+ | Female | Single/widowed | 2021 | 4 | Monday |
| Respiratory vaccines | 60-69 | Female | Single/widowed | 2015 | 4 | Tuesday |
| Respiratory vaccines | 20-29 | Female | Single/widowed | 2024 | 4 | Tuesday |
| Respiratory vaccines | 20-29 | Male | Married | 2017 | 1 | Wednesday |
| Respiratory vaccines | 50-59 | Male | NA | 2017 | 3 | Monday |
| Respiratory vaccines | 0-19 | Male | Married | 2020 | 2 | Friday |
| Respiratory vaccines | 0-19 | Male | Married | 2020 | 4 | Friday |
| Respiratory vaccines | 70+ | Female | Single/widowed | 2015 | 2 | Friday |
| Respiratory vaccines | 40-49 | Female | Single/widowed | 2019 | 2 | Monday |
| Respiratory vaccines | 50-59 | Female | Married | 2022 | 2 | Thursday |
| Respiratory vaccines | 20-29 | Male | Divorced | 2018 | 2 | Tuesday |
| Respiratory vaccines | 0-19 | Male | Married | 2017 | 1 | Saturday |
| Respiratory vaccines | 50-59 | Male | NA | 2022 | 4 | Saturday |
| Respiratory vaccines | 40-49 | Female | NA | 2021 | 3 | Friday |
| Respiratory vaccines | 40-49 | Male | Single/widowed | 2022 | 1 | Wednesday |
| Respiratory vaccines | 0-19 | Female | Divorced | 2020 | 2 | Tuesday |
| Respiratory vaccines | 30-39 | Male | Single/widowed | 2023 | 2 | Saturday |
| Respiratory vaccines | 30-39 | Female | Married | 2021 | 2 | Friday |
| Respiratory vaccines | 50-59 | Male | Single/widowed | 2024 | 2 | Sunday |
| Respiratory vaccines | 50-59 | Female | Married | 2016 | 3 | Monday |
| Respiratory vaccines | 40-49 | Male | NA | 2017 | 2 | Saturday |
| Respiratory vaccines | 60-69 | Male | Single/widowed | 2015 | 2 | Sunday |
| Respiratory vaccines | 50-59 | Male | NA | 2020 | 4 | Tuesday |
| Respiratory vaccines | 40-49 | Male | Single/widowed | 2019 | 3 | Wednesday |
| Respiratory vaccines | 60-69 | Male | Married | 2017 | 2 | Monday |
| Respiratory vaccines | 20-29 | Male | Divorced | 2020 | 1 | Sunday |
| Respiratory vaccines | 20-29 | Male | Single/widowed | 2018 | 3 | Thursday |
| Respiratory vaccines | 0-19 | Male | Married | 2024 | 2 | Sunday |
| Respiratory vaccines | 20-29 | Male | Divorced | 2016 | 3 | Friday |
| Respiratory vaccines | 50-59 | Female | Married | 2023 | 1 | Wednesday |
| Respiratory vaccines | 60-69 | Female | Divorced | 2022 | 4 | Monday |
| Respiratory vaccines | 70+ | Male | Single/widowed | 2022 | 3 | Wednesday |
| Respiratory vaccines | 50-59 | Female | Married | 2021 | 2 | Friday |
| Respiratory vaccines | 40-49 | Female | Married | 2017 | 1 | Monday |
| Respiratory vaccines | 0-19 | Female | Divorced | 2016 | 2 | Thursday |
| Respiratory vaccines | 40-49 | Male | Divorced | 2015 | 3 | Friday |
| Respiratory vaccines | 70+ | Female | Married | 2015 | 2 | Wednesday |
| Respiratory vaccines | 20-29 | Female | Married | 2018 | 3 | Saturday |
| Respiratory vaccines | 20-29 | Female | Married | 2015 | 3 | Thursday |
| Respiratory vaccines | 30-39 | Male | Single/widowed | 2015 | 2 | Friday |
| Respiratory vaccines | 50-59 | Female | Divorced | 2024 | 3 | Saturday |
| Respiratory vaccines | 50-59 | Male | NA | 2022 | 4 | Monday |
| Respiratory vaccines | 60-69 | Female | Single/widowed | 2015 | 3 | Monday |
| Respiratory vaccines | 30-39 | Male | Married | 2016 | 1 | Wednesday |
| Respiratory vaccines | 60-69 | Female | NA | 2020 | 4 | Friday |
| Respiratory vaccines | 40-49 | Male | NA | 2017 | 4 | Friday |
| Respiratory vaccines | 50-59 | Male | Divorced | 2015 | 2 | Monday |
| Respiratory vaccines | 0-19 | Male | Married | 2024 | 1 | Monday |
| Respiratory vaccines | 40-49 | Female | NA | 2018 | 4 | Thursday |
| Respiratory vaccines | 0-19 | Female | Married | 2022 | 3 | Saturday |
| Respiratory vaccines | 0-19 | Male | Married | 2018 | 3 | Wednesday |
| Respiratory vaccines | 50-59 | Male | Divorced | 2024 | 4 | Saturday |
| Respiratory vaccines | 60-69 | Male | Married | 2022 | 2 | Saturday |
| Respiratory vaccines | 30-39 | Male | NA | 2023 | 1 | Friday |
| Respiratory vaccines | 0-19 | Male | Married | 2021 | 3 | Thursday |
| Respiratory vaccines | 20-29 | Male | Married | 2015 | 4 | Wednesday |
| Respiratory vaccines | 50-59 | Female | Married | 2019 | 2 | Friday |
| Respiratory vaccines | 70+ | Male | Married | 2018 | 1 | Sunday |
| Respiratory vaccines | 60-69 | Male | Single/widowed | 2015 | 3 | Saturday |
| Respiratory vaccines | 0-19 | Female | Married | 2019 | 4 | Tuesday |
| Respiratory vaccines | 50-59 | Female | Married | 2022 | 4 | Monday |
| Respiratory vaccines | 60-69 | Female | Divorced | 2020 | 4 | Friday |
| Respiratory vaccines | 50-59 | Male | Single/widowed | 2016 | 2 | Tuesday |
| Respiratory vaccines | 0-19 | Male | Divorced | 2019 | 2 | Thursday |
| Respiratory vaccines | 70+ | Female | Single/widowed | 2018 | 4 | Wednesday |
| Respiratory vaccines | 20-29 | Male | Married | 2021 | 2 | Thursday |
| Respiratory vaccines | 30-39 | Male | Single/widowed | 2022 | 2 | Friday |
| Respiratory vaccines | 20-29 | Female | Married | 2020 | 2 | Friday |
| Respiratory vaccines | 20-29 | Female | Married | 2018 | 4 | Thursday |
| Respiratory vaccines | 0-19 | Female | Divorced | 2024 | 2 | Friday |
| Respiratory vaccines | 30-39 | Female | Married | 2019 | 2 | Friday |
| Respiratory vaccines | 0-19 | Female | NA | 2017 | 4 | Tuesday |
| Respiratory vaccines | 50-59 | Female | Single/widowed | 2023 | 3 | Sunday |
| Respiratory vaccines | 0-19 | Female | Married | 2021 | 4 | Monday |
| Respiratory vaccines | 30-39 | Female | Married | 2017 | 2 | Sunday |
| Respiratory vaccines | 70+ | Female | Married | 2018 | 1 | Sunday |
| Respiratory vaccines | 0-19 | Male | Single/widowed | 2016 | 2 | Friday |
| Respiratory vaccines | 60-69 | Male | Divorced | 2017 | 1 | Friday |
| Respiratory vaccines | 60-69 | Male | Single/widowed | 2016 | 1 | Saturday |
| Respiratory vaccines | 70+ | Female | NA | 2016 | 1 | Tuesday |
| Respiratory vaccines | 60-69 | Female | Single/widowed | 2017 | 3 | Thursday |
| Respiratory vaccines | 70+ | Male | Married | 2022 | 4 | Wednesday |
| Respiratory vaccines | 30-39 | Male | Married | 2023 | 1 | Saturday |
| Respiratory vaccines | 60-69 | Female | Single/widowed | 2015 | 4 | Wednesday |
| Respiratory vaccines | 50-59 | Female | Married | 2015 | 4 | Monday |
| Respiratory vaccines | 0-19 | Female | NA | 2021 | 2 | Friday |
| Respiratory vaccines | 60-69 | Female | Married | 2015 | 4 | Wednesday |
| Respiratory vaccines | 0-19 | Male | Single/widowed | 2019 | 3 | Wednesday |
| Respiratory vaccines | 20-29 | Male | Single/widowed | 2022 | 3 | Sunday |
| Respiratory vaccines | 70+ | Male | NA | 2024 | 4 | Friday |
| Respiratory vaccines | 20-29 | Female | Divorced | 2022 | 3 | Thursday |
| Respiratory vaccines | 60-69 | Male | Married | 2024 | 2 | Friday |
| Respiratory vaccines | 60-69 | Male | Married | 2015 | 2 | Friday |
| Respiratory vaccines | 60-69 | Female | Single/widowed | 2024 | 2 | Thursday |
| Respiratory vaccines | 60-69 | Female | Married | 2021 | 3 | Monday |
| Respiratory vaccines | 70+ | Male | Divorced | 2016 | 4 | Wednesday |
| Respiratory vaccines | 60-69 | Male | Married | 2020 | 2 | Thursday |
| Respiratory vaccines | 40-49 | Female | Married | 2023 | 3 | Saturday |
| Respiratory vaccines | 40-49 | Female | Married | 2017 | 1 | Saturday |
| Respiratory vaccines | 70+ | Male | Married | 2019 | 4 | Saturday |
| Respiratory vaccines | 20-29 | Female | Married | 2015 | 2 | Friday |
| Respiratory vaccines | 20-29 | Male | Divorced | 2015 | 4 | Tuesday |
| Respiratory vaccines | 70+ | Male | Married | 2019 | 1 | Wednesday |
| Respiratory vaccines | 40-49 | Male | Divorced | 2021 | 4 | Friday |
| Respiratory vaccines | 60-69 | Male | Married | 2017 | 3 | Friday |
| Respiratory vaccines | 40-49 | Female | Divorced | 2023 | 2 | Saturday |
| Respiratory vaccines | 20-29 | Male | Married | 2015 | 2 | Thursday |
| Respiratory vaccines | 0-19 | Female | NA | 2020 | 2 | Wednesday |
| Respiratory vaccines | 0-19 | Male | Single/widowed | 2016 | 2 | Friday |
| Respiratory vaccines | 20-29 | Male | Single/widowed | 2019 | 2 | Monday |
| Respiratory vaccines | 20-29 | Male | NA | 2018 | 1 | Friday |
| Respiratory vaccines | 0-19 | Male | Single/widowed | 2020 | 1 | Friday |
| Respiratory vaccines | 40-49 | Female | Married | 2017 | 1 | Wednesday |
| Respiratory vaccines | 30-39 | Male | NA | 2023 | 4 | Monday |
| Respiratory vaccines | 50-59 | Female | Married | 2018 | 1 | Saturday |
| Respiratory vaccines | 60-69 | Male | Married | 2016 | 1 | Thursday |
| Respiratory vaccines | 60-69 | Female | Single/widowed | 2016 | 1 | Friday |
| Respiratory vaccines | 20-29 | Male | Divorced | 2022 | 1 | Friday |
| Respiratory vaccines | 40-49 | Male | Married | 2024 | 3 | Saturday |
| Respiratory vaccines | 20-29 | Male | Married | 2015 | 4 | Monday |
| Respiratory vaccines | 70+ | Male | Married | 2019 | 3 | Wednesday |
| Respiratory vaccines | 30-39 | Male | Divorced | 2016 | 2 | Saturday |
| Respiratory vaccines | 20-29 | Female | Single/widowed | 2015 | 2 | Sunday |
| Respiratory vaccines | 0-19 | Female | Married | 2017 | 1 | Thursday |
| Respiratory vaccines | 40-49 | Female | Married | 2022 | 1 | Thursday |
| Respiratory vaccines | 60-69 | Male | Divorced | 2020 | 3 | Friday |
| Respiratory vaccines | 50-59 | Male | Married | 2023 | 1 | Wednesday |
| Respiratory vaccines | 20-29 | Female | Married | 2019 | 1 | Saturday |
| Respiratory vaccines | 0-19 | Male | Married | 2018 | 2 | Friday |
| Respiratory vaccines | 0-19 | Female | Married | 2021 | 3 | Friday |
| Respiratory vaccines | 50-59 | Female | Single/widowed | 2018 | 3 | Tuesday |
| Respiratory vaccines | 30-39 | Male | Single/widowed | 2021 | 4 | Wednesday |
| Respiratory vaccines | 60-69 | Female | Married | 2021 | 3 | Sunday |
| Respiratory vaccines | 60-69 | Male | Married | 2020 | 4 | Thursday |
| Respiratory vaccines | 40-49 | Male | Married | 2018 | 2 | Monday |
| Respiratory vaccines | 20-29 | Female | Single/widowed | 2015 | 3 | Tuesday |
create_tab <- function(data, var, label) {
tab_main <- data |>
filter(!is.na({{ var }})) |>
tabyl({{ var }}, category) |>
mutate(Category = label, .before = 1) |>
rename(Label = {{ var }}) |>
mutate(Label = as.character(Label))
na_count <- sum(is.na(pull(data, {{ var }})))
if (na_count > 0) {
# Dynamically build missing row across all category levels
missing_row <- data |>
filter(is.na({{ var }})) |>
count(category) |>
pivot_wider(names_from = category, values_from = n, values_fill = 0) |>
mutate(Category = label, Label = "Missing", .before = 1)
tab_final <- bind_rows(tab_main, missing_row)
} else {
tab_final <- tab_main
}
return(tab_final)
}tab_age <- create_tab(vacc_1524, age_group, "Age")
tab_sex <- create_tab(vacc_1524, gender, "Gender")
tab_marital_status <- create_tab(vacc_1524, marital_status, "Marital status")
tab_year <- create_tab(vacc_1524, vacc_service_year, "Year")
tab_quarter <- create_tab(vacc_1524, vacc_quarter, "Quarter")
tab_weekday <- create_tab(vacc_1524, vacc_weekday, "Weekday")tab_combined <- bind_rows(
tab_sex,
tab_age,
tab_marital_status,
tab_year,
tab_quarter,
tab_weekday
)
tab_combined Category Label Respiratory vaccines Childhood vaccines Other
Gender Female 445 232 65
Gender Male 510 240 61
Age 0-19 146 66 23
Age 20-29 160 81 17
Age 30-39 131 76 18
Age 40-49 134 58 17
Age 50-59 126 65 19
Age 60-69 135 65 14
Age 70+ 123 61 18
Marital status Divorced 117 52 15
Marital status Married 458 218 61
Marital status Single/widowed 283 156 41
Marital status Missing 97 46 9
Year 2015 110 46 8
Year 2016 99 45 14
Year 2017 81 45 10
Year 2018 83 47 13
Year 2019 105 51 12
Year 2020 98 45 12
Year 2021 87 52 12
Year 2022 90 40 9
Year 2023 96 46 17
Year 2024 106 55 19
Quarter 1 237 115 40
Quarter 2 237 122 36
Quarter 3 228 117 22
Quarter 4 253 118 28
Weekday Friday 152 64 16
Weekday Monday 127 59 20
Weekday Saturday 111 68 22
Weekday Sunday 129 73 12
Weekday Thursday 145 62 19
Weekday Tuesday 138 71 19
Weekday Wednesday 153 75 18
tab_combined <- tab_combined |>
rowwise() |>
mutate(
Total = sum(
`Respiratory vaccines`,
`Childhood vaccines`,
Other,
na.rm = TRUE
),
is_missing = trimws(tolower(Label)) == "missing",
Respiratory = ifelse(
is_missing,
comma(`Respiratory vaccines`),
paste0(
comma(`Respiratory vaccines`),
" (",
round(100 * `Respiratory vaccines` / Total, 1),
")"
)
),
Childhood = ifelse(
is_missing,
comma(`Childhood vaccines`),
paste0(
comma(`Childhood vaccines`),
" (",
round(100 * `Childhood vaccines` / Total, 1),
")"
)
),
Other_grp = ifelse(
is_missing,
comma(Other),
paste0(comma(Other), " (", round(100 * Other / Total, 1), ")")
)
) |>
ungroup() |>
select(Category, Label, Respiratory, Childhood, Other_grp)
tab_combined# A tibble: 34 × 5
Category Label Respiratory Childhood Other_grp
<chr> <chr> <chr> <chr> <chr>
1 Gender Female 445 (60) 232 (31.3) 65 (8.8)
2 Gender Male 510 (62.9) 240 (29.6) 61 (7.5)
3 Age 0-19 146 (62.1) 66 (28.1) 23 (9.8)
4 Age 20-29 160 (62) 81 (31.4) 17 (6.6)
5 Age 30-39 131 (58.2) 76 (33.8) 18 (8)
6 Age 40-49 134 (64.1) 58 (27.8) 17 (8.1)
7 Age 50-59 126 (60) 65 (31) 19 (9)
8 Age 60-69 135 (63.1) 65 (30.4) 14 (6.5)
9 Age 70+ 123 (60.9) 61 (30.2) 18 (8.9)
10 Marital status Divorced 117 (63.6) 52 (28.3) 15 (8.2)
# ℹ 24 more rows
tab_combined <- tab_combined |>
group_by(Category) |>
mutate(Category = ifelse(row_number() == 1, Category, "")) |>
ungroup()
tab_combined# A tibble: 34 × 5
Category Label Respiratory Childhood Other_grp
<chr> <chr> <chr> <chr> <chr>
1 "Gender" Female 445 (60) 232 (31.3) 65 (8.8)
2 "" Male 510 (62.9) 240 (29.6) 61 (7.5)
3 "Age" 0-19 146 (62.1) 66 (28.1) 23 (9.8)
4 "" 20-29 160 (62) 81 (31.4) 17 (6.6)
5 "" 30-39 131 (58.2) 76 (33.8) 18 (8)
6 "" 40-49 134 (64.1) 58 (27.8) 17 (8.1)
7 "" 50-59 126 (60) 65 (31) 19 (9)
8 "" 60-69 135 (63.1) 65 (30.4) 14 (6.5)
9 "" 70+ 123 (60.9) 61 (30.2) 18 (8.9)
10 "Marital status" Divorced 117 (63.6) 52 (28.3) 15 (8.2)
# ℹ 24 more rows
totals <- vacc_1524 |> count(category) |> deframe()rm(
tab_age,
tab_sex,
tab_marital_status,
tab_year,
tab_quarter,
tab_weekday,
tab_combined,
#ft_vacc_summary,
totals,
create_tab
)# ------------1. Table without title and footer -------------------------------------------+
library(dplyr)
library(janitor)
library(tibble)
library(scales)
library(flextable)
#.......................Ensure grouping variables are characters............................
vacc_1524 <- vacc_1524 |>
mutate(across(c(age_group, gender, marital_status, vacc_service_year, vacc_quarter, vacc_weekday), as.character))
# ---------------- Table generation function ....................
create_tab <- function(data, var, label) {
tab_main <- data |>
filter(!is.na({{ var }})) |>
tabyl({{ var }}, category) |>
mutate(Category = label, .before = 1) |>
rename(Label = {{ var }}) |>
mutate(Label = as.character(Label))
na_count <- sum(is.na(pull(data, {{ var }})))
if (na_count > 0) {
missing_row <- tibble(
Category = label,
Label = "Missing",
`Respiratory vaccines` = sum(data$vacc_group3 == "Respiratory vaccines" & is.na(pull(data, {{ var }})), na.rm = TRUE),
`Childhood vaccines` = sum(data$vacc_group3 == "Childhood vaccines" & is.na(pull(data, {{ var }})), na.rm = TRUE),
Other = sum(data$vacc_group3 == "Other" & is.na(pull(data, {{ var }})), na.rm = TRUE)
)
tab_final <- bind_rows(tab_main, missing_row)
} else {
tab_final <- tab_main
}
return(tab_final)
}
# .................... Create summary tables ....................
tab_age <- create_tab(vacc_1524, age_group, "Age")
tab_sex <- create_tab(vacc_1524, gender, "Gender")
tab_marital_status <- create_tab(vacc_1524, marital_status, "Marital status")
tab_year <- create_tab(vacc_1524, vacc_service_year, "Year")
tab_quarter <- create_tab(vacc_1524, vacc_quarter, "Quarter")
tab_weekday <- create_tab(vacc_1524, vacc_weekday, "Weekday")
# ......................Combine all tabs ....................
tab_combined <- bind_rows(
tab_sex,
tab_age,
tab_marital_status,
tab_year,
tab_quarter,
tab_weekday
)
# ....................... Create row % and format as text ....................
tab_combined <- tab_combined |>
rowwise() |>
mutate(
Total = sum(
`Respiratory vaccines`,
`Childhood vaccines`,
Other,
na.rm = TRUE
),
is_missing = trimws(tolower(Label)) == "missing",
Respiratory = ifelse(is_missing,
comma(`Respiratory vaccines`),
paste0(comma(`Respiratory vaccines`), " (", round(100 * `Respiratory vaccines` / Total, 1), ")")),
Childhood = ifelse(is_missing,
comma(`Childhood vaccines`),
paste0(comma(`Childhood vaccines`), " (", round(100 * `Childhood vaccines` / Total, 1), ")")),
Other_grp = ifelse(is_missing,
comma(Other),
paste0(comma(Other), " (", round(100 * Other / Total, 1), ")"))
) |>
ungroup() |>
select(Category, Label, Respiratory, Childhood, Other_grp)
# ............................. Remove repeated variable names ....................
tab_combined <- tab_combined |>
group_by(Category) |>
mutate(Category = ifelse(row_number() == 1, Category, "")) |>
ungroup()
# ........................... Get total counts per vaccine group ....................
totals <- vacc_1524 |>
count(category) |>
deframe()
# .................... Create flextable ....................
ft_vacc_summary <- tab_combined |>
flextable() |>
set_header_labels(
Category = "Variable type",
Label = "Values",
Respiratory = "Number (%)ᵃ",
Childhood = "Number (%)ᵃ",
Other_grp = "Number (%)ᵃ"
) |>
add_header_row(
# Second row
values = c(
"",
"",
paste0(
"Respiratory vaccines (N = ",
format(totals["Respiratory vaccines"], big.mark = ","),
")"
),
paste0(
"Childhood vaccines (N = ",
format(totals["Childhood vaccines"], big.mark = ","),
")"
),
paste0("Other (N = ", format(totals["Other"], big.mark = ","), ")")
)
) |>
add_header_row(
# First row (merged header)
values = c("Description", "Vaccination uptake"),
colwidths = c(2, 3)
) |>
theme_box() |>
set_table_properties(layout = "autofit", width = 1) |>
width(j = 1, width = 1.5) |>
width(j = 2, width = 2.5) |>
width(j = 3:4, width = 1.5) |>
align(align = "justify", part = "all") |>
border_remove() |>
border(
part = "header",
i = 1,
border.top = fp_border(color = "black", width = 1)
) |>
border(
part = "header",
i = 3,
border.bottom = fp_border(color = "black", width = 1)
) |>
border_inner_h(
part = "header",
border = fp_border(color = "black", width = 0.8)
) |>
# border_inner_h(
# part = "body",
# border = fp_border(color = "black", width = 0.8)
# ) |>
# border(
# part = "body",
# i = nrow(tab_combined),
# border.bottom = fp_border(color = "black", width = 1)
# ) |>
set_caption(as_paragraph(as_chunk("Table: Vaccination uptake across service year, quarter, and weekday",
props = fp_text(font.size = 14, font.family = "Times New Roman", bold = FALSE)))) |>
fontsize(part = "header", size = 12) |>
fontsize(part = "body", size = 11) |>
fontsize(part = "footer", size = 10) |>
font(part = "all", fontname = "Times New Roman") |>
bg(part = "header", bg = "lightblue") |>
bg(j = 1:5, i = seq(1, nrow(tab_combined), 2), bg = "white") |>
bg(j = 1:5, i = seq(2, nrow(tab_combined), 2), bg = "lightgray") |>
add_footer_lines(
values = "ᵃ Vaccination proportions were calculated row-wise (i.e., by row)."
)
# --- Display the table ---
ft_vacc_summaryDescription | Vaccination uptake | |||
|---|---|---|---|---|
Respiratory vaccines (N = 955) | Childhood vaccines (N = 472) | Other (N = 126) | ||
Variable type | Values | Number (%)ᵃ | Number (%)ᵃ | Number (%)ᵃ |
Gender | Female | 445 (60) | 232 (31.3) | 65 (8.8) |
Male | 510 (62.9) | 240 (29.6) | 61 (7.5) | |
Age | 0-19 | 146 (62.1) | 66 (28.1) | 23 (9.8) |
20-29 | 160 (62) | 81 (31.4) | 17 (6.6) | |
30-39 | 131 (58.2) | 76 (33.8) | 18 (8) | |
40-49 | 134 (64.1) | 58 (27.8) | 17 (8.1) | |
50-59 | 126 (60) | 65 (31) | 19 (9) | |
60-69 | 135 (63.1) | 65 (30.4) | 14 (6.5) | |
70+ | 123 (60.9) | 61 (30.2) | 18 (8.9) | |
Marital status | Divorced | 117 (63.6) | 52 (28.3) | 15 (8.2) |
Married | 458 (62.1) | 218 (29.6) | 61 (8.3) | |
Single/widowed | 283 (59) | 156 (32.5) | 41 (8.5) | |
Missing | 0 | 0 | 0 | |
Year | 2015 | 110 (67.1) | 46 (28) | 8 (4.9) |
2016 | 99 (62.7) | 45 (28.5) | 14 (8.9) | |
2017 | 81 (59.6) | 45 (33.1) | 10 (7.4) | |
2018 | 83 (58) | 47 (32.9) | 13 (9.1) | |
2019 | 105 (62.5) | 51 (30.4) | 12 (7.1) | |
2020 | 98 (63.2) | 45 (29) | 12 (7.7) | |
2021 | 87 (57.6) | 52 (34.4) | 12 (7.9) | |
2022 | 90 (64.7) | 40 (28.8) | 9 (6.5) | |
2023 | 96 (60.4) | 46 (28.9) | 17 (10.7) | |
2024 | 106 (58.9) | 55 (30.6) | 19 (10.6) | |
Quarter | 1 | 237 (60.5) | 115 (29.3) | 40 (10.2) |
2 | 237 (60) | 122 (30.9) | 36 (9.1) | |
3 | 228 (62.1) | 117 (31.9) | 22 (6) | |
4 | 253 (63.4) | 118 (29.6) | 28 (7) | |
Weekday | Friday | 152 (65.5) | 64 (27.6) | 16 (6.9) |
Monday | 127 (61.7) | 59 (28.6) | 20 (9.7) | |
Saturday | 111 (55.2) | 68 (33.8) | 22 (10.9) | |
Sunday | 129 (60.3) | 73 (34.1) | 12 (5.6) | |
Thursday | 145 (64.2) | 62 (27.4) | 19 (8.4) | |
Tuesday | 138 (60.5) | 71 (31.1) | 19 (8.3) | |
Wednesday | 153 (62.2) | 75 (30.5) | 18 (7.3) | |
ᵃ Vaccination proportions were calculated row-wise (i.e., by row). | ||||
datatable(
tab_combined,
rownames = FALSE,
extensions = 'Buttons',
options = list(
dom = 'Bfrtip',
buttons = c('copy', 'csv', 'excel', 'pdf', 'print'),
pageLength = 25,
autoWidth = TRUE,
columnDefs = list(list(width = '200px', targets = c(0, 1)))
),
caption = htmltools::tags$caption(
style = 'caption-side: top; font-size: 18px; font-family: "Times New Roman"; font-weight: bold;',
'Table: Vaccination uptake across service year, quarter, and weekday'
),
class = 'stripe hover cell-border'
)