16. Census Data with tidycensus

Author

Patrick Spauster

Video Tutorial

Intro to TidyCensus

Tidycensus is incredible powerful and gives you access to a ton of census data. Once you get it down you’ll be able to use it to quickly grab a bunch of data.

https://walker-data.com/census-r/an-introduction-to-tidycensus.html

library(tidycensus)
library(tidyverse)
library(janitor)

Request your own API key here: https://api.census.gov/data/key_signup.html

Install the API key:

census_api_key("8524147f6edf7fe4b7c85681397fe5acd6993d62")

You can use this key for practicing this demo, but please request your own for your projects and future use.

When you get your own key install it, so you won’t have to call this function again with census_api_key("key", install = TRUE)

Browse available variables:

Use load variables to browse and find the variables of interest

v20 <- load_variables(2023, "acs5", cache = TRUE)

AGE BY LANGUAGE SPOKEN AT HOME FOR THE POPULATION 5 YEARS AND OVER

  • B16007_001 Estimate!!Total:

  • B16007_002 Estimate!!Total:!!5 to 17 years:

  • B16007_003 Estimate!!Total:!!5 to 17 years:!!Speak only English

  • B16007_004 Estimate!!Total:!!5 to 17 years:!!Speak Spanish

  • B16007_005 Estimate!!Total:!!5 to 17 years:!!Speak other Indo-European languages

  • B16007_006 Estimate!!Total:!!5 to 17 years:!!Speak Asian and Pacific Island languages

  • B16007_007 Estimate!!Total:!!5 to 17 years:!!Speak other languages

Pull data

I use the basic usage of tidycensus webpage to find the right argument names to use.

langs_by_puma <- get_acs(
    geography = "public use microdata area", 
    variables = c( totalkids = "B16007_002",
                   englishkids = "B16007_003",
                   spanishkids = "B16007_004",
                   indoeurkids = "B16007_005",
                   apikids = "B16007_006",
                   otherkids = "B16007_007" ), 
    state = "New York", 
    year = 2023, survey = "acs5" ) %>% 
  filter(str_detect(NAME, "NYC")) %>% 
  mutate(moeshare = moe / estimate)
langs_by_boro <- get_acs(
  geography = "county",
  variables = c(
    totalkids = "B16007_002",
    englishkids = "B16007_003",
    spanishkids = "B16007_004",
    indoeurkids = "B16007_005",
    apikids = "B16007_006",
    otherkids = "B16007_007"
  ),
  state =  "New York",
  year = 2023,
  survey = "acs5"
) %>%
  filter(
    NAME == "Kings County, New York" |
      NAME == "Queens County, New York" |
      NAME == "New York County, New York" |
      NAME == "Bronx County, New York" |
      NAME == "Richmond County, New York"
  ) %>%
  clean_names()

Write langs_by_boro as a CSV file into the same folder

write_csv(langs_by_boro, 'langs_by_boro.csv')
#this output might look familiar from our ggplot lesson!

Tidycensus also has a “wide” option to make calculating percentages, for example, easier.

get_acs(
  geography = "county",
  variables = c(
    totalkids = "B16007_002",
    englishkids = "B16007_003",
    spanishkids = "B16007_004",
    indoeurkids = "B16007_005",
    apikids = "B16007_006",
    otherkids = "B16007_007"
  ),
  state =  "New York",
  year = 2023,
  survey = "acs5",
  output = "wide" #look here!
) %>%
  filter(
    NAME == "Kings County, New York" |
      NAME == "Queens County, New York" |
      NAME == "New York County, New York" |
      NAME == "Bronx County, New York" |
      NAME == "Richmond County, New York"
  ) %>%
  clean_names() %>% 
  mutate(
    pct_englishkids = englishkids_e / totalkids_e, #_u is for the estimate and _m is the margin or error
    pct_spanishkids = spanishkids_e / totalkids_e,
    pct_indoeurkids = indoeurkids_e / totalkids_e,
    pct_aapikids = apikids_e / totalkids_e,
    pct_otherkids = otherkids_e / totalkids_e
  ) %>% 
  select(name, starts_with("pct"))

Crosswalks and matching census geographies

Let’s say I wanted to compare a number of statistics by Brooklyn neighborhood. But oh no! The census doesn’t provide data at the neighborhood level. Fear not, you can use something called a crosswalk to match data from different geographies. Census data makes it easy - they have standard GEOID columns that allow you to match data at different geographies.

Census data is powerful because you can join it with data on whatever you are interested in at almost any geographic level.

First I’ll grab some statistics at the tract level in Brooklyn.

pop_data <- get_acs(
  geography = "tract",
  variables = c(total_population = "B01003_001",
                median_household_income = "B19019_001",
                race_eth_denom = "B03002_001",
                white_nonhsp = "B03002_003"
  ),
  year = 2023,
  state = "New York",
  county = "Kings"
)

Then I’ll read in this crosswalk between neighborhoods and tracts from the NYC Open data portal. By joining the two on GEOID I can summarize at the neighborhood level

nta_tract <- read_csv("https://data.cityofnewyork.us/resource/hm78-6dwm.csv",
                      col_types = cols(geoid = col_character())) %>% 
  filter(boroname == "Brooklyn")

pop_data_joined <- nta_tract %>% 
  full_join(pop_data, by = c("geoid" = "GEOID"))
  
pop_data_joined %>%
  filter(variable == "total_population") %>%
  group_by(ntaname) %>% 
  summarize(total_population = sum(estimate))

What if I just wanted to compare one neighborhood to the rest of Brooklyn? Using the pull() function which rips a list from a dataframe variable I can mutate my way to that summary.

gowanus_tracts <- nta_tract %>% 
  mutate(geoid = as.character(geoid)) %>% 
  filter(ntaname == "Carroll Gardens-Cobble Hill-Gowanus-Red Hook") %>% 
  pull(geoid)

pop_data_clean <- pop_data %>% 
  mutate(gowanus = if_else(GEOID %in% gowanus_tracts, "Gowanus", "Rest of BK"))
  
pop_data_clean %>%
  filter(variable == "total_population") %>%
  group_by(gowanus) %>% 
  summarize(total = sum(estimate))