ETC5523: Communicating with Data

Tutorial 5

Author

Michael Lydeamore

Published

1 May 2001

🎯 Objectives

  • extract model information with accessor functions and broom
  • build, critique and refine regression and descriptive tables with gtsummary and kableExtra
  • apply communication choices for precision, units, alignment and labels
  • select and interpret the results that matter for a stated audience

Install the R-packages (once per machine):

install.packages(c("broom", "carData", "gtsummary", "kableExtra", "tidyverse"))

The Salaries data are in the carData package — no separate download is needed.

Data: Academic salaries

The Salaries data set in carData contains 9-month salaries for 397 college faculty in the US (carData::Salaries). Variables:

  • salary — 9-month salary (USD)
  • rankAsstProf, AssocProf, Prof
  • disciplineA (theoretical) or B (applied)
  • yrs.since.phd — years since PhD
  • yrs.service — years of service
  • sexFemale / Male

We use one model throughout the tutorial so you can see the full workflow from model object to communicative table.

library(tidyverse)
library(carData)
library(broom)
library(gtsummary)
library(kableExtra)

data(Salaries, package = "carData")
glimpse(Salaries)
Rows: 397
Columns: 6
$ rank          <fct> Prof, Prof, AsstProf, Prof, Prof, AssocProf, Prof, Prof,…
$ discipline    <fct> B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, A, A,…
$ yrs.since.phd <int> 19, 20, 4, 45, 40, 6, 30, 45, 21, 18, 12, 7, 1, 2, 20, 1…
$ yrs.service   <int> 18, 16, 3, 39, 41, 6, 23, 45, 20, 18, 8, 2, 1, 0, 18, 3,…
$ sex           <fct> Male, Male, Male, Male, Male, Male, Male, Male, Male, Fe…
$ salary        <int> 139750, 173200, 79750, 115000, 141500, 97000, 175000, 14…

👥 Exercise 5A

From model object to communicative regression table

fit <- lm(salary ~ rank + yrs.since.phd + sex, data = Salaries)

fit contains much more than a printed summary. Your audience is a technically competent, time-poor head of school who wants to know whether rank and sex are associated with salary after adjusting for time since PhD.

A1. Extract the right pieces

Using fit, compare what you get from:

  • coef(fit) and confint(fit) / summary(fit)
  • broom::tidy(fit, conf.int = TRUE) and broom::glance(fit) and broom::augment(fit) (first 6 rows)

Which broom verb gives you:

  1. one row per term,
  2. one row per model,
  3. one row per observation?

Why is tidy(fit, conf.int = TRUE) a better starting point for a table than copying values from summary(fit)?

A2. Start from a default table

Build the default regression table with gtsummary:

tbl_regression(fit, intercept = TRUE)

Render it (run it yourself). Then critique it for the head-of-school audience:

  1. Which row answers the substantive question? Is the intercept useful here?
  2. Can the labels be understood without seeing the R code?
  3. Does every column (e.g. p-values) need to be shown?
  4. Is the displayed precision appropriate for salaries in dollars?

A3. Refine the table for its purpose

Refine the table by adapting the lecture workflow:

  • hide the intercept (intercept = FALSE),
  • give plain labels with units,
  • round to an appropriate precision,
  • hide p-values for this audience,
  • embolden variable labels.

Render the refined table. What changed and why?

A4. Write the take-away sentence

Using the refined table (or tidy(fit, conf.int = TRUE)), write one sentence for the head of school that:

  • names the comparison (e.g. Prof vs AsstProf, or Male vs Female, holding yrs.since.phd constant),
  • states the size in dollars with its 95% confidence interval,
  • avoids claiming causation (“associated with”, not “caused by”).

🛠️ Exercise 5B

Descriptive tables and communication polish

B1. A descriptive summary that makes comparison easy

Produce a descriptive summary by sex using the same workflow as the lecture’s by = cyl example. Adapt it for Salaries:

Salaries |>
  select(salary, yrs.since.phd, yrs.service, rank, discipline, sex) |>
  tbl_summary(
    by = sex,
    label = list(
      salary ~ "Salary (USD)",
      yrs.since.phd ~ "Years since PhD",
      yrs.service ~ "Years of service"
    ),
    statistic = all_continuous() ~ "{mean} ({sd})",
    digits = all_continuous() ~ 0,
    missing = "no"
  ) |>
  bold_labels()

Questions:

  1. Why might "{mean} ({sd})" be reasonable for salary here, and when would "{median} ({p25}, {p75})" be preferred?
  2. What does missing = "no" do, and when would you set it to "ifany"?
  3. How do the column labels and digits choices reflect the communication principles for precision and units?

B2. Polish a table for publication

The code below produces a final-product table badly. It repeats units in every cell, shows 5 decimal places, lacks comma grouping, and misaligns numbers.

bad <- Salaries |>
  slice_head(n = 4) |>
  transmute(
    Rank = rank,
    Salary = paste0("$", salary, " USD"),
    `Years since PhD` = as.character(round(yrs.since.phd + rnorm(4)/1000, 5))
  )

bad |>
  kbl(align = "lcc", caption = "First four faculty (poorly formatted)") |>
  kable_classic(full_width = FALSE)
First four faculty (poorly formatted)
Rank Salary Years since PhD
Prof $139750 USD 18.99976
Prof $173200 USD 19.9997
AsstProf $79750 USD 3.99949
Prof $115000 USD 45.00037

Task: Fix the table using kableExtra (as in the lecture). Your polished table should:

  • put units once in the column header (e.g. Salary (USD)), not in each cell,
  • show salaries as whole dollars with comma grouping (e.g. 139,750),
  • display years with at most 1 decimal and trailing zeroes where needed,
  • right-align numeric columns and left-align text, centre any spanner if you add one,
  • apply kable_classic() styling and add a clear caption.

Hint: scales::comma(), formatC() or kbl(digits=, format.args=) + column_spec() help. The lecture’s “Numerical precision”, “Column alignment”, and “Labels within tables” slides show the exact patterns.

Take-away checklist (from lecture)
  • Start with the audience’s question, not everything the model produced.
  • Show estimates with uncertainty (CI), units, reference categories, and sample/context — not just p-values.
  • Choose precision, alignment, and labels deliberately; polish with gtsummary + kableExtra.

Workshop 5 continues this by turning a statistical output into a full results paragraph and a self-contained caption.