Code
library(dplyr)
library(gapminder)
gap2007 <- gapminder %>% filter(year == 2007)The Data Visualization page covers when tables beat graphs (roughly: 20 numbers or fewer, or many localized comparisons) and how to design them (sort by values, not alphabetically; shade; round a lot). This page shows how to actually build such tables in R, reproducibly.
gtgt is to tables what ggplot2 is to graphs: a grammar. You pipe a data frame in and add layers of formatting.
library(gt)
gap2007 %>%
group_by(continent) %>%
summarise(
Countries = n(),
`Life Expectancy` = mean(lifeExp),
`GDP per Capita` = mean(gdpPercap),
Population = sum(pop)
) %>%
arrange(desc(`Life Expectancy`)) %>% # sort by values, not alphabet
gt() %>%
fmt_number(columns = `Life Expectancy`, decimals = 1) %>%
fmt_currency(columns = `GDP per Capita`, decimals = 0) %>%
fmt_number(columns = Population, suffixing = TRUE) %>% # 1.2B not 1200000000
data_color(columns = `Life Expectancy`, palette = "viridis") %>%
tab_header(title = "The World in 2007, by Continent") %>%
tab_source_note("Data: Gapminder")| The World in 2007, by Continent | ||||
| continent | Countries | Life Expectancy | GDP per Capita | Population |
|---|---|---|---|---|
| Oceania | 2 | 80.7 | $29,810 | 24.55M |
| Europe | 30 | 77.6 | $25,054 | 586.10M |
| Americas | 25 | 73.6 | $11,003 | 898.87M |
| Asia | 33 | 70.7 | $12,473 | 3.81B |
| Africa | 52 | 54.8 | $3,089 | 929.54M |
| Data: Gapminder | ||||
Note how the design rules from the visualization page appear as code: arrange() sorts by the substantively interesting column, fmt_number(decimals = 1) rounds a lot, data_color() shades cells by value so patterns are visible.
modelsummaryHand-copying coefficients into Word tables is the leading cause of avoidable errors in submitted papers. modelsummary builds the table straight from the model objects.
library(modelsummary)
m1 <- lm(lifeExp ~ log(gdpPercap), data = gap2007)
m2 <- lm(lifeExp ~ log(gdpPercap) + continent, data = gap2007)
modelsummary(
list("Bivariate" = m1, "+ Continent" = m2),
coef_rename = c(
"log(gdpPercap)" = "Log GDP per Capita",
"continentAmericas" = "Americas",
"continentAsia" = "Asia",
"continentEurope" = "Europe",
"continentOceania" = "Oceania"
),
gof_map = c("nobs", "r.squared"),
stars = TRUE,
notes = "Reference category: Africa. OLS estimates, standard errors in parentheses."
)| Bivariate | + Continent | |
|---|---|---|
| + p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001 | ||
| Reference category: Africa. OLS estimates, standard errors in parentheses. | ||
| (Intercept) | 4.950 | 20.138*** |
| (3.858) | (4.033) | |
| Log GDP per Capita | 7.203*** | 4.631*** |
| (0.442) | (0.527) | |
| Americas | 11.694*** | |
| (1.655) | ||
| Asia | 10.114*** | |
| (1.476) | ||
| Europe | 11.268*** | |
| (1.894) | ||
| Oceania | 12.929** | |
| (4.521) | ||
| Num.Obs. | 142 | 142 |
| R2 | 0.654 | 0.767 |
modelsummary() outputs to HTML, LaTeX, and Word depending on your output format — one code chunk serves the website and the paper. Its sibling datasummary_skim() gives a one-line descriptive overview of a whole dataset:
You have both tools now. A useful rule:
Many papers now do both: plot in the text, full table in the appendix. Since both come from the same model object, this costs you two chunks, not an afternoon.
gtsummary — publication-ready descriptive and regression tables with sensible medical/social-science defaults.kableExtra — lighter-weight alternative to gt, good for PDF output.gt::gtsave() exports any gt table to .png, .html, .rtf, or .tex.