Mapping Census data

You can follow along with the 04_tidycensus_viz file in the nicar-2025-tidycensus project folder that you downloaded in the intro link.

The repo containing the data and scripts for this section is on Github. To install those files, run the lines of code below.

#install.packages("usethis")
usethis::use_course("https://github.com/r-journalism/nicar-2025-tidycensus/archive/master.zip")

# Run this in the console of RStudio
file.edit("04_tidycensusviz.R")

To follow along with this walkthrough, simply run the lines of code in the gray boxes in the R console. Be sure to run them in order. If you run into an error, it may be because you skipped running some preceding lines of code.

Load libraries

library(tidyverse)
library(tidycensus)
library(sf)
library(mapview)

“Spatial” ACS data

  • One of the best features of tidycensus is the argument geometry = TRUE, which gets you the correct Census geometries with no hassle

  • get_acs() with geometry = TRUE returns a spatial Census dataset containing simple feature geometries;

Downloading “Spatial” ACS data

  • geometry = TRUE does the hard work for you of acquiring and pre-joining spatial Census data
median_value_map <- get_acs(
  geography = "tract",
  state= "MN",
  county="Hennepin",
  variables = "B25077_001", # median values of home
  year = 2022,
  geometry = TRUE
)
Getting data from the 2018-2022 5-year ACS
Downloading feature geometry from the Census website.  To cache shapefiles for use in future sessions, set `options(tigris_use_cache = TRUE)`.

  • We get back a _simple features data frame
median_value_map
Simple feature collection with 329 features and 3 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -93.76838 ymin: 44.78519 xmax: -93.17722 ymax: 45.24662
Geodetic CRS:  NAD83
First 10 features:
         GEOID estimate   moe                       geometry
1  27053021505   306200 19165 MULTIPOLYGON (((-93.40064 4...
2  27053026713   350400 28695 MULTIPOLYGON (((-93.43159 4...
3  27053000102   219300 13782 MULTIPOLYGON (((-93.29919 4...
4  27053108700   260500 15666 MULTIPOLYGON (((-93.24235 4...
5  27053102100   193400 22082 MULTIPOLYGON (((-93.3082 45...
6  27053026814   260200 13156 MULTIPOLYGON (((-93.3211 45...
7  27053023802   529500 79402 MULTIPOLYGON (((-93.32898 4...
8  27053100800   192800 21081 MULTIPOLYGON (((-93.30833 4...
9  27053125800   195900 38910 MULTIPOLYGON (((-93.26259 4...
10 27053025403   273600  8221 MULTIPOLYGON (((-93.28698 4...

Exploring Census data interactively

library(mapview)

mapview(median_value_map)

Creating a shaded map with zcol

mapview(median_value_map, zcol = "estimate")

Try all the code again in a different county

median_value_map <- get_acs(
  geography = "tract",
  state= "MN", # Changeme
  county="Hennepin", # Change me
  variables = "B25077_001", # median values of home
  year = 2022,
  geometry = TRUE
)

mapview(median_value_map, zcol = "estimate")

Migration data

Let’s map some county-to-county migration data from the Census! [Link]

county_migration <- get_flows(
  geography = "county",
  county = "Hennepin",
  state = "MN"
)
county_migration
# A tibble: 2,496 × 7
   GEOID1 GEOID2 FULL1_NAME                 FULL2_NAME   variable estimate   moe
   <chr>  <chr>  <chr>                      <chr>        <chr>       <dbl> <dbl>
 1 27053  <NA>   Hennepin County, Minnesota Africa       MOVEDIN      2015   519
 2 27053  <NA>   Hennepin County, Minnesota Africa       MOVEDOUT       NA    NA
 3 27053  <NA>   Hennepin County, Minnesota Africa       MOVEDNET       NA    NA
 4 27053  <NA>   Hennepin County, Minnesota Asia         MOVEDIN      3877   581
 5 27053  <NA>   Hennepin County, Minnesota Asia         MOVEDOUT       NA    NA
 6 27053  <NA>   Hennepin County, Minnesota Asia         MOVEDNET       NA    NA
 7 27053  <NA>   Hennepin County, Minnesota Central Ame… MOVEDIN       646   198
 8 27053  <NA>   Hennepin County, Minnesota Central Ame… MOVEDOUT       NA    NA
 9 27053  <NA>   Hennepin County, Minnesota Central Ame… MOVEDNET       NA    NA
10 27053  <NA>   Hennepin County, Minnesota Caribbean    MOVEDIN        82    64
# ℹ 2,486 more rows

Downloading map data

county_map <- get_acs(
  geography = "county",
  variable = c("Population"="B03002_001"),
  geometry = TRUE
)
Getting data from the 2019-2023 5-year ACS
Downloading feature geometry from the Census website.  To cache shapefiles for use in future sessions, set `options(tigris_use_cache = TRUE)`.
county_map
Simple feature collection with 3222 features and 5 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -179.1467 ymin: 17.88328 xmax: 179.7785 ymax: 71.38782
Geodetic CRS:  NAD83
First 10 features:
   GEOID                           NAME   variable estimate moe
1  01003        Baldwin County, Alabama Population   239945  NA
2  01069        Houston County, Alabama Population   107628  NA
3  01005        Barbour County, Alabama Population    24757  NA
4  01119         Sumter County, Alabama Population    12020  NA
5  05091        Miller County, Arkansas Population    42588  NA
6  05133        Sevier County, Arkansas Population    15797  NA
7  05093   Mississippi County, Arkansas Population    39749  NA
8  06037 Los Angeles County, California Population  9848406  NA
9  06087  Santa Cruz County, California Population   266021  NA
10 06097      Sonoma County, California Population   485642  NA
                         geometry
1  MULTIPOLYGON (((-88.02858 3...
2  MULTIPOLYGON (((-85.71209 3...
3  MULTIPOLYGON (((-85.74803 3...
4  MULTIPOLYGON (((-88.41492 3...
5  MULTIPOLYGON (((-94.04343 3...
6  MULTIPOLYGON (((-94.47732 3...
7  MULTIPOLYGON (((-90.2888 35...
8  MULTIPOLYGON (((-118.6044 3...
9  MULTIPOLYGON (((-122.3177 3...
10 MULTIPOLYGON (((-123.5335 3...

Prep the migration data

county_migration_moved <- county_migration |>
  filter(variable=="MOVEDIN") |>
  filter(!is.na(GEOID2)) |>
  select(GEOID=GEOID2, migration=estimate) 

Join the migration data with the shapefile

county_map_migration <- county_map %>%
  inner_join(county_migration_moved)

mapview(county_map_migration, zcol = "migration")

Can you map migration out?


county_migration_moved <- county_migration |>
  filter(variable=="MOVEDOUT") |>
  filter(!is.na(GEOID2)) |>
  select(GEOID=GEOID2, migration=estimate) 

county_map_migration <- county_map %>%
  inner_join(county_migration_moved)

mapview(county_map_migration, zcol = "migration")

More resources