More than a third of Rutherford County renters can’t truly afford their leases, especially those living along the border with Davidson County, the latest Census data suggest.
Financial experts generally recommend spending no more than 30 percent of one’s pre-tax income on housing. But along Rutherford County’s border with Davidson County, around two in five renters shell out 35 percent or more of their pre-tax income to keep a roof over their heads, according to the 2022 American Community Survey conducted by the U.S. Census Bureau.
Rep. Robert Stevens, a Republican who represents the area in the Tennessee House of Representatives, supported a bill this past spring designed to spur low-income housing development through municipal partnerships with private development corporations.
The rest of Rutherford County’s delegation to the state House also supported the bill, with the exception of Republican Rep. Bryan Terry, who represents District 48 in the eastern half of the county, where about a third of renters spend more than 35 percent of their pre-tax income on rent. Terry was present during the bill’s consideration but did not cast a vote.
Estimate by district | ||||
District | Estimate | Estimate_MOE | From | To |
---|---|---|---|---|
State House District 13 (2022), Tennessee | 42.9 | 7.7 | 35.2 | 50.6 |
State House District 37 (2022), Tennessee | 40.3 | 5.5 | 34.8 | 45.8 |
State House District 34 (2022), Tennessee | 35.9 | 4.8 | 31.1 | 40.7 |
State House District 49 (2022), Tennessee | 35.7 | 5.6 | 30.1 | 41.3 |
State House District 48 (2022), Tennessee | 34.4 | 5.9 | 28.5 | 40.3 |
Here’s the full script, start to finish, all in one chunk. See below for a step-by-step explanation of how to use the script, and how it works.
# Installing and loading required packages
if (!require("tidyverse"))
install.packages("tidyverse")
if (!require("tidycensus"))
install.packages("tidycensus")
if (!require("sf"))
install.packages("sf")
if (!require("mapview"))
install.packages("mapview")
if (!require("gtExtras"))
install.packages("gtExtras")
library(tidyverse)
library(tidycensus)
library(sf)
library(mapview)
library(gtExtras)
# Transmitting API key
census_api_key("PasteYourAPIKeyBetweenTheseQuoteMarks")
# Fetching ACS codebooks
DetailedTables <- load_variables(2022, "acs5", cache = TRUE)
SubjectTables <- load_variables(2022, "acs5/subject", cache = TRUE)
ProfileTables <- load_variables(2022, "acs5/profile", cache = TRUE)
Codebook <- DetailedTables %>%
select(name, label, concept)
Codebook <- bind_rows(Codebook,SubjectTables)
Codebook <- bind_rows(Codebook,ProfileTables)
Codebook <- Codebook %>%
distinct(label, .keep_all = TRUE)
rm(DetailedTables,
SubjectTables,
ProfileTables)
# Filtering the codebook
MyVars <- Codebook %>%
filter(grepl("GRAPI", label) &
grepl("Percent!!", label))
# Making a table of the filtered variables
MyVarsTable <- gt(MyVars) %>%
tab_header("Variables") %>%
cols_align(align = "left") %>%
gt_theme_538
# Displaying the table
MyVarsTable
# Defining the variable to retrieve
VariableList =
c(Estimate_ = "DP04_0142P")
# Fetching data
AllData <- get_acs(
geography = "state legislative district (lower chamber)",
state = "TN",
variables = VariableList,
year = 2022,
survey = "acs5",
output = "wide",
geometry = TRUE
)
# Mutating, selecting and sorting the data
AllData <- AllData %>%
mutate(
District = NAME,
Estimate = Estimate_E,
Estimate_MOE = Estimate_M,
From = round(Estimate - Estimate_MOE, 2),
To = round(Estimate + Estimate_MOE, 2)
) %>%
select(District, Estimate, Estimate_MOE, From, To, geometry) %>%
arrange(desc(Estimate))
# Filtering for Rutherford County districts
MyData <- AllData %>%
filter(
District == "State House District 13 (2022), Tennessee" |
District == "State House District 37 (2022), Tennessee" |
District == "State House District 49 (2022), Tennessee" |
District == "State House District 48 (2022), Tennessee" |
District == "State House District 34 (2022), Tennessee"
)
# Producing a map
MapData <- st_as_sf(MyData)
MyMap <- mapview(MapData,
zcol = "Estimate",
layer.name = "Estimate",
popup = TRUE)
#Displaying the map
MyMap
# Producing a table
TableData <- st_drop_geometry(MapData)
MyTable <- gt(TableData) %>%
tab_header("Estimate by district") %>%
cols_align(align = "left") %>%
gt_theme_538
# Displaying the table
MyTable
Let’s step through the script for a look at how it works and how you can tweak parts of it to get the data you want.
The script relies on help from five R packages:
Tidyverse makes R code easier to write and more intuitive.
Tidycensus helps R retrieve data from the U.S. Census Bureau’s application programming interfact, or API.
Sf, which stands for “simple features,” lets R read map files, also called geospatial files.
Mapview helps R customize and display map files easily.
gtExtras helps R make nicely formatted tables.
The first several lines of the script use the if()
and
install.packages()
functions to install each package if it
hasn’t been installed previously. Then, it activates each package using
the library()
function.
if (!require("tidyverse"))
install.packages("tidyverse")
if (!require("tidycensus"))
install.packages("tidycensus")
if (!require("sf"))
install.packages("sf")
if (!require("mapview"))
install.packages("mapview")
if (!require("gtExtras"))
install.packages("gtExtras")
library(tidyverse)
library(tidycensus)
library(sf)
library(mapview)
library(gtExtras)
Next, the script uses the census_api_key()
function from
the tidyverse package to open and authenticate a connection to the
Census Bureau’s API. Before you run the script, you must replace
PasteYourAPIKeyBetweenTheseQuoteMarks
with the API key that
you received by filling out and submitting the Request a U.S. Census Data API Key form.
census_api_key("PasteYourAPIKeyBetweenTheseQuoteMarks")
A typical American Community Survey dataset contains nearly 30,000
variables in close to 50 categories of social, economic, housing, and
demographic topics. Finding precisely what you need can be a challenge.
This block of code uses the load_variables()
function from
tidycensus to produces a data frame containing each the name,
label/description, and concept description for each variable available
in a specified ACS dataset.
In this example, the load_variables()
function uses uses
the 2022
and acs5
arguments to tell the Census
API to send variables for the 2022 five-year American Community Survey
dataset.
ACS data come in either single-year datasets that cover only geographic areas with 65,000 or more residents or in five-year datasets that cover all geographic areas, regardless of population. The five-year 2022 dataset requested in the example will cover all geographic areas and reflect variable estimates for the five-year period from 2018 to 2022.
You can change the acs5
arguments to acs1
to retrieve a single-year dataset. You can change the 2022
year arguments, too. For single-year datasets, you can specify 2005 or
later. For five-year datasets, you can request 2009 or later. At the
time this example was created, the 2018-2022 dataset was the
latest-available five-year dataset.
If you change an argument, be sure to change all three occurrences of
the argument in the code. Why three? The ACS breaks its variable
descriptions into three different files. The code fetches each of the
three separately, then combines them using the bind_rows()
function from tidyverse. The distinct()
function, also from
tidyverse, finds and removes any duplicate records. Finally, the
rm()
function, from base R, removes the three component
variable files, leaving only the “Codebook” file.
DetailedTables <- load_variables(2022, "acs5", cache = TRUE)
SubjectTables <- load_variables(2022, "acs5/subject", cache = TRUE)
ProfileTables <- load_variables(2022, "acs5/profile", cache = TRUE)
Codebook <- DetailedTables %>%
select(name, label, concept)
Codebook <- bind_rows(Codebook,SubjectTables)
Codebook <- bind_rows(Codebook,ProfileTables)
Codebook <- Codebook %>%
distinct(label, .keep_all = TRUE)
rm(DetailedTables,
SubjectTables,
ProfileTables)
You certainly don’t want to scroll through nearly 30,000 variable descriptions to find what you’re looking for. This block of code gives you a way to find and display only the descriptions that mention keywords you can specify.
In this example, the code is set to find variable descriptions that contain both “GRAPI” and “Percent!!” “GRAPI” is an acronym for “Gross Rent as a Percentage of Household Income.” “Percent!!” with the two exclamation points is how the Census Bureau denotes variables that show a percentage rather than a count.
You probably wouldn’t know these two shortcuts unless you have some experience working with ACS variables. You’d probably have to start with something like “gross rent,” then, perhaps, repeat the search a few times, with increasingly specific search terms, to narrow the results. Hey, it’s better than scrolling through nearly 30,000 records.
The code uses the %>%
pipe operator and
filter()
function from tidyverse, as well as the
grepl()
string-search function from base R.
MyVars <- Codebook %>%
filter(grepl("GRAPI", label) &
grepl("Percent!!", label))
This code uses the gt()
function and a few other
functions from gtExtras to make a table of the variable descriptions
that met the search criteria. Sending R the name of the table,
MyVarsTable, in the last line of the block tells R to display the table.
The resulting table is shown below the code block.
MyVarsTable <- gt(MyVars) %>%
tab_header("Variables") %>%
cols_align(align = "left") %>%
gt_theme_538
# Displaying the table
MyVarsTable
Variables | ||
name | label | concept |
---|---|---|
DP04_0136P | Percent!!GROSS RENT AS A PERCENTAGE OF HOUSEHOLD INCOME (GRAPI)!!Occupied units paying rent (excluding units where GRAPI cannot be computed) | Selected Housing Characteristics |
DP04_0137P | Percent!!GROSS RENT AS A PERCENTAGE OF HOUSEHOLD INCOME (GRAPI)!!Occupied units paying rent (excluding units where GRAPI cannot be computed)!!Less than 15.0 percent | Selected Housing Characteristics |
DP04_0138P | Percent!!GROSS RENT AS A PERCENTAGE OF HOUSEHOLD INCOME (GRAPI)!!Occupied units paying rent (excluding units where GRAPI cannot be computed)!!15.0 to 19.9 percent | Selected Housing Characteristics |
DP04_0139P | Percent!!GROSS RENT AS A PERCENTAGE OF HOUSEHOLD INCOME (GRAPI)!!Occupied units paying rent (excluding units where GRAPI cannot be computed)!!20.0 to 24.9 percent | Selected Housing Characteristics |
DP04_0140P | Percent!!GROSS RENT AS A PERCENTAGE OF HOUSEHOLD INCOME (GRAPI)!!Occupied units paying rent (excluding units where GRAPI cannot be computed)!!25.0 to 29.9 percent | Selected Housing Characteristics |
DP04_0141P | Percent!!GROSS RENT AS A PERCENTAGE OF HOUSEHOLD INCOME (GRAPI)!!Occupied units paying rent (excluding units where GRAPI cannot be computed)!!30.0 to 34.9 percent | Selected Housing Characteristics |
DP04_0142P | Percent!!GROSS RENT AS A PERCENTAGE OF HOUSEHOLD INCOME (GRAPI)!!Occupied units paying rent (excluding units where GRAPI cannot be computed)!!35.0 percent or more | Selected Housing Characteristics |
DP04_0143P | Percent!!GROSS RENT AS A PERCENTAGE OF HOUSEHOLD INCOME (GRAPI)!!Occupied units paying rent (excluding units where GRAPI cannot be computed)!!Not computed | Selected Housing Characteristics |
Deciphering ACS variable descriptions takes a little practice. But what we’re after here is the variable that shows the percentage of renter-occupied households spending 35 percent or more of their total household income on rent. That would be the next-to-last item in the table, the description of which reads, “Percent!!GROSS RENT AS A PERCENTAGE OF HOUSEHOLD INCOME (GRAPI)!!Occupied units paying rent (excluding units where GRAPI cannot be computed)!!35.0 percent or more.”
To retrieve the data for that variable, you need the variable’s name,
DP04_0142P, and you’ll need to paste or carefully type it between the
quote marks after Estimate_ =
in this snippet of code:
VariableList =
c(Estimate_ = "DP04_0142P")
This block of code uses the get_acs()
function from
tidycensus and a whole bunch of arguments to get DP04_0142P
for every Tennessee state House district and save it in a dataframe
called AllData.
Many of the get_acs()
function’s arguments are
customizable. Customizing them here, though, would require customizing
later parts of the script, particularly those that filter the data for
just the districts in Rutherford County, Tennessee.
But just so you know,
state legislative district (lower chamber)
could be changed
to any one of the geographies availabe for five-year ACS data. The tidycensus documentation’s “Geography in tidycensus”
section contains a list of available geographies and the types of ACS
datasets you can get them from. Additionally, you can change
TN
to the two-letter abbreviation of any other state,
2022
to any other available year, and acs5
to
acs1
to retrieve single year, rather than five-year,
data.
AllData <- get_acs(
geography = "state legislative district (lower chamber)",
state = "TN",
variables = VariableList,
year = 2022,
survey = "acs5",
output = "wide",
geometry = TRUE
)
There’s nothing to change here. R is just using various tidyverse functions to rename some variables, compute the error margin’s rounded-off lower and upper limits, and sort the data.
AllData <- AllData %>%
mutate(
District = NAME,
Estimate = Estimate_E,
Estimate_MOE = Estimate_M,
From = round(Estimate - Estimate_MOE, 2),
To = round(Estimate + Estimate_MOE, 2)
) %>%
select(District, Estimate, Estimate_MOE, From, To, geometry) %>%
arrange(desc(Estimate))
Tennessee has 99 state House districts. In this example, we’re after
just the five that cover Rutherford County, where MTSU is. This code
uses the filter()
function from tidyverse to filter the
retrieved data for just those five districts. Then, it stores the
filtered results in a dataframe called MyData
.
MyData <- AllData %>%
filter(
District == "State House District 13 (2022), Tennessee" |
District == "State House District 37 (2022), Tennessee" |
District == "State House District 49 (2022), Tennessee" |
District == "State House District 48 (2022), Tennessee" |
District == "State House District 34 (2022), Tennessee"
)
We’re in the home stretch now. Here, R uses the
st_as_sf()
function from sf to turn MyData into a
geospatial file that can be mapped, using the geospatial information in
the file’s geometry
column. Tidycensus added that
geometry
column when the original data was retrieved. The
data wrangling code, above, retained it for exactly this moment.
Once the map has been made and stored as an object called MyMap,
sending R the name MyMap
tells R to display the map. Here,
the map will show up below the code block.
MapData <- st_as_sf(MyData)
MyMap <- mapview(MapData,
zcol = "Estimate",
layer.name = "Estimate",
popup = TRUE)
#Displaying the map
MyMap
Time for the gtExtras package to do its thing again. This code uses
the package’s gt()
function and some other functions from
the package to make a nice table of each Rutherford County district’s
DP04_0142P
estimate and error margin limits. Along the way,
the code uses the sf package’s st_drop_geometry()
function
to strip the geometry column out of the data. No sense in having that
show up in the table.
Finally, the code tells R to show the table by sending R the table’s
name, MyTable
. Here, the table shows up below the code
block.
TableData <- st_drop_geometry(MapData)
MyTable <- gt(TableData) %>%
tab_header("Estimate by district") %>%
cols_align(align = "left") %>%
gt_theme_538
# Displaying the table
MyTable
Estimate by district | ||||
District | Estimate | Estimate_MOE | From | To |
---|---|---|---|---|
State House District 13 (2022), Tennessee | 42.9 | 7.7 | 35.2 | 50.6 |
State House District 37 (2022), Tennessee | 40.3 | 5.5 | 34.8 | 45.8 |
State House District 34 (2022), Tennessee | 35.9 | 4.8 | 31.1 | 40.7 |
State House District 49 (2022), Tennessee | 35.7 | 5.6 | 30.1 | 41.3 |
State House District 48 (2022), Tennessee | 34.4 | 5.9 | 28.5 | 40.3 |