Example simple feature to data frame…

library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.0     ✔ readr     2.1.4
## ✔ forcats   1.0.0     ✔ stringr   1.5.0
## ✔ ggplot2   3.4.1     ✔ tibble    3.1.8
## ✔ lubridate 1.9.2     ✔ tidyr     1.3.0
## ✔ purrr     1.0.1     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(sf)
## Linking to GEOS 3.10.2, GDAL 3.4.2, PROJ 8.2.1; sf_use_s2() is TRUE
#> Linking to GEOS 3.8.1, GDAL 3.2.1, PROJ 7.2.1

fips_geometries <- readRDS(url("https://wilkelab.org/SDS375/datasets/US_counties.rds")) %>%
  rename(FIPS = GEOID)

US_census <- read_csv("https://wilkelab.org/SDS375/datasets/US_census.csv",
                      col_types = cols(FIPS = "c")
          )

# works
fips_geometries %>%
  left_join(US_census, by = "FIPS") %>%
  ggplot() +
  geom_sf(aes(fill = home_ownership)) + 
  scale_fill_viridis_c()

# from Claus Wilke on ggplot2
#' Title
#'
#' @param geometries_frame 
#'
#' @return
#' @export
#'
#' @examples
#' geometries_frame_to_id_group()
geometries_frame_to_id_group <- function(geometries_frame){

    nsubgroup <- geometries_frame$geometry %>% purrr::map_dbl(.f = length)

    tibble(nsubgroup) %>% 
      mutate(major_group = 1:n()) %>% # row number 
      uncount(weights = nsubgroup) %>% 
      group_by(major_group) %>% 
      mutate(subgroup = 1:n()) %>% 
      mutate(major_and_minor = paste0(major_group,'.', subgroup)) %>% 
      ungroup() %>% 
      mutate(polygon = 1:n()) %>% 
      ungroup()

}



#' Title
#'
#' @param geometries_frame 
#'
#' @return
#' @export
#'
#' @examples
#' geometries_frame_to_df(nc) 
geometries_frame_to_df <- function(geometries_frame){

      polygons_flat <- data.frame()
    
      polygons <- geometries_frame$geometry %>% purrr::flatten() %>% purrr::flatten()
      
    for (i in 1:length(polygons)){
      
      temp <- polygons[[i]] %>% 
        data.frame() %>% 
        mutate(polygon = i)
      
      polygons_flat = bind_rows(polygons_flat, temp)
  

  }

  names(polygons_flat)[1] <- "x"
  names(polygons_flat)[2] <- "y"

  polygons_flat
  
}
  


#' Title
#'
#' @param geometries_frame 
#'
#' @return
#' @export
#'
#' @examples
#' geometries_frame_to_id_group(nc) %>% 
#' count(major_group)
geometries_frame_to_ggplot_reference <- function(geometries_frame = aseg_geometries_frame){
  
  keep_frame <- geometries_frame
  
  keep_frame$geometry = NULL
  
  keep_frame %>% 
    data.frame() %>% 
    janitor::clean_names() %>% 
    mutate(major_group = row_number()) %>% 
    left_join(geometries_frame_to_id_group(geometries_frame)) %>% 
    left_join(geometries_frame_to_df(geometries_frame))
  
}
  


  
fips_ggplot2_reference <- fips_geometries %>% remove_missing() %>% 
  geometries_frame_to_ggplot_reference()  
## Joining with `by = join_by(major_group)`
## Warning in left_join(., geometries_frame_to_id_group(geometries_frame)): Each row in `x` is expected to match at most 1 row in `y`.
## ℹ Row 114 of `x` matches multiple rows.
## ℹ If multiple matches are expected, set `multiple = "all"` to silence this
##   warning.
## Joining with `by = join_by(polygon)`
## Warning in left_join(., geometries_frame_to_df(geometries_frame)): Each row in `x` is expected to match at most 1 row in `y`.
## ℹ Row 1 of `x` matches multiple rows.
## ℹ If multiple matches are expected, set `multiple = "all"` to silence this
##   warning.
compute_county <- function(data, scales){
 
  data %>% 
    inner_join(fips_ggplot2_reference, multiple = "all") 

}

Step 2: pass to ggproto

StatCounty <- ggplot2::ggproto(`_class` = "StatCounty",
                                  `_inherit` = ggplot2::Stat,
                                  # required_aes = c("fips"),
                                  # setup_data = my_setup_data,
                                  compute_panel = compute_county,
                                  default_aes = aes(group = after_stat(polygon))
                                  )

Step 3: write geom_* function

geom_polygon_county <- function(
  mapping = NULL,
  data = NULL,
  position = "identity",
  na.rm = FALSE,
  show.legend = NA,
  inherit.aes = TRUE, ...) {
  ggplot2::layer(
    stat = StatCounty,  # proto object from step 2
    geom = ggplot2::GeomPolygon,  # inherit other behavior
    data = data,
    mapping = mapping,
    position = position,
    show.legend = show.legend,
    inherit.aes = inherit.aes,
    params = list(na.rm = na.rm, ...)
  )
}

step 4: test geom_* function, celebrate!

us_census <- read_csv("https://wilkelab.org/SDS375/datasets/US_census.csv",
                      col_types = cols(FIPS = "c")
          )  

us_census %>% 
  ggplot() + 
  aes(fips = FIPS) +
  geom_polygon_county(linewidth = .02, color = "darkgrey") +
  aes(fill = mean_work_travel) + 
  scale_fill_viridis_c(option = "magma")
## Joining with `by = join_by(fips)`

fips_geometries %>%
  left_join(US_census, by = "FIPS") %>%
  ggplot() +
  geom_sf(aes(fill = mean_work_travel), linewidth = .1) + 
  scale_fill_viridis_c(option = "magma")