
gt and gtExtras
Why do we care about tables?
Why do we care about graphs?
Both Graphs and tables are tools for communication
Better Graphs/Tables are better communication
grammar of graphicsggplot2 is an application of the grammar of graphics for R
A default dataset and set of mappings from variables to aesthetics
One or more layers of geometric objects
One scale for each aesthetic mapping used
A coordinate system
The facet specification
grammar of graphics1Easy enough to rapidly prototype graphics at the “speed of thought”

Powerful enough for final “publication”

Construct a wide variety of useful tables with a cohesive set of table parts. These include the table header, the stub, the column labels and spanner column labels, the table body and the table footer.
Easy enough to rapidly prototype

Powerful enough for final “publication”


🌶️ Hot take - Horizontal bar charts and simple charts with facets are already tables.

| body_mass | plot | |
|---|---|---|
| Adelie | ||
| male | 4,043.49 | |
| female | 3,368.84 | |
| Chinstrap | ||
| male | 3,938.97 | |
| female | 3,527.21 | |
| Gentoo | ||
| male | 5,484.84 | |
| female | 4,679.74 | |
ggplot(grp_df, aes(x = body_mass, y = sex)) +
geom_col(fill = "purple") +
geom_text(
aes(
label = scales::label_number(big.mark = ",", accuracy = .1)(body_mass)
),
position = position_nudge(x = -100), hjust = 1,
color = "white", fontface = "bold"
) +
facet_wrap(~species, ncol = 1) +
bbplot::bbc_style() +
theme(
panel.grid.major.x = element_line(color = "grey"),
panel.grid.major.y = element_blank()
)


gtExtrasThemes: 7 themes that style almost every element of a gt table, built off of data journalism-styled tables
Utilities: Helper functions for aligning/padding numbers, adding fontawesome icons, images, highlighting, dividers, styling by group, creating two tables or two column layouts, extracting ordered data from a gt table internals, or generating a random dataset for reprex
Plotting: 12 plotting functions for inline sparklines, win-loss charts, distributions (density/histogram), percentiles, dot + bar, bar charts, confidence intervals, or summarizing an entire dataframe!
Colors: 3 functions, a palette for “Hulk” style scale (purple/green), coloring rows with good defaults, or adding a “color box” along with the cell value





# A tibble: 3 × 4
cyl mean sd mpg_data
<dbl> <dbl> <dbl> <list>
1 4 26.7 4.51 <dbl [11]>
2 6 19.7 1.45 <dbl [7]>
3 8 15.1 2.56 <dbl [14]>
gtExtras plotting provides an opinionated API that predominantly supports one way of doing things, rather than providing maximum flexibility to accommodate different workflows.
One way == one-liner
The “one way” is almost always list() data with focused function arguments.
Ultimate goal: Simple, Inline, Effective




# gtExtras can calculate basic conf int
# using confint() function
ci_table <- generate_df(
n = 50, n_grps = 3,
mean = c(10, 15, 20), sd = c(10, 10, 10),
with_seed = 37
) %>%
dplyr::group_by(grp) %>%
dplyr::summarise(
n = dplyr::n(),
avg = mean(values),
sd = sd(values),
list_data = list(values)
) %>%
gt::gt() %>%
gt_plt_conf_int(list_data, ci = 0.9)
# You can also provide your own values
# based on your own algorithm/calculations
pre_calc_ci_tab <- dplyr::tibble(
mean = c(12, 10), ci1 = c(8, 5), ci2 = c(16, 15),
ci_plot = c(12, 10)
) %>%
gt::gt() %>%
gt_plt_conf_int(
column = ci_plot,
ci_columns = c(ci1, ci2),
palette = c("red", "lightgrey", "black", "red")
)
gt functionstidyevalTidy evaluation is a special type of non-standard evaluation used throughout the tidyverse. - Programming with
dplyr
This powers the ability to do:
You can get most tidyeval things working with just two new concepts:
{{ var }}, also known as ‘curly-curly’... for many argumentstidyeval in practicetidyeval in practicetidyeval with gtgtExtras::gt_add_divider(), simplifiedlibrary(gt)
gt_add_divider <- function(gt_object, columns, ..., include_labels = TRUE) {
stopifnot("Table must be of class 'gt_tbl'" = "gt_tbl" %in% class(gt_object))
gt_object %>%
tab_style(
# dots include passed named arguments to the internal function
style = cell_borders(sides = "right", ...),
locations = if (isTRUE(include_labels)) {
# columns to affect
list(cells_body(columns = {{ columns }}),
cells_column_labels(columns = {{ columns }}))
} else {
cells_body(columns = {{ columns }})
}
)
}tidyeval with gt in practice| 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 |
tidyeval with gt in practice| 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 |
gt - more argumentsbasic_table %>%
# optional arguments accepted by name via `...`
gt_add_divider(cyl, weight = px(2), 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 |
gt - moar argumentsbasic_table %>%
### include_labels as an existing argument
gt_add_divider(
c(cyl,mpg), weight = px(3),
color = "orange", include_labels = FALSE
)| 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 |
tidyeval with gtgtExtras::gt_add_divider(), simplifiedlibrary(gt)
gt_add_divider <- function(gt_object, columns, ..., include_labels = TRUE) {
stopifnot("Table must be of class 'gt_tbl'" = "gt_tbl" %in% class(gt_object))
gt_object %>%
tab_style(
# dots include passed named arguments to the internal function
style = cell_borders(sides = "right", ...),
locations = if (isTRUE(include_labels)) {
# columns to affect
list(cells_body(columns = {{ columns }}),
cells_column_labels(columns = {{ columns }}))
} else {
cells_body(columns = {{ columns }})
}
)
}gt themes!gt_theme_nytimes <- function(gt_object, ...){
stopifnot("'gt_object' must be a 'gt_tbl', have you accidentally passed raw data?" = "gt_tbl" %in% class(gt_object))
gt_object %>%
tab_options(
heading.align = "left",
column_labels.border.top.style = "none",
table.border.top.style = "none",
column_labels.border.bottom.style = "none",
column_labels.border.bottom.width = 1,
column_labels.border.bottom.color = "#334422",
table_body.border.top.style = "none",
table_body.border.bottom.color = "white",
heading.border.bottom.style = "none",
data_row.padding = px(7),
column_labels.font.size = px(12),
...
) %>%
tab_style(
style = cell_text(
color = "darkgrey",
font = google_font("Source Sans Pro"),
transform = "uppercase"
),
locations = cells_column_labels(everything())
) %>%
tab_style(
style = cell_text(font = google_font("Libre Franklin"),
weight = 800),
locations = cells_title(groups = "title")
) %>%
tab_style(
style = cell_text(
font = google_font("Source Sans Pro"),
weight = 400
),
locations = cells_body()
)
}gt themesgt_theme_basic <- function(gt_object, ...){
gt_object %>%
tab_options(
heading.align = "left",
data_row.padding = px(7),
column_labels.font.size = px(12),
...
) %>%
tab_style(
style = cell_text(
color = "darkgrey",
font = google_font("Source Sans Pro"),
transform = "uppercase"
),
locations = cells_column_labels(everything())
)
}gt + Plotsgt + PlotsgtExtras + Plots3 step process:
list() data by row/groupggplot2 graph and save to diskStep infinity: protect against user-input/error handling
gtExtras + create graphplot_out <- ggplot(data = NULL, aes(x = vals, y = factor("1"))) +
geom_col(width = 0.1, color = palette[1], fill = palette[1]) +
geom_vline(
xintercept = target_vals, color = palette[2], size = 1.5,
alpha = 0.7
) +
geom_vline(xintercept = 0, color = "black", size = 1) +
theme_void() +
coord_cartesian(xlim = c(0, max_val)) +
scale_x_continuous(expand = expansion(mult = c(0, 0.15))) +
scale_y_discrete(expand = expansion(mult = c(0.1, 0.1))) +
theme_void() +
theme(
legend.position = "none",
plot.margin = margin(0, 0, 0, 0, "pt"),
plot.background = element_blank(),
panel.background = element_blank()
)gtExtras + save/readgtExtras + gtgt_plt_bullet <- function(gt_object, column = NULL, target = NULL, width = 65,
palette = c("grey", "red")) {
# extract the values from specified columns
all_vals <- gt_index(gt_object, {{ column }})
max_val <- max(all_vals, na.rm = TRUE)
length_val <- length(all_vals)
target_vals <- gt_index(gt_object, {{ target }})
col_bare <- gt_index(gt_object, {{ column }}, as_vector = FALSE) %>%
dplyr::select({{ column }}) %>%
names()
tab_out <- gt_object %>%
text_transform(
locations = cells_body({{ column }}),
fn = function(x) {
bar_fx <- function(vals, target_vals) {
...plotting_code...
...write_read_code...
img_plot
}
tab_built <- mapply(bar_fx, all_vals, target_vals)
tab_built
}
) %>%
gt::cols_align(align = "left", columns = {{ column }}) %>%
gt::cols_hide({{ target }})
tab_out
}gt “extra” functions!