U.S. Judges

Very few judges have been women, historically speaking. Judgeships are lifetime appointments and the data indicates that most stay in their position 20+ years.

Sankey Diagram
R
Author

Steven Villalon

Published

June 10, 2025


Final plot

Question of Interest

In a single chart, demonstrate the ratio of nominations that came from each party, the gender of nominees, and how long they served.

Goal: Make a Sankey diagram showing the flow of nominations through gender and length of service.

1. Packages & Dependencies

Show Code
# Load packages
library(tidyverse)
library(tidytuesdayR)
library(here)
library(showtext)
library(ggtext)
library(ggalluvial)
library(scales)

# Load helper functions
source(here::here("R/utils/tidy_tuesday_helpers.R"))

# Set project title
title <- "US Judges"
tt_date <- "2025-06-10"

2. Load Data

Show Code
# Load data from tidytuesdayR package
tuesdata <- tidytuesdayR::tt_load(tt_date)

# Extract elements from tuesdata
appointments <- tuesdata$judges_appointments
people <- tuesdata$judges_people

# Remove tuesdata file
rm(tuesdata)

3. Examine Data

Show Code
# View data
head(appointments)
# A tibble: 6 × 15
  judge_id court_name  court_type president_name president_party nomination_date
     <dbl> <chr>       <chr>      <chr>          <chr>           <chr>          
1     3419 U. S. Dist… USDC       Barack Obama   Democratic      07/28/2011     
2        1 U. S. Dist… USDC       Franklin D. R… Democratic      02/03/1936     
3        2 U. S. Dist… USDC       Rutherford B.… Republican      01/06/1880     
4        3 U. S. Dist… USDC       Ronald Reagan  Republican      07/22/1982     
5        4 U. S. Dist… USDC       Jimmy Carter   Democratic      09/28/1979     
6        5 U. S. Dist… USDC       Gerald Ford    Republican      06/18/1976     
# ℹ 9 more variables: predecessor_last_name <chr>,
#   predecessor_first_name <chr>, senate_confirmation_date <chr>,
#   commission_date <chr>, chief_judge_begin <dbl>, chief_judge_end <dbl>,
#   retirement_from_active_service <chr>, termination_date <chr>,
#   termination_reason <chr>
Show Code
head(people)
# A tibble: 6 × 13
  judge_id name_first name_middle name_last name_suffix birth_date
     <dbl> <chr>      <chr>       <chr>     <chr>            <dbl>
1     3419 Ronnie     <NA>        Abrams    <NA>              1968
2        1 Matthew    T.          Abruzzo   <NA>              1889
3        2 Marcus     Wilson      Acheson   <NA>              1828
4        3 William    Marsh       Acker     Jr.               1927
5        4 Harold     Arnold      Ackerman  <NA>              1928
6        5 James      Waldo       Ackerman  <NA>              1926
# ℹ 7 more variables: birthplace_city <chr>, birthplace_state <chr>,
#   death_date <dbl>, death_city <chr>, death_state <chr>, gender <chr>,
#   race <chr>

4. Cleaning

Show Code
# Join tables and remove unneccesary columns
df <- appointments |> 
  left_join(people, by = "judge_id") |> 
  select(judge_id, name_first, name_middle, name_last, president_party, gender, commission_date, termination_date)

# Remove judges nominated by other parties (e.g., Whig, Federalist, etc.)
df <- df |> 
    mutate(president_party_grouped = case_when(
      president_party == "Republican" ~ "Republican",
      president_party == "Democratic" ~ "Democrat",
      TRUE ~ "Other"
  )) |> 
  filter(president_party_grouped != "Other") |> 
  select(-president_party)
Show Code
# Convert dates from character to Date type and calculate service length
df <- df |> 
    mutate(start_date = as.Date(commission_date, format = "%m/%d/%Y"),
           end_date = as.Date(termination_date, format = "%m/%d/%Y"),
           end_date = if_else(
             is.na(end_date),
             Sys.Date(),
             end_date),
           length_of_service = round(as.numeric(end_date - start_date) / 365.25, 1)
    )
Show Code
# Create service length buckets
 df <- df |> 
   mutate(length_of_service = case_when(
    length_of_service < 5 ~ "0 - 5 years",
    length_of_service < 10 ~ "5 - 10 years",
    length_of_service < 15 ~ "10 - 15 years",
    length_of_service < 20 ~ "15 - 20 years",
    length_of_service >= 20 ~ "20+ years"
    ))
Show Code
# Group data for the Sankey
agg_df <- as.data.frame(table(df$president_party_grouped, 
                              df$gender, 
                              df$length_of_service))

# Add column names
colnames(agg_df) <- c("presidents_party", "judge_gender", "service_length", "freq")

# Re-order factor levels
agg_df$service_length <- fct_relevel(agg_df$service_length, c("0 - 5 years",
                                                              "5 - 10 years", 
                                                              "10 - 15 years", 
                                                              "15 - 20 years", 
                                                              "20+ years"))

5. Visualization

Show Code
# Load font
font_add_google("Merriweather", "merriweather")
showtext_auto()
showtext_opts(dpi = 300)

# Make plot
final_plot <- ggplot(agg_df) +
  aes(
    axis1 = presidents_party,
    axis2 = judge_gender,
    axis3 = service_length,
    y = freq
  ) +
  geom_alluvium(
    aes(fill = presidents_party),
    width = 1 / 12
  ) +
  scale_fill_manual(values = c(
    "Democrat" = "#3B77AF",
    "Republican" = "#C43D35"
  )) +
  geom_stratum(
    width = 0.4,
    fill = "gray80"
  ) +
  geom_text(
    stat = "stratum",
    aes(label = after_stat(stratum)),
    family = "merriweather",
    color = "#4A4A4A"
  ) +
  scale_x_discrete(
    limits = c("President's Party", "Gender", "Service Length"),
    expand = c(0.1, 0.1)
  ) +
  scale_y_continuous(labels = scales::comma) +
  theme_minimal(base_family = "merriweather") +
  labs(
    title = "U.S. Judge Appointments Since 1789",
    subtitle = "Federal appointments are a big deal because most judges end up serving more than 20 years. \nRepublican presidents have appointed 208 more judges than Democratic presidents. Historically, \nmost judges have been men, but women are increasingly holding these positions.",
    y = "Appointments",
    caption = "\nChart produced by Steven Villalon for Tidy Tuesday exercise on June 10, 2025."
  ) +
  theme(
    plot.background = element_rect(fill = "#EFDECD"),
    panel.background = element_rect(fill = "#EFDECD"),
    panel.grid = element_blank(),
    legend.position = "none",
    plot.margin = margin(t = 20, r = 20, b = 20, l = 20),
    plot.title = element_text(
      face = "bold",
      size = 20,
      color = "#8A3324"
    ),
    plot.subtitle = element_text(
      size = 10,
      color = "gray30"
    ),
    plot.caption = element_text(
      hjust = 0,
      color = "gray30"
    ),
    axis.text.x = element_text(
      size = 12,
      color = "gray30"
    ),
    axis.title.y = element_text(
      size = 12,
      color = "gray30",
      margin = margin(r = 10)
    )
  )

final_plot

6. Export Visualization

Show Code
# Select file formats to export to
formats_to_export <- c("png", "svg")

# Save files to the output folder (uses custom R script)
save_tt_plots(
  plot = final_plot, 
  title = title, 
  date = tt_date,
  output_folder = "output", 
  formats = formats_to_export, 
  height = 6,
  width = 8,
  dpi = 300
  )

7. Session Info

R version 4.4.1 (2024-06-14)
Platform: x86_64-apple-darwin20
Running under: macOS Ventura 13.7.6

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.4-x86_64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-x86_64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: America/New_York
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] scales_1.4.0       ggalluvial_0.12.5  ggtext_0.1.2       showtext_0.9-7    
 [5] showtextdb_3.0     sysfonts_0.8.9     here_1.0.1         tidytuesdayR_1.2.1
 [9] lubridate_1.9.4    forcats_1.0.0      stringr_1.5.1      dplyr_1.1.4       
[13] purrr_1.0.4        readr_2.1.5        tidyr_1.3.1        tibble_3.2.1      
[17] ggplot2_3.5.2      tidyverse_2.0.0   

loaded via a namespace (and not attached):
 [1] utf8_1.2.5         rappdirs_0.3.3     generics_0.1.4     xml2_1.3.8        
 [5] stringi_1.8.7      hms_1.1.3          digest_0.6.37      magrittr_2.0.3    
 [9] evaluate_1.0.3     grid_4.4.1         timechange_0.3.0   RColorBrewer_1.1-3
[13] fastmap_1.2.0      rprojroot_2.0.4    jsonlite_2.0.0     textshaping_1.0.1 
[17] httr2_1.1.2        cli_3.6.5          crayon_1.5.3       rlang_1.1.6       
[21] gitcreds_0.1.2     bit64_4.6.0-1      withr_3.0.2        yaml_2.3.10       
[25] parallel_4.4.1     tools_4.4.1        tzdb_0.5.0         curl_6.2.3        
[29] vctrs_0.6.5        R6_2.6.1           lifecycle_1.0.4    bit_4.6.0         
[33] htmlwidgets_1.6.4  vroom_1.6.5        ragg_1.4.0         pkgconfig_2.0.3   
[37] pillar_1.10.2      gtable_0.3.6       glue_1.8.0         gh_1.5.0          
[41] Rcpp_1.0.14        systemfonts_1.2.3  xfun_0.52          tidyselect_1.2.1  
[45] knitr_1.50         farver_2.1.2       htmltools_0.5.8.1  svglite_2.2.1     
[49] labeling_0.4.3     rmarkdown_2.29     compiler_4.4.1     gridtext_0.1.5    

8. Github

Back to top