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()
)
gtExtras
Themes: 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
functionstidyeval
Tidy 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 gt
gtExtras::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 practicempg | 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 practicempg | 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 gt
gtExtras::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
+ gt
gt_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!