library(sf)
library(tidycensus)
library(tidyverse)
library(tigris)5 Census Data Wrangling
5.1 Overview
So far, we have used spatial data to characterize access to opioid treatment resources and link those patterns with community-level information. Adding demographic, social, and economic context can help us identify whether disparities in access exist across different populations and better understand factors that may contribute to observed spatial patterns. Many of these community characteristics are available from the U.S. Census Bureau.
The American Community Survey (ACS) is an ongoing survey that provides annually updated estimates of population and housing characteristics. ACS estimates are available as 1-year and 5-year products. We generally recommend using the 5-year estimates for small geographic areas because they provide greater statistical reliability, particularly for less populated areas and smaller population subgroups. In this tutorial, we use the 2020–2024 ACS 5-year estimates, referred to as the 2024 ACS 5-year estimates.
In this tutorial, we demonstrate how to use the tidycensus package to explore and download commonly used ACS variables at multiple geographic levels, both with and without spatial geometries. We will also wrangle these data into formats that can be integrated with other spatial datasets for further analysis. This tutorial focuses on ACS data available through the Census Bureau API. Additional tutorials and documentation for tidycensus are available from the package authors.
Our objectives are thus to:
- Download ACS data through the Census API
- Download Census geographic boundaries
- Explore ACS data at multiple geographic levels
- Wrangle, clean, and merge Census data for further spatial analysis
5.2 Environment Setup
To replicate the code and functions illustrated in this tutorial, you’ll need to have R and RStudio downloaded and installed on your system. This tutorial assumes some familiarity with the R programming language.
5.2.1 Input/Output
Our only external input will be a GeoJSON file of the Chicago city boundary. All ACS data used in this tutorial will be downloaded directly from the U.S. Census Bureau using the Census API.
- Chicago City Boundary:
boundaries_chicago.geojson
We will generate Census datasets at multiple geographic levels throughout this tutorial. Our final outputs will include:
- A CSV file and spatial file with selected ACS characteristics at the county level for the state of Illinois.
- A CSV file and spatial file with selected ACS characteristics for zipcodes within the city of Chicago.
These outputs can be used for further spatial analysis or linked with other datasets using their geographic identifiers.
5.2.2 Load Libraries
We will use the following packages in this tutorial:
sf: to manipulate spatial datatidycensus: to download Census data using the ACS APItidyverse: to manipulate and clean datatigris: to download Census TIGER/Line geographic boundaries
First, load the required libraries.
5.3 Enable Census API Key
To use the Census API, we first need to sign up for an API key. The API key allows the Census Bureau API to identify and authorize requests from your R session. A Census API key can be requested here.
Once you receive your key, install it by running the code below.
#census_api_key("YourAPIKeyHere", install = TRUE) # installs the key for future sessions.Setting install = TRUE saves the key to your .Renviron file so that it is available for future R sessions.
If you are working on a shared computer or do not want to save your API key, you can use the same function with install = FALSE.
To check whether a Census API key is already installed, run:
#Sys.getenv("CENSUS_API_KEY")The output should be a long string of characters and numbers. If not, save and restart R, as that may be required with installing for the first time. Continue to test the API key to ensure it’s working correctly before continuing.
5.3.1 Test API Key
Using API services can at require patience. Before diving into the rest of the tutorial, let’s test the API first. Run the line of code below to get a view of population data from 2020, at the state level. If you get an error, troubleshoot to resolve!
pop2020 <- get_acs(geography = "state",
variables = "B01001_001",
year = 2020)
head(pop2020)# A tibble: 6 × 5
GEOID NAME variable estimate moe
<chr> <chr> <chr> <dbl> <dbl>
1 01 Alabama B01001_001 4893186 NA
2 02 Alaska B01001_001 736990 NA
3 04 Arizona B01001_001 7174064 NA
4 05 Arkansas B01001_001 3011873 NA
5 06 California B01001_001 39346023 NA
6 08 Colorado B01001_001 5684926 NA
Troubleshooting tips include:
- You may need to overwrite an older Census API key.
- Your API key may not have been updated correctly. Use a different email address to generate a new API key, or wait 48 hours and use the same email address.
- Search your error in Google, check StackOverflow tips, & restart as needed.
- Wait a few hours or a day, come back, and try again.
5.4 Load Data Dynamically
We can now start using the tidycensus package to download demographic and socioeconomic data from the U.S. Census Bureau. In this tutorial, we will cover methods to download ACS data at the state, county, census tract, and ZIP Code Tabulation Area (ZCTA) levels. We will also demonstrate how to download the data with and without spatial geometry.
To download a particular variable or table using tidycensus, we need the relevant variable ID. We can identify these IDs by reviewing the variables available through the load_variables() function. For details on exploring available ACS variables and identifying their variable IDs, see the Explore Variables section in the Appendix.
We can download ACS variables using the get_acs() function. Because ACS data are based on survey samples, estimates are provided along with a margin of error (moe). The tidycensus package returns both values for each requested variable in tidy format.
For the examples covered in this tutorial, the four main inputs for the get_acs() function are:
geography: the geographic level at which to retrieve the data (state, county, tract, or zcta)variables: a character string or vector of variable IDs to retrieveyear: the ACS data year to retrievegeometry: whether to include spatial geometry in the returned data (TRUE / FALSE)
5.4.1 State Level
To begin, we can download selected demographic characteristics for all states.
stateDf <- get_acs(geography = "state",
variables = c(totalPop = "B01001_001",
hispanic = "B03003_003",
white = "B02001_002",
black = "B02001_003",
asian = "B02001_005"),
year = 2024, geometry = FALSE)
head(stateDf)# A tibble: 6 × 5
GEOID NAME variable estimate moe
<chr> <chr> <chr> <dbl> <dbl>
1 01 Alabama totalPop 5086768 NA
2 01 Alabama white 3284260 4017
3 01 Alabama black 1311062 3846
4 01 Alabama asian 74435 1388
5 01 Alabama hispanic 284135 217
6 02 Alaska totalPop 735706 NA
As we can see, the data are available in tidy format. We can use other tools in the tidyverse to clean and manipulate the data.
stateDf <- stateDf %>%
select(GEOID, NAME, variable, estimate) %>%
pivot_wider(names_from = variable, values_from = estimate) %>%
mutate(hispanicPr = hispanic / totalPop,
whitePr = white / totalPop,
blackPr = black / totalPop,
asianPr = asian / totalPop) %>%
select(GEOID, totalPop, hispanicPr, whitePr, blackPr, asianPr)
head(stateDf)# A tibble: 6 × 6
GEOID totalPop hispanicPr whitePr blackPr asianPr
<chr> <dbl> <dbl> <dbl> <dbl> <dbl>
1 01 5086768 0.0559 0.646 0.258 0.0146
2 02 735706 0.0731 0.596 0.0300 0.0645
3 04 7378838 0.314 0.590 0.0460 0.0358
4 05 3049391 0.0904 0.691 0.147 0.0163
5 06 39287377 0.402 0.397 0.0543 0.155
6 08 5862189 0.225 0.705 0.0405 0.0329
5.4.2 County Level
Similarly, for county-level data:
- use
geography = "county"to download all counties in the U.S. - use
geography = "county",state = sampleStateNameto download all counties within a state - use
geography = "county",state = sampleStateName,county = sampleCountyNameto download a specific county
For example, the code below downloads selected demographic characteristics for all counties in Illinois.
countyDf <- get_acs(geography = "county",
variables = c(totalPop = "B01001_001",
hispanic = "B03003_003",
white = "B02001_002",
black = "B02001_003",
asian = "B02001_005"),
year = 2024, state = "IL", geometry = FALSE) %>%
select(GEOID, NAME, variable, estimate) %>%
pivot_wider(names_from = variable, values_from = estimate) %>%
mutate(hispanicPr = hispanic / totalPop,
whitePr = white / totalPop,
blackPr = black / totalPop,
asianPr = asian / totalPop) %>%
select(GEOID, totalPop, hispanicPr, whitePr, blackPr, asianPr)
head(countyDf)# A tibble: 6 × 6
GEOID totalPop hispanicPr whitePr blackPr asianPr
<chr> <dbl> <dbl> <dbl> <dbl> <dbl>
1 17001 64754 0.0208 0.906 0.0284 0.00672
2 17003 4875 0.00677 0.630 0.331 0.00431
3 17005 16716 0.0201 0.867 0.0612 0.00658
4 17007 53230 0.256 0.718 0.0305 0.00960
5 17009 6322 0.0671 0.745 0.179 0.000475
6 17011 32866 0.102 0.875 0.00587 0.00873
5.4.3 Census tract Level
For census tract-level data, at minimum a state must be provided.
- use
geography = "tract",state = sampleStateNameto download all census tracts within a state - use
geography = "tract",state = sampleStateName,county = sampleCountyNameto download all census tracts within a specific county
The code below downloads selected demographic characteristics for all census tracts in Illinois.
tractDf <- get_acs(geography = "tract",
variables = c(totalPop = "B01001_001",
hispanic = "B03003_003",
white = "B02001_002",
black = "B02001_003",
asian = "B02001_005"),
year = 2024, state = "IL", geometry = FALSE) %>%
select(GEOID, NAME, variable, estimate) %>%
pivot_wider(names_from = variable, values_from = estimate) %>%
mutate(hispanicPr = hispanic / totalPop,
whitePr = white / totalPop,
blackPr = black / totalPop,
asianPr = asian / totalPop) %>%
select(GEOID, totalPop, hispanicPr, whitePr, blackPr, asianPr)
head(tractDf)# A tibble: 6 × 6
GEOID totalPop hispanicPr whitePr blackPr asianPr
<chr> <dbl> <dbl> <dbl> <dbl> <dbl>
1 17001000100 4219 0 0.882 0.0356 0.0166
2 17001000201 1889 0.00159 0.931 0.0291 0
3 17001000202 3033 0.0310 0.778 0.0633 0.0501
4 17001000400 3451 0.0151 0.770 0.143 0
5 17001000500 1808 0.137 0.877 0.0271 0.0144
6 17001000600 3931 0.0231 0.907 0.0237 0.00254
5.4.4 Zipcode Level
For zipcode-level data, use geography = "zcta". ZIP Code Tabulation Areas (ZCTAs) are Census Bureau approximations of U.S. Postal Service ZIP Codes. Because ZCTAs can cross county and state boundaries, tidycensus retrieves ZCTA data for the entire U.S.
zctaDf <- get_acs(geography = "zcta",
variables = c(totalPop = "B01001_001",
hispanic = "B03003_003",
white = "B02001_002",
black = "B02001_003",
asian = "B02001_005"),
year = 2024, geometry = FALSE) %>%
select(GEOID, NAME, variable, estimate) %>%
pivot_wider(names_from = variable, values_from = estimate) %>%
mutate(hispanicPr = hispanic / totalPop,
whitePr = white / totalPop,
blackPr = black / totalPop,
asianPr = asian / totalPop) %>%
select(GEOID, totalPop, hispanicPr, whitePr, blackPr, asianPr)Inspect the data.
head(zctaDf)# A tibble: 6 × 6
GEOID totalPop hispanicPr whitePr blackPr asianPr
<chr> <dbl> <dbl> <dbl> <dbl> <dbl>
1 00601 16669 0.993 0.798 0.0145 0.00108
2 00602 37233 0.972 0.272 0.0180 0.00118
3 00603 48448 0.984 0.729 0.0333 0.000186
4 00606 5163 0.997 0.666 0.00136 0.00232
5 00610 25357 0.964 0.187 0.0189 0
6 00611 1383 0.974 0.612 0.0253 0
dim(zctaDf)[1] 33772 6
Because ZCTA data are retrieved for the entire nation, we can filter the results to a particular region after downloading them. For example, Chicago ZIP Codes commonly begin with 606, so we can use str_detect() as a simple non-spatial filter. If geometry information is available, we can instead overlay the ZCTA geometries with a city boundary to identify the required region. We will illustrate this approach in the Get Geometry section.
In addition to demographic characteristics, ACS data can provide community context that may help us better understand geographic patterns in healthcare access. Here, we use poverty and household vehicle availability as two examples of access-relevant community characteristics.
zipChicagoDf <- get_acs(geography = "zcta",
variables = c(povertyTotal = "B17001_001",
povertyBelow = "B17001_002",
households = "B08201_001",
noVehicle = "B08201_002"),
year = 2024, geometry = FALSE) %>%
select(GEOID, NAME, variable, estimate) %>%
filter(str_detect(GEOID, "^606")) %>%
pivot_wider(names_from = variable, values_from = estimate) %>%
mutate(povertyPr = povertyBelow / povertyTotal,
noVehiclePr = noVehicle / households) %>%
select(GEOID, povertyPr, noVehiclePr)Inspect the Chicago ZCTA data.
head(zipChicagoDf)# A tibble: 6 × 3
GEOID povertyPr noVehiclePr
<chr> <dbl> <dbl>
1 60601 0.0564 0.454
2 60602 0.113 0.552
3 60603 0.141 0.367
4 60604 0.125 0.206
5 60605 0.0979 0.391
6 60606 0.0453 0.549
5.4.5 Save Data
We can now save the county and Chicago zipcode datasets as CSV files using the code below.
write.csv(countyDf, "data/ilcounty_24_demographic.csv")
write.csv(zipChicagoDf, "data/chizips_24_context.csv")5.5 Get Geometry
Geometry and geographic boundaries are key components of American Community Survey data because they provide the spatial framework used to map and analyze Census estimates. Geographic boundaries may be updated over time, and Census data for a given year generally correspond to geographic definitions available for that data vintage. In this tutorial, we use 2024 Census geographic boundaries to correspond with the 2024 ACS 5-year estimates.
The datasets downloaded so far do not include spatial geometry. To conduct spatial analysis, we need to join these data frames to spatially enabled sf objects using a common geographic identifier such as GEOID.
We can download Census geographic boundaries using two methods: - using tigris - using tidycensus
5.5.1 Using tigris
To download TIGER/Line geographic boundaries provided by the U.S. Census Bureau, we can use the tigris package. Setting cb = TRUE downloads generalized cartographic boundary files, which have less spatial detail and smaller file sizes than full-resolution TIGER/Line files.
For states, counties, and census tracts, we use 2024 boundary files. The 2024 cartographic boundary file for ZCTAs is not currently available, so we use the 2020 ZCTA boundaries.
yearToFetch <- 2024
stateShp <- states(year = yearToFetch, cb = TRUE)
countyShp <- counties(year = yearToFetch, state = "IL", cb = TRUE)
tractShp <- tracts(state = "IL", year = yearToFetch, cb = TRUE)
zctaShp <- zctas(year = 2020, cb = TRUE)After downloading the geometry files, we can merge these shapes with the demographic data downloaded in the previous section.
For states:
# check object types & identifier variable type
# str(stateShp)
# str(stateDf)
stateShp <- merge(stateShp, stateDf, by.x = "STATEFP", by.y = "GEOID", all.x = TRUE)
head(stateShp)Simple feature collection with 6 features and 14 fields
Geometry type: MULTIPOLYGON
Dimension: XY
Bounding box: xmin: -179.1467 ymin: 30.22333 xmax: 179.7785 ymax: 71.38782
Geodetic CRS: NAD83
STATEFP STATENS GEOIDFQ GEOID STUSPS NAME LSAD ALAND
1 01 01779775 0400000US01 01 AL Alabama 00 1.311856e+11
2 02 01785533 0400000US02 02 AK Alaska 00 1.479509e+12
3 04 01779777 0400000US04 04 AZ Arizona 00 2.943661e+11
4 05 00068085 0400000US05 05 AR Arkansas 00 1.346585e+11
5 06 01779778 0400000US06 06 CA California 00 4.036734e+11
6 08 01779779 0400000US08 08 CO Colorado 00 2.684190e+11
AWATER totalPop hispanicPr whitePr blackPr asianPr
1 4581813708 5086768 0.05585767 0.6456477 0.25773969 0.01463306
2 244710526650 735706 0.07313111 0.5955885 0.02999976 0.06445917
3 853991999 7378838 0.31357322 0.5900494 0.04601849 0.03578057
4 3122715710 3049391 0.09036919 0.6914810 0.14713528 0.01629375
5 20291632828 39287377 0.40162933 0.3971951 0.05433084 0.15528041
6 1185541418 5862189 0.22533170 0.7051564 0.04049835 0.03287117
geometry
1 MULTIPOLYGON (((-88.05338 3...
2 MULTIPOLYGON (((-132.4825 5...
3 MULTIPOLYGON (((-114.8163 3...
4 MULTIPOLYGON (((-94.61792 3...
5 MULTIPOLYGON (((-118.6044 3...
6 MULTIPOLYGON (((-109.0603 3...
Similarly for counties, zctas & census tracts we can use the code below.
countyShp <- merge(countyShp, countyDf, by.x = "GEOID", by.y = "GEOID", all.x = TRUE)%>%
select(GEOID, totalPop, hispanicPr, whitePr, blackPr, asianPr)
tractShp <- merge(tractShp, tractDf, by.x = "GEOID", by.y = "GEOID", all.x = TRUE)%>%
select(GEOID, totalPop, hispanicPr, whitePr, blackPr, asianPr)The 2024 cartographic boundary file for ZCTAs is not currently available, so we use the 2020 ZCTA boundaries, which correspond to the current ZCTA geography used by the 2024 ACS.
zctaShp <- merge(zctaShp, zctaDf, by.x = "GEOID20", by.y = "GEOID", all.x = TRUE) %>%
select(GEOID20, totalPop, hispanicPr, whitePr, blackPr, asianPr)5.5.2 Using tidycensus
The previous method adds an additional step of using the tigris package to download geographic boundary files. The tidycensus package can also invoke tigris within the get_acs() function, allowing us to download ACS data with geometry by setting geometry = TRUE.
Adding geometry to each variable can increase the size of intermediate datasets and slow down processing, particularly when downloading many variables. For larger requests, we recommend downloading the ACS variables without geometry and then downloading a single variable with geometry = TRUE. The resulting spatial object can then be merged with the larger ACS dataset. We illustrate this approach below.
First, download the census tract demographic data without geometry.
tractDf <- get_acs(geography = "tract",
variables = c(totalPop = "B01001_001",
hispanic = "B03003_003",
white = "B02001_002",
black = "B02001_003",
asian = "B02001_005"),
year = 2024, state = "IL", geometry = FALSE) %>%
select(GEOID, NAME, variable, estimate) %>%
pivot_wider(names_from = variable, values_from = estimate) %>%
mutate(hispanicPr = hispanic / totalPop,
whitePr = white / totalPop,
blackPr = black / totalPop,
asianPr = asian / totalPop) %>%
select(GEOID, totalPop, hispanicPr, whitePr, blackPr, asianPr)We can then download a single variable with geometry.
tractShp <- get_acs(geography = "tract",
variables = c(totalPop = "B01001_001"),
year = 2024, state = "IL", geometry = TRUE) %>%
select(GEOID, NAME, variable, estimate) %>%
pivot_wider(names_from = variable, values_from = estimate)Merge the geometry with the demographic data using GEOID.
tractShp <- merge(tractShp, tractDf, by.x = "GEOID", by.y = "GEOID", all.x = TRUE)
head(tractShp)Simple feature collection with 6 features and 8 fields
Geometry type: MULTIPOLYGON
Dimension: XY
Bounding box: xmin: -91.42005 ymin: 39.92603 xmax: -91.32937 ymax: 39.97247
Geodetic CRS: NAD83
GEOID NAME totalPop.x totalPop.y
1 17001000100 Census Tract 1; Adams County; Illinois 4219 4219
2 17001000201 Census Tract 2.01; Adams County; Illinois 1889 1889
3 17001000202 Census Tract 2.02; Adams County; Illinois 3033 3033
4 17001000400 Census Tract 4; Adams County; Illinois 3451 3451
5 17001000500 Census Tract 5; Adams County; Illinois 1808 1808
6 17001000600 Census Tract 6; Adams County; Illinois 3931 3931
hispanicPr whitePr blackPr asianPr geometry
1 0.000000000 0.8824366 0.03555345 0.016591609 MULTIPOLYGON (((-91.37766 3...
2 0.001588142 0.9311805 0.02911593 0.000000000 MULTIPOLYGON (((-91.39646 3...
3 0.030992417 0.7777778 0.06330366 0.050115397 MULTIPOLYGON (((-91.3937 39...
4 0.015068096 0.7696320 0.14256737 0.000000000 MULTIPOLYGON (((-91.42005 3...
5 0.137168142 0.8772124 0.02710177 0.014380531 MULTIPOLYGON (((-91.4034 39...
6 0.023149326 0.9071483 0.02365810 0.002543882 MULTIPOLYGON (((-91.39655 3...
To get ZCTA-level data for a specific region, we first download the required dataset for the entire country and then filter the relevant ZCTAs by overlaying the downloaded dataset with the geometry of the region of interest.
Here, we download the poverty and household vehicle availability measures used in the previous section without geometry.
zctaContextDf <- get_acs(geography = "zcta",
variables = c(povertyTotal = "B17001_001",
povertyBelow = "B17001_002",
households = "B08201_001",
noVehicle = "B08201_002"),
year = 2024,geometry = FALSE) %>%
select(GEOID, NAME, variable, estimate) %>%
pivot_wider(names_from = variable, values_from = estimate) %>%
mutate(povertyPr = povertyBelow / povertyTotal,
noVehiclePr = noVehicle / households) %>%
select(GEOID, povertyPr, noVehiclePr)Download the 2020 ZCTA boundaries and merge them with the 2024 ACS data.
zctaShp <- zctas(year = 2020, cb = TRUE)
zctaShp <- merge(zctaShp, zctaContextDf, by.x = "GEOID20", by.y = "GEOID", all.x = TRUE) %>%
select(GEOID20, povertyPr, noVehiclePr)Inspect the data.
head(zctaShp)Simple feature collection with 6 features and 3 fields
Geometry type: MULTIPOLYGON
Dimension: XY
Bounding box: xmin: -67.23915 ymin: 18.11201 xmax: -66.66106 ymax: 18.51335
Geodetic CRS: NAD83
GEOID20 povertyPr noVehiclePr geometry
1 00601 0.5990141 0.12014563 MULTIPOLYGON (((-66.83637 1...
2 00602 0.4695903 0.12575266 MULTIPOLYGON (((-67.23915 1...
3 00603 0.4442137 0.15985296 MULTIPOLYGON (((-67.16914 1...
4 00606 0.5448383 0.08978495 MULTIPOLYGON (((-67.05134 1...
5 00610 0.4226457 0.08923365 MULTIPOLYGON (((-67.22534 1...
6 00611 0.3984093 0.12812960 MULTIPOLYGON (((-66.82743 1...
Read in the Chicago city boundary file, which can be download here.
chiCityBoundary <- st_read("data/boundaries_chicago.geojson")Reading layer `boundaries_chicago' from data source
`/Users/maryniakolak/Code/opioid-environment-toolkit/data/boundaries_chicago.geojson'
using driver `GeoJSON'
Simple feature collection with 1 feature and 4 fields
Geometry type: MULTIPOLYGON
Dimension: XY
Bounding box: xmin: -87.94011 ymin: 41.64454 xmax: -87.52414 ymax: 42.02304
Geodetic CRS: WGS 84
Set the same coordinate reference system for both spatial datasets.
chiCityBoundary <- st_transform(chiCityBoundary, 4326)
zctaShp <- st_transform(zctaShp, 4326)Only keep ZCTAs that intersect the Chicago city boundary.
zipChicagoShp <- st_intersection(zctaShp, chiCityBoundary)Inspect data.
head(zipChicagoShp)Simple feature collection with 6 features and 7 fields
Geometry type: GEOMETRY
Dimension: XY
Bounding box: xmin: -87.94011 ymin: 41.68284 xmax: -87.52454 ymax: 42.01914
Geodetic CRS: WGS 84
GEOID20 povertyPr noVehiclePr name objectid shape_area
15446 46394 0.15652542 0.07039934 CHICAGO 1 6450276623.31
20826 60007 0.07980461 0.05677877 CHICAGO 1 6450276623.31
20834 60018 0.06665457 0.05746167 CHICAGO 1 6450276623.31
20864 60068 0.02912086 0.04585859 CHICAGO 1 6450276623.31
20872 60076 0.06489307 0.05527768 CHICAGO 1 6450276623.31
20873 60077 0.14556758 0.09126948 CHICAGO 1 6450276623.31
shape_len geometry
15446 845282.931362 MULTIPOLYGON (((-87.52454 4...
20826 845282.931362 MULTIPOLYGON (((-87.93993 4...
20834 845282.931362 MULTIPOLYGON (((-87.93523 4...
20864 845282.931362 MULTIPOLYGON (((-87.85584 4...
20872 845282.931362 POLYGON ((-87.70919 42.0118...
20873 845282.931362 POLYGON ((-87.76167 42.0082...
5.5.3 Save Data
We previously saved our datasets without geometry information as CSV files. Now we can save the Illinois county demographic data and Chicago ZCTA community context data with geometries as shapefiles using write_sf().
write_sf(countyShp, "data/ilcounty_24_demographic.shp")
write_sf(zipChicagoShp, "data/chizips_24_context.shp")5.6 Appendix
5.6.1 Explore Variables
Using tidycensus, we can download ACS variables from several types of tables. Some commonly used products include:
Data Profiles: Broad collections of social, economic, housing, and demographic characteristics, such as Social Characteristics (DP02), Economic Characteristics (DP03), Housing Characteristics (DP04), and Demographic and Housing Estimates (DP05).Subject Tables: Topic-specific tables that provide estimates and percentages for subjects such as age and sex (S0101), employment, education, income, and disability.Detailed Tables: Detailed ACS tables identified primarily by B and C table IDs. These tables provide the variables used throughout this tutorial.
We can explore the variables available for our year of interest using load_variables(). Variable IDs and table structures may change over time, so it is important to check the variables for the ACS year being used.
sVarNames <- load_variables(2024, "acs5/subject", cache = TRUE)
pVarNames <- load_variables(2024, "acs5/profile", cache = TRUE)
otherVarNames <- load_variables(2024, "acs5", cache = TRUE)
head(pVarNames)# A tibble: 6 × 3
name label concept
<chr> <chr> <chr>
1 DP02PR_0001 Estimate!!HOUSEHOLDS BY TYPE!!Total households Select…
2 DP02PR_0001P Percent!!HOUSEHOLDS BY TYPE!!Total households Select…
3 DP02PR_0002 Estimate!!HOUSEHOLDS BY TYPE!!Total households!!Married-… Select…
4 DP02PR_0002P Percent!!HOUSEHOLDS BY TYPE!!Total households!!Married-c… Select…
5 DP02PR_0003 Estimate!!HOUSEHOLDS BY TYPE!!Total households!!Married-… Select…
6 DP02PR_0003P Percent!!HOUSEHOLDS BY TYPE!!Total households!!Married-c… Select…
A tibble containing table and variable information includes three main columns: name, label, and concept.
name identifies the ACS variable. concept generally identifies the table or topic associated with the variable, while label provides a description of the variable.
We can explore these tibbles to identify the appropriate variable ID for use with get_acs(). For example, we can search the Subject Tables for variables related to children under 5 years of age.
sVarNames %>%
filter(str_detect(concept, "AGE AND SEX")) %>%
filter(str_detect(label, "Under 5 years")) %>%
mutate(label = sub("^Estimate!!", "", label)) %>%
select(variableId = name, label)# A tibble: 0 × 2
# ℹ 2 variables: variableId <chr>, label <chr>
We can also search directly within a specific Subject Table, such as S0101.
sVarNames %>%
filter(str_sub(name, 1, 5) == "S0101") %>%
filter(str_detect(label, "Under 5 years")) %>%
mutate(label = sub("^Estimate!!", "", label)) %>%
select(variableId = name, label)# A tibble: 6 × 2
variableId label
<chr> <chr>
1 S0101_C01_002 Total!!Total population!!AGE!!Under 5 years
2 S0101_C02_002 Percent!!Total population!!AGE!!Under 5 years
3 S0101_C03_002 Male!!Total population!!AGE!!Under 5 years
4 S0101_C04_002 Percent Male!!Total population!!AGE!!Under 5 years
5 S0101_C05_002 Female!!Total population!!AGE!!Under 5 years
6 S0101_C06_002 Percent Female!!Total population!!AGE!!Under 5 years
Similarly, we can explore Data Profile variables. For example, the following code searches for per capita income variables.
pVarNames %>%
filter(str_detect(label, "Per capita")) %>%
mutate(label = sub("^Estimate!!", "", label)) %>%
select(variable = name, label)# A tibble: 2 × 2
variable label
<chr> <chr>
1 DP03_0088 INCOME AND BENEFITS (IN 2024 INFLATION-ADJUSTED DOLLARS)!!Per capi…
2 DP03_0088P Percent!!INCOME AND BENEFITS (IN 2024 INFLATION-ADJUSTED DOLLARS)!…
We can also search for age-related variables in the Demographic and Housing Estimates profile.
pVarNames %>%
filter(str_detect(label, "Under 5 years")) %>%
mutate(label = sub("^Estimate!!", "", label)) %>%
select(variable = name, label)# A tibble: 2 × 2
variable label
<chr> <chr>
1 DP05_0005 SEX AND AGE!!Total population!!Under 5 years
2 DP05_0005P Percent!!SEX AND AGE!!Total population!!Under 5 years
Because variable IDs and table structures can change across ACS releases, users should verify the appropriate variable IDs for each year. For analyses across multiple years, Detailed Tables can often provide a more consistent basis for identifying comparable variables. The examples in this tutorial primarily use Detailed Table variables.
For example, we can search the Detailed Tables for per capita income.
otherVarNames %>%
filter(str_detect(label, "Per capita income")) %>%
mutate(label = sub("^Estimate!!", "", label)) %>%
select(variable = name, label)# A tibble: 10 × 2
variable label
<chr> <chr>
1 B19301A_001 Per capita income in the past 12 months (in 2024 inflation-adjus…
2 B19301B_001 Per capita income in the past 12 months (in 2024 inflation-adjus…
3 B19301C_001 Per capita income in the past 12 months (in 2024 inflation-adjus…
4 B19301D_001 Per capita income in the past 12 months (in 2024 inflation-adjus…
5 B19301E_001 Per capita income in the past 12 months (in 2024 inflation-adjus…
6 B19301F_001 Per capita income in the past 12 months (in 2024 inflation-adjus…
7 B19301G_001 Per capita income in the past 12 months (in 2024 inflation-adjus…
8 B19301H_001 Per capita income in the past 12 months (in 2024 inflation-adjus…
9 B19301I_001 Per capita income in the past 12 months (in 2024 inflation-adjus…
10 B19301_001 Per capita income in the past 12 months (in 2024 inflation-adjus…