Advanced Table Skills

November 3, 2026

gtExtras

The gtExtras package

  • By now we know the difference between a graph and a table
    • Tables are good for comparing individual values
    • Graphs are for visualizing a whole bunch of values
  • Usually we keep our graphs and tables separate
  • But are there times when we might want to combine them?
  • gtExtras lets us include sparklines, histograms, etc. in our tables

Democracy Example


library(vdemdata)

# Grab the polyarchy scores for the BRICS countries between 2000 and 2020
brics_dem <- vdem |>
  filter(year >= 2000 & year <= 2020,
         country_text_id %in% c("BRA", "RUS", "IND", "CHN", "ZAF")) |>
  select(country = country_name, year, v2x_polyarchy)

Democracy Example


library(dplyr)

dem_summary <- brics_dem|>
  group_by(country) |>
  summarize(
    max = max(v2x_polyarchy, na.rm = TRUE),
    mean = mean(v2x_polyarchy, na.rm = TRUE),
    sd = sd(v2x_polyarchy, na.rm = TRUE),
    dem_data = list(v2x_polyarchy),
    .groups = "drop"
  )

Democracy Example


library(gtExtras)

dem_summary |>
  gt() |>
  gt_plt_sparkline(dem_data) #from gtExtras
country max mean sd dem_data
Brazil 0.875 0.83452381 0.058298901 0.7
China 0.103 0.09042857 0.009615464 0.08
India 0.739 0.63666667 0.112689988 0.4
Russia 0.403 0.30238095 0.040974963 0.26
South Africa 0.779 0.75795238 0.018834745 0.7

Your Turn!


  • Download democracy data for one year
  • Indicator is your choice
  • Add region codes
  • Group and summarize by region
  • Add an appropriate inline plot to your table

When Not to Use a Table

Not a Good Table

name estimate lower_90 upper_90
Barnstable 40442 39554 41330
Berkshire 33040 32074 34006
Bristol 36910 36347 37473
Dukes 40119 34791 45447
Essex 39756 39174 40338
Franklin 34775 33712 35838
Hampden 32262 31707 32817
Hampshire 30795 29868 31722
Middlesex 51808 51337 52279
Nantucket 45717 38260 53174
Norfolk 52591 51991 53191
Plymouth 43684 43014 44354
Suffolk 39200 38624 39776
Worcester 39009 38454 39564

Use Confidence Intervals Instead

Download and Wrangle the Data

get_acs() returns an estimate and a margin of error (moe) for each county

library(tidycensus)
library(janitor)
library(stringr)

mass_med_inc <- get_acs(
  geography = "county",
  variables = c(median_income = "B06011_001"),
  state = "MA",
  year = 2021
  ) |>
  clean_names() |>
  mutate(name = str_replace_all(name, " County, Massachusetts", ""))

Build the Confidence Intervals

The ACS reports margins of error at the 90% level, so estimate ± moe gives a 90% confidence interval

mass_med_inc <- mass_med_inc |>
  mutate(
    lower_90 = estimate - moe, # lower bound of the 90% CI
    upper_90 = estimate + moe  # upper bound of the 90% CI
  ) |>
  select(name, estimate, lower_90, upper_90)

Plot the Confidence Intervals


library(ggplot2)

mass_med_inc |>
  ggplot(aes(x = estimate, y = reorder(name, estimate))) +
  geom_errorbar(aes(xmin = lower_90, xmax = upper_90)) +
  geom_point(color = "red", size = 2) +
  labs(title = "Household income by county in Massachusetts",
       subtitle = "2017-2021 American Community Survey",
       y = "",
       x = "Median Income", 
       caption = "ACS estimate (bars represent 90% confidence intervals)") +
  theme_minimal()

Your Turn!


  • Download data on median income for another state
  • Plot the income estimates for that state

Time Permitting

reactable


  • reactable is an R package that lets you create interactive tables
  • You can sort, filter, and search the table
  • Based on the React Table library
  • Let’s explore it together!