library(glptools)
library(tidycensus)
glp_load_packages(graphs = T)
showtext_auto()
font_add("Museo Sans", "MuseoSans_300.otf")
font_add("Museo Sans 300 Italic", "MuseoSans_300_Italic.otf")
knitr::opts_knit$set(root.dir = rprojroot::find_rstudio_root_file())
knitr::opts_chunk$set(echo = TRUE, warning = FALSE, message = FALSE,
dev.args=list(bg="transparent"))
Main metro monitor image here…
Growth indicators measure change in the size of a metropolitan area economy and the economy’s level of entrepreneurial activity. Growth creates new opportunities for individuals and can help a metropolitan economy become more efficient. Entrepreneurship plays a critical role in growth, creating new jobs and new output; entrepreneurial activity can also indicate investors’ confidence in future growth and prosperity.
These measures are not adjusted for population. The per capita data are included in the prosperity section.
Output, or Gross Domestic Product (GDP) measures the total value of goods and services produced in a metropolitan area, including wages and profits.
After adjusting for inflation, Louisville’s GDP increased by around $12 billion from 2002 to 2019, falling slightly further behind the peer average. Because this data is not adjusted for population, our GDP is largely a reflection of our city’s size. Our GDP per capita is included in the Standard of Living section.
The Metro Monitor ranks cities on their percent change in GDP.
gdp <- readxl::read_excel("raw_data/GeoFRED_Gross_Domestic_Product__All_Industries_by_County_Thousands_of_U.S._Dollars.xls", skip = 1)
gdp %<>%
pivot_longer(`2001`:`2020`, names_to = "year", values_to = "GDP") %>%
transmute(
FIPS = `Region Code`,
year = as.numeric(year),
GDP = GDP *1000)
COLA_df <- glptools::COLA_df %>%
select(year, cpi) %>%
distinct() %>%
mutate(base_cpi = cpi[year == 2020],
cpi_index = base_cpi/cpi) %>%
select(year, cpi_index)
gdp2 <- gdp %>%
pull_peers(geog = "MSA") %>%
left_join(MSA_FIPS, by = "FIPS") %>%
mutate(core_c = if_else(FIPS %in% FIPS_df_two_stl$FIPS, "core", "suburb")) %>%
group_by(MSA, year, core_c) %>%
summarize(GDP = sum(GDP), .groups = "drop") %>%
pivot_wider(names_from = core_c, values_from = GDP)
gdp2 %<>%
filter(year %in% c(2009, 2019)) %>%
left_join(COLA_df, by = "year") %>%
mutate(core = core * cpi_index,
suburb = suburb * cpi_index) %>%
group_by(MSA) %>%
summarize(core = core[year == 2019] - core[year == 2009],
suburb = suburb[year == 2019] - suburb[year == 2009]) %>%
mutate(percent_suburb = suburb / (suburb + core) * 100) %>%
left_join(MSA_FIPS_core_county, by = "MSA") %>%
pull_peers(add_info = T) %>%
select(city, core, suburb, percent_suburb)
write_csv(gdp2, "gdp_change.csv")
rm(COLA_df)
gdp %<>%
pull_peers() %>%
stl_merge(GDP, method = "sum", simple = T) %>%
COLA(GDP, base_year = 2020, rpp = F)
metro_monitor <- gdp
png("images/gdp_trend.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
trend(
gdp,
GDP,
plot_title = "Gross Domestic Product",
y_title = "Dollars",
label_function = dollar_format(scale = 0.000000001, suffix = "B"),
axis_function = dollar_format(scale = 0.000000001, suffix = "B", accuracy = 1))
invisible(dev.off())
Louisville’s gross domestic product
Gross Domestic Product
Jobs measure the total number of occupied full- and part-time wage and salaried employment positions in a metropolitan economy.
The number of jobs in Louisville increased at a similar rate to peer cities from 2005 to 2020. Louisville added around 126,000 jobs from the low of 2010 to a high in 2019. The number of jobs decreased by 48,000 in 2020 due to the pandemic.
Because this data is not adjusted for population, the nunber of jobs is largely a reflection of our city’s size.
if ("jobs.RData" %in% list.files("intermediate_data")) {
load("intermediate_data/jobs.RData")
} else {
library(censusapi)
# Create data frame of variables
var_df <- crossing(FIPS = unique(FIPS_df_two_stl$FIPS),
year = 2005:2020,
name = "timeseries/qwi/sa", #c("timeseries/qwi/sa", "timeseries/qwi/se", "timeseries/qwi/rh"),
industry_code = "00")
# function wrapper to get data
get_fxn <- function(data) {
getCensus(
name = data$name,
vars = c("Emp", "EmpEnd", "EarnBeg"),
region = "county:" %p% str_sub(data$FIPS, 3, 5),
regionin = "state:" %p% str_sub(data$FIPS, 1, 2),
time = data$year,
key = "52e7948461b29e2ed1f7c53ceee270e6f7d8bcfe") %>%
nest(results = everything())
}
# Get data
future::plan(future::multisession)
all_jobs = var_df %>%
group_by(row_number()) %>%
nest() %>%
mutate(results = furrr::future_map_dfr(data, get_fxn)) %>%
select(-data) %>%
unnest(results) %>%
unnest(results) %>%
ungroup()
# repeat process for jobs at young firms
var_df %<>% crossing(age = c(1, 2, 3))
get_fxn <- function(data) {
getCensus(
name = data$name,
vars = c("Emp", "EmpEnd", "EarnBeg"),
region = "county:" %p% str_sub(data$FIPS, 3, 5),
regionin = "state:" %p% str_sub(data$FIPS, 1, 2),
time = data$year,
ownercode = "A05",
firmage = data$age,
key = "52e7948461b29e2ed1f7c53ceee270e6f7d8bcfe") %>%
nest(results = everything())
}
young_jobs = var_df %>%
group_by(row_number()) %>%
nest() %>%
mutate(results = furrr::future_map_dfr(data, get_fxn)) %>%
select(-data) %>%
unnest(results) %>%
unnest(results) %>%
ungroup()
clean_fxn <- function(df) {
df %>%
mutate(
FIPS = state %p% county,
year = as.numeric(str_sub(time, 1, 4)),
Emp = as.numeric(Emp),
EmpEnd = as.numeric(EmpEnd),
EarnBeg = as.numeric(EarnBeg),
Qemp = Emp + EmpEnd,
wages = EarnBeg * 12) %>%
group_by(FIPS, year) %>%
summarize(
jobs = mean(Qemp),
wages = mean(wages),
.groups = "drop")
}
all_jobs %<>%
clean_fxn()
jobs <- all_jobs %>%
stl_merge(jobs, method = "sum", simple = T)
wages <- all_jobs %>%
stl_merge(wages, method = "mean", simple = T) %>%
COLA(wages, rpp = F)
young_jobs %<>%
clean_fxn() %>%
stl_merge(jobs, method = "sum", simple = T) %>%
select(
FIPS, year,
young_jobs = jobs)
save(jobs, wages, young_jobs, file= "intermediate_data/jobs.RData")
}
metro_monitor %<>%
left_join(jobs, by = c("FIPS", "year")) %>%
left_join(young_jobs, by = c("FIPS", "year")) %>%
left_join(wages, by = c("FIPS", "year"))
png("images/jobs_trend.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
trend(
metro_monitor,
jobs,
plot_title = "Jobs",
y_title = "None")
invisible(dev.off())
Jobs
Jobs at young firms captures the total number of full- and part-time wage and salaried jobs at young, private-sector firms age five years. It reflects the employment impact of entrepreneurship in a metropolitan area.
png("images/young_jobs_trend.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
trend(
metro_monitor,
young_jobs,
plot_title = "Jobs at young firms",
subtitle_text = "Firms younger than 5 years",
y_title = "None")
invisible(dev.off())
Jobs at young Firms
Prosperity indicators capture changes in the average wealth and income produced by an economy. When a metropolitan area grows by increasing the productivity of its workers, through innovation or by upgrading workers’ skills, for example, the value of those workers’ labor rises. As the value of labor rises, so can wages. Increases in productivity and wages are what ultimately improve living standards for workers and families.
Average annual wage equals aggregate annual wages paid to workers divided by the total number of jobs.
png("images/average_wage_ranking.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
ranking(
metro_monitor,
wages,
year = 2019,
plot_title = "Average Annual Wages, 2019",
y_title = "Dollars")
invisible(dev.off())
Average Wage Ranking
png("images/average_wage_trend.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
trend(
metro_monitor,
wages,
plot_title = "Average Annual Wages",
y_title = "Dollars")
invisible(dev.off())
Average Wage Trend
Productivity equals Gross Domestic Product divided by the total number of jobs, a crude measure of a metropolitan economy’s overall productivity.
metro_monitor %<>%
mutate(productivity = GDP / jobs)
png("images/productivity_ranking.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
ranking(
metro_monitor,
productivity,
year = 2019,
plot_title = "Productivity, 2019",
subtitle_text = "GDP per job",
y_title = "Dollars")
invisible(dev.off())
Productivity
png("images/productivity_trend.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
trend(
metro_monitor,
productivity,
plot_title = "Productivity",
subtitle_text = "GDP per job",
y_title = "Dollars")
invisible(dev.off())
Productivity Trend
Standard of living equals Gross Metropolitan Product divided by total metropolitan population (GMP per capita), which reflects a metropolitan economy’s average standard of living.
pop_df <- glpdata::population_county %>%
filter(race == "total", sex == "total") %>%
select(FIPS, year, population) %>%
complete(FIPS, year = 2000:2020) %>%
fill(population, .direction = "down")
standard_of_living <- left_join(gdp, pop_df, by = c("FIPS", "year"))
standard_of_living %<>%
mutate(standard_of_living = GDP / population) %>%
select(-population, -GDP)
metro_monitor %<>%
left_join(standard_of_living, by = c("FIPS", "year"))
png("images/standard_of_living_ranking.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
ranking(
metro_monitor,
standard_of_living,
year = 2019,
plot_title = "Standard of Living, 2019",
subtitle_text = "GDP per person",
y_title = "Dollars")
invisible(dev.off())
Productivity
png("images/standard_of_living_trend.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
trend(
metro_monitor,
standard_of_living,
plot_title = "Standard of Living",
subtitle_text = "GDP per person",
y_title = "Dollars")
invisible(dev.off())
Productivity Trend
Inclusion indicators measure how the benefits of growth and prosperity in a metropolitan economy—specifically, changes in employment and income—are distributed among individuals. Inclusive growth enables more people to invest in their skills and to purchase more goods and services. Thus, inclusive growth can increase human capital and raise aggregate demand, boosting prosperity and growth.)
Employment rate measures the share of individuals ages 18 to 64 who are currently employed.
if ("employment.RData" %in% list.files("intermediate_data")) {
load("intermediate_data/employment.RData")
} else {
# Load data
unemployment_vars_00 <- build_census_var_df("sf3", c("PCT35", "P150"))
unemployment_vars_05_1yr <- build_census_var_df("acs1", c("B23001", "B23002"))
unemployment_vars_05_5yr <- build_census_var_df("acs5", "B23001")
unemployment_05_1yr <- bind_rows(unemployment_vars_00, unemployment_vars_05_1yr)
unemployment_05_5yr <- bind_rows(unemployment_vars_00, unemployment_vars_05_5yr)
unemployment_05_1yr <- bind_rows(unemployment_vars_00, unemployment_vars_05_1yr)
unemployment_05_5yr <- bind_rows(unemployment_vars_00, unemployment_vars_05_5yr)
unemployment_county <- get_census(unemployment_05_1yr, "FIPS", parallel = T)
unemployment_map_all <- get_census(unemployment_05_5yr, "tract_all", parallel = T)
test1 <- unemployment_county
test2 <- unemployment_map_all
# Categorize employment variables
unemployment_county %<>%
mutate(
employed = case_when(
str_detect(label, "Employed|Armed Forces") ~ TRUE,
str_detect(label, "Unemployed") ~ FALSE,
str_detect(label, "ot in labor force") ~ FALSE,
str_detect(label, "Total") ~ NA))
unemployment_map_all %<>%
mutate(
employed = case_when(
str_detect(label, "Employed|Armed Forces") ~ TRUE,
str_detect(label, "Unemployed") ~ FALSE,
str_detect(label, "ot in labor force") ~ FALSE,
str_detect(label, "Total") ~ NA))
# Categories check out
#test <- unemployment_map_all2 %>% filter(tract == "01073012602", var_type == "estimate", year == 2017, sex == "male")
unemployment_county %<>% process_census(var_names = "value", cat_var = "employed", output_name = "employment", age_groups = "18_64")
unemployment_map_all %<>% process_census(var_names = "value", cat_var = "employed", output_name = "employment", age_groups = "18_64")
save(unemployment_county, unemployment_map_all, file= "intermediate_data/employment.RData")
}
unemployment_county_small <- unemployment_county %>%
filter(sex == "total", race == "total", var_type == "percent") %>%
select(FIPS, year, employment)
metro_monitor %<>%
left_join(unemployment_county_small, by = c("FIPS", "year"))
test = unemployment_county %>%filter(FIPS == "21111", year %in% c(2009, 2019), sex == "total")
png("images/employment_ranking.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
ranking(
metro_monitor,
employment,
plot_title = "Employment, 2019",
subtitle_text = "Percent of adults age 18-64 who are employed")
invisible(dev.off())
Overall Employment Rate
png("images/employment_trend.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
trend(
metro_monitor,
employment,
plot_title = "Employment",
subtitle_text = "Percent of adults age 18-64 who are employed")
invisible(dev.off())
Overall Employment Rate
Median earnings measures the annual wage earned by the person in the middle of a metropolitan area’s income distribution (among people at least 16 years old).
# earnings <- glpdata::earnings_county
#
# earnings %<>%
# filter(sex == "total", race == "total", var_type == "estimate") %>%
# select(FIPS, year, median_earnings) %>%
# unique()
microdata <- arrow::read_feather("../../glpdata/data-raw/microdata/acs_micro_repwts.feather")
microdata %<>%
filter(age >= 16,
INCEARN > 0)
earnings <- survey_by_demog(microdata, "INCEARN", "PERWT", type = "median",
breakdowns = c("total", "sex", "race"))
earnings_county <- earnings %>%
filter(var_type == "estimate", sex == "total", race == "total") %>%
COLA(INCEARN, base_year = 2019) %>%
select(FIPS, year, median_earnings = INCEARN)
metro_monitor %<>% left_join(earnings_county, by = c("FIPS", "year"))
png("images/earnings_ranking.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
ranking(
metro_monitor,
median_earnings,
y_title = "Dollars",
plot_title = "Median Earnings, 2019",
subtitle_text = "Adjusted for inflation and cost of living")
invisible(dev.off())
Earnings Ranking
png("images/earnings_trend.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
trend(
metro_monitor,
median_earnings,
plot_title = "Median Earnings",
subtitle_text = "Adjusted for inflation and cost of living")
invisible(dev.off())
Earnings Trend
Relative earnings poverty measures the share of people earning less than half of the local median wage (among people at least 16 years old).
earnings_cutoffs <- earnings %>%
filter(var_type == "estimate", sex == "total", race == "total") %>%
mutate(relative_pov_rate = INCEARN / 2) %>%
select(FIPS, year, relative_pov_rate)
microdata %<>%
left_join(earnings_cutoffs, by = c("FIPS", "year")) %>%
mutate(relative_poverty = if_else(INCEARN < relative_pov_rate, T, F))
relative_earnings_poverty <- survey_by_demog(microdata, "relative_poverty", "PERWT", type = "categorical",
breakdowns = c("total", "sex", "race"))
relative_earnings_poverty_county <- relative_earnings_poverty %>%
filter(sex == "total", race == "total", var_type == "percent") %>%
select(FIPS, year, relative_poverty)
metro_monitor %<>%
left_join(relative_earnings_poverty_county, by = c("FIPS", "year"))
png("images/relative_poverty_ranking.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
ranking(
metro_monitor,
relative_poverty,
order = "Ascending",
plot_title = "Relative Poverty, 2019",
subtitle_text = "Percent of Workers earning less than 50% of the area median earnings")
invisible(dev.off())
Relative Poverty Ranking
png("images/relative_poverty_trend.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
trend(
metro_monitor,
relative_poverty,
plot_title = "Relative Poverty",
rollmean = 1,
subtitle_text = "Percent of Workers earning less than 50% of the area median earnings")
invisible(dev.off())
Relative Poverty Trend
Racial inclusion indicators measure the gap between the non-Hispanic white population and people of color on indicators of inclusion: median income, employment rate, and relative income poverty.
Employment rate measures the share of individuals ages 18 to 64 who are currently employed.
# unemployment_race <- unemployment_county %>%
# group_by(race) %>%
# filter(var_type == "CI") %>%
# summarize(avg_CI = mean(employment, na.rm=T))
unemployment_race <- unemployment_county %>%
filter(var_type == "percent",
sex == "total",
race %in% c("white", "black"))
unemployment_race_gap <- unemployment_county %>%
filter(race %in% c("white", "total"), sex == "total", var_type %in% c("estimate", "population")) %>%
pivot_wider(names_from = race, values_from = employment) %>%
mutate(nonwhite = total - white) %>%
pivot_vartype_wider(total:nonwhite) %>%
mutate(percent = estimate / population * 100) %>%
pivot_vartype_longer() %>%
filter(var_type == "percent") %>%
mutate(employment_race_gap = white - nonwhite) %>%
select(FIPS, year, employment_race_gap)
metro_monitor %<>%
left_join(unemployment_race_gap, by = c("FIPS", "year"))
png("images/employment_race.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
trend(
unemployment_race,
employment,
cat = "race",
plot_title = "Employment",
subtitle_text = "Percent of adults age 16-64 who are employed")
invisible(dev.off())
Employment Race
png("images/employment_race_gap.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
trend(
metro_monitor,
employment_race_gap,
rollmean = 1,
y_title = "Percentage Points",
plot_title = "Employment Gap between white and non-white residents",
subtitle_text = "Percent of adults age 16-64 who are employed")
invisible(dev.off())
Employment Gap
Median earnings measures the annual wage earned by the person in the middle of a metropolitan area’s income distribution (among people at least 16 years old).
microdata_recode <- microdata %>%
mutate(race = if_else(race == "white", "white", "nonwhite"))
earnings_white_nonwhite <- survey_by_demog(microdata_recode, "INCEARN", "PERWT", type = "median",
breakdowns = "race")
earnings_white_nonwhite %<>%
filter(var_type == "estimate") %>%
select(FIPS, year, race, INCEARN) %>%
pivot_wider(names_from = race, values_from = INCEARN) %>%
mutate(earnings_race_gap = white - nonwhite) %>%
select(FIPS, year, earnings_race_gap) %>%
COLA(earnings_race_gap, rpp = F)
metro_monitor %<>%
left_join(earnings_white_nonwhite, by = c("FIPS", "year"))
earnings_race <- earnings %>%
filter(var_type == "estimate") %>%
filter(sex == "total", race %in% c("white", "black", "hispanic")) %>%
COLA(INCEARN, base_year = 2019)
png("images/earnings_race.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
trend(
earnings_race,
INCEARN,
cat = "race",
y_title = "Dollars",
pctiles = F,
rollmean = 1,
plot_title = "Median Earnings",
subtitle_text = "Adjusted for inflation and cost of living")
invisible(dev.off())
Earnings Race
png("images/earnings_race_gap.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
trend(
metro_monitor,
earnings_race_gap,
rollmean = 1,
y_title = "Dollars",
plot_title = "Earnings Gap between white and non-white residents",
subtitle_text = "Adjusted for inflation and cost of living")
invisible(dev.off())
Earnings Gap
Relative earnings poverty measures the share of people earning less than half of the local median wage (among people at least 16 years old).
relative_poverty_white_nonwhite <- survey_by_demog(microdata_recode, "relative_poverty",
"PERWT", type = "categorical",
breakdowns = "race")
relative_poverty_white_nonwhite %<>%
filter(var_type == "percent") %>%
select(FIPS, year, race, relative_poverty) %>%
pivot_wider(names_from = race, values_from = relative_poverty) %>%
mutate(relative_poverty_race_gap = nonwhite - white) %>%
select(FIPS, year, relative_poverty_race_gap)
metro_monitor %<>%
left_join(relative_poverty_white_nonwhite, by = c("FIPS", "year"))
relative_earnings_poverty_race <- relative_earnings_poverty %>%
filter(var_type == "percent") %>%
filter(sex == "total", race %in% c("white", "black", "hispanic"))
png("images/relative_poverty_race.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
trend(
relative_earnings_poverty_race,
relative_poverty,
cat = "race",
pctiles = F,
rollmean = 1,
plot_title = "Relative Poverty",
subtitle_text = "Percent of Workers earning less than 50% of the area median earnings")
invisible(dev.off())
Relative Poverty Race
png("images/relative_poverty_gap.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
trend(
metro_monitor,
relative_poverty_race_gap,
rollmean = 1,
y_title = "Percentage Points",
plot_title = "Relative Poverty Gap between white and non-white residents",
subtitle_text = "Percent of Workers earning less than 50% of the area median earnings")
invisible(dev.off())
Relative Poverty Gap
Gender inclusion indicators measure the gap between females and males on indicators of inclusion: median income, employment rate, and relative income poverty.
Employment rate measures the share of individuals ages 18 to 64 who are currently employed.
unemployment_gender_gap <- unemployment_county %>%
filter(race == "total", sex %in% c("male", "female"), var_type == "percent") %>%
pivot_wider(names_from = "sex", values_from = "employment") %>%
mutate(employment_gender_gap = male - female) %>%
select(FIPS, year, employment_gender_gap)
metro_monitor %<>%
left_join(unemployment_gender_gap, by = c("FIPS", "year"))
employment_gender <- unemployment_county %>%
filter(var_type == "percent", race == "total")
png("images/employment_GENDER.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
trend(
employment_gender,
employment,
cat = "sex",
plot_title = "Employment",
subtitle_text = "Percent of adults age 16-64 who are employed")
invisible(dev.off())
Employment Gender
png("images/employment_gender_gap.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
trend(
metro_monitor,
employment_gender_gap,
rollmean = 1,
y_title = "Percentage Points",
plot_title = "Employment Gap between female and male residents",
subtitle_text = "Percent of adults age 16-64 who are employed")
invisible(dev.off())
Employment Gender Gap
Median earnings measures the annual wage earned by the person in the middle of a metropolitan area’s income distribution (among people at least 16 years old).
earnings_gender <- earnings %>%
filter(var_type == "estimate") %>%
filter(race == "total") %>%
COLA(INCEARN, base_year = 2019)
earnings_gender_gap <- earnings_gender %>%
pivot_wider(names_from = "sex", values_from = "INCEARN") %>%
mutate(earnings_gender_gap = female - male) %>%
select(FIPS, year, earnings_gender_gap)
metro_monitor %<>%
left_join(earnings_gender_gap, by = c("FIPS", "year"))
png("images/earnings_gender.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
trend(
earnings_gender,
INCEARN,
cat = "sex",
rollmean = 1,
y_title = "Dollars",
plot_title = "Median Earnings",
subtitle_text = "Adjusted for inflation and cost of living")
invisible(dev.off())
Earnings Gender
png("images/earnings_gender_gap.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
trend(
metro_monitor,
earnings_gender_gap,
rollmean = 1,
y_title = "Dollars",
plot_title = "Earnings Gap between female and maleresidents",
subtitle_text = "Adjusted for inflation and cost of living")
invisible(dev.off())
Earnings Gap
relative_poverty_gender_gap <- relative_earnings_poverty %>%
filter(race == "total", sex %in% c("male", "female"), var_type == "percent") %>%
pivot_wider(names_from = "sex", values_from = "relative_poverty") %>%
mutate(relative_poverty_gender_gap = female - male) %>%
select(FIPS, year, relative_poverty_gender_gap)
metro_monitor %<>%
left_join(relative_poverty_gender_gap, by = c("FIPS", "year"))
relative_poverty_gender <- relative_earnings_poverty %>%
filter(var_type == "percent", race == "total")
png("images/relative_poverty_gender.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
trend(
relative_poverty_gender,
relative_poverty,
cat = "sex",
plot_title = "Relative Poverty",
subtitle_text = "Percent of Workers earning less than 50% of the area median earnings")
invisible(dev.off())
Relative Poverty Gender
png("images/relative_poverty_gender_gap.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
trend(
metro_monitor,
relative_poverty_gender_gap,
rollmean = 1,
y_title = "Percentage Points",
plot_title = "Relative Poverty Gap between female and male residents",
subtitle_text = "Percent of Workers earning less than 50% of the area median earnings")
invisible(dev.off())
Relative poverty Gender Gap
Geographic inclusion indicators measure changes in the gap between the most advantaged (top 20%) and least advantaged (bottom 20%) of census tracts in each metropolitan area, for each of three underlying inclusion indicators: employment rate, median household income, and relative poverty rate.
Employment rate measures the share of individuals ages 18 to 64 who are currently employed.
# Categorize census tracts as top_20 or bottom_20
unemployment_map_all %<>%
mutate(FIPS = str_sub(tract, 1, 5),
FIPS = if_else(FIPS %in% c("29189", "29510"), "MERGED", FIPS)) %>%
filter(var_type == "percent") %>%
group_by(FIPS, year) %>%
arrange(employment) %>%
mutate(
category = case_when(
employment > quantile(employment, 0.8) ~ "top_20",
employment < quantile(employment, 0.2) ~ "bottom_20",
TRUE ~ "middle_60")) %>%
ungroup() %>%
mutate(year = year + 2)
unemployment_map_lou <- unemployment_map_all %>%
filter(FIPS == "21111", year == 2017, race == "total", sex == "total", var_type == "percent")
# Summarize employment by group and spread across columns
unemployment_map_summary <- unemployment_map_all %>%
group_by(FIPS, year, category) %>%
summarize(employment = mean(employment), .groups = "drop") %>%
pivot_wider(values_from = "employment", names_from = "category") %>%
mutate(employment_gap = top_20 - bottom_20)
unemployment_map_final <- unemployment_map_summary %>%
select(FIPS, year, employment_geography_gap = employment_gap)
metro_monitor %<>%
left_join(unemployment_map_final, by = c("FIPS", "year"))
unemployment_map <- glptools::map_tract %>%
left_join(unemployment_map_lou, by = c("tract"))
pal <- leaflet::colorNumeric(
palette = RColorBrewer::brewer.pal(9, "BuPu"),
domain = range(unemployment_map_lou$employment))
pal2 <- leaflet::colorFactor(
palette = c("#0e4a99", "#f58021"),
domain = c("Highest employment", "Lowest employment"))
m <- leaflet()
top <- unemployment_map %>%
filter(category %in% c("top_20"))
bottom <- unemployment_map %>%
filter(category %in% c("bottom_20"))
m <- m %>% addPolygons(
data = unemployment_map,
color = "#444444", weight = 1, smoothFactor = 0.5, opacity = 1.0, fillOpacity = 0.5,
fillColor = ~pal(employment)) %>%
addTiles(urlTemplate = '//{s}.basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}.png')
m <- m %>%
addLegend(pal = pal, values = range(unemployment_map_lou$employment), opacity = 0.7, title = "Employment Rate (%)", position = "bottomright") %>%
addLegend(pal = pal2, values = c("Highest employment", "Lowest employment"), opacity = 0.7, title = "", position = "bottomright")
m <- m %>% addPolygons(
data = top,
color = "#0e4a99", weight = 2, smoothFactor = 0.5, opacity = 1.0, fill = FALSE)
m <- m %>% addPolygons(
data = bottom,
color = "#f58021", weight = 2, smoothFactor = 0.5, opacity = 1.0, fill = FALSE)
m
png("images/neighborhood_employment_trend.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
trend(
select(unemployment_map_summary, -middle_60),
bottom_20:top_20,
plot_title = "Neighborhood employment gap, 2019",
cat = c("Top 20% of Neighborhoods" = "top_20", "Bottom 20% of Neighborhoods" = "bottom_20"),
subtitle_text = "Percent of adults age 18-64 who are employed")
invisible(dev.off())
Overall Employment Rate
Household income measures the average total income earned by households.
hh_income_5yr <- build_census_var_df("acs5", "B19013")
hh_income_map_all <- get_census(hh_income_5yr, "tract_all", var_name = "hh_income", parallel = T)
hh_income_map_all %<>%
select(tract, year, sex, race, var_type, hh_income) %>%
COLA(hh_income) %>%
mutate(year = year + 2) %>%
mutate(hh_income = if_else(hh_income < 0, NA_real_, hh_income))
# Categorize census tracts as top_20 or bottom_20
hh_income_map_all %<>%
mutate(FIPS = str_sub(tract, 1, 5),
FIPS = if_else(FIPS %in% c("29189", "29510"), "MERGED", FIPS)) %>%
filter(var_type == "estimate") %>%
group_by(FIPS, year) %>%
arrange(hh_income) %>%
mutate(
category = case_when(
hh_income > quantile(hh_income, 0.8, na.rm = TRUE) ~ "top_20",
hh_income < quantile(hh_income, 0.2, na.rm = TRUE) ~ "bottom_20",
TRUE ~ "middle_60")) %>%
ungroup()
income_map_lou <- hh_income_map_all %>%
filter(FIPS == "21111", year == 2019, race == "total", sex == "total", var_type == "estimate")
# Summarize employment by group and spread across columns
hh_income_summary <- hh_income_map_all %>%
group_by(FIPS, year, category) %>%
summarize(hh_income = mean(hh_income, na.rm = TRUE), .groups = "drop") %>%
pivot_wider(values_from = "hh_income", names_from = "category") %>%
mutate(income_gap = top_20 - bottom_20)
hh_income_final <- hh_income_summary %>%
select(FIPS, year, income_geo_gap = income_gap)
metro_monitor %<>%
left_join(hh_income_final, by = c("FIPS", "year"))
hh_income_map <- glptools::map_tract %>%
left_join(income_map_lou, by = c("tract"))
pal <- leaflet::colorNumeric(
palette = RColorBrewer::brewer.pal(9, "BuPu"),
domain = range(income_map_lou$hh_income, na.rm=T))
pal2 <- leaflet::colorFactor(
palette = c("#0e4a99", "#f58021"),
domain = c("Highest income", "Lowest income"))
m <- leaflet()
top <- hh_income_map %>%
filter(category %in% c("top_20"))
bottom <- hh_income_map %>%
filter(category %in% c("bottom_20"))
m <- m %>% addPolygons(
data = hh_income_map,
color = "#444444", weight = 1, smoothFactor = 0.5, opacity = 1.0, fillOpacity = 0.5,
fillColor = ~pal(hh_income)) %>%
addTiles(urlTemplate = '//{s}.basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}.png')
m <- m %>%
addLegend(pal = pal, values = range(income_map_lou$hh_income, na.rm = T), opacity = 0.7, title = "Household Income ($)", position = "bottomright") %>%
addLegend(pal = pal2, values = c("Highest income", "Lowest income"), opacity = 0.7, title = "", position = "bottomright")
m <- m %>% addPolygons(
data = top,
color = "#0e4a99", weight = 2, smoothFactor = 0.5, opacity = 1.0, fill = FALSE)
m <- m %>% addPolygons(
data = bottom,
color = "#f58021", weight = 2, smoothFactor = 0.5, opacity = 1.0, fill = FALSE)
m
png("images/neighborhood_income_trend.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
trend(
select(hh_income_summary, -middle_60),
bottom_20:top_20,
plot_title = "Neighborhood income gap, 2019",
cat = c("Top 20% of Neighborhoods" = "top_20", "Bottom 20% of Neighborhoods" = "bottom_20"),
subtitle_text = "Household Income")
invisible(dev.off())
## Neighborhood gap in relative
poverty
Relative earnings poverty measures the share of people earning less than half of the local median wage (among people at least 16 years old).
if ("employment.RData" %in% list.files("intermediate_data")) {
load("intermediate_data/relative_poverty.RData")
} else {
earnings_05 <- build_census_var_df("acs5", "B20001")
earnings_map <- get_census(earnings_05, "tract_all")
relative_poverty_map = earnings_map %>%
mutate(
label = str_remove_all(label, paste(c("Estimate!!Total:!!Male:!!", "Estimate!!Total!!Female!!", "Estimate!!Total!!Male!!",
"Male:!!", "Estimate!!Total:!!Female:!!", "Female:!!", ",", "(?<=\\d{1,3}) (?=\\d{3})",
"\\$"),
collapse = "|"))) %>%
filter(str_detect(label, "Female|Male|Total", negate = T)) %>%
mutate(
label = case_when(
label == "1 to 2499 or loss" ~ "0 to 2500",
label == "100000 or more" ~ "100000 to 1000000",
TRUE ~ label)) %>%
mutate(
FIPS = str_sub(tract, 1, 5),
FIPS = if_else(FIPS %in% c("29189", "29510"), "MERGED", FIPS),
earnings_low = as.numeric(str_extract(label, "\\d*(?= to)")),
earnings_high = as.numeric(str_extract(label, "(?<=to )\\d*"))) %>%
left_join(earnings_cutoffs, by = c("FIPS", "year")) %>%
mutate(
relative_poverty_num = case_when(
relative_pov_rate > earnings_high ~ value,
relative_pov_rate < earnings_low ~ 0,
TRUE ~ (relative_pov_rate - earnings_low) / (earnings_high - earnings_low) * value)) %>%
group_by(FIPS, tract, year) %>%
summarize(
relative_poverty = sum(relative_poverty_num) / sum(value) * 100,
.groups = "drop")
# Categorize census tracts as top_20 or bottom_20
relative_poverty_map %<>%
group_by(FIPS, year) %>%
arrange(relative_poverty) %>%
mutate(
category = case_when(
relative_poverty > quantile(relative_poverty, 0.8) ~ "top_20",
relative_poverty < quantile(relative_poverty, 0.2) ~ "bottom_20",
TRUE ~ "middle_60")) %>%
ungroup() %>%
mutate(year = year + 2)
relative_poverty_map_lou <- relative_poverty_map %>%
filter(FIPS == "21111", year == 2019)
# Summarize employment by group and spread across columns
relative_poverty_map_summary <- relative_poverty_map %>%
group_by(FIPS, year, category) %>%
summarize(relative_poverty = mean(relative_poverty), .groups = "drop") %>%
pivot_wider(values_from = "relative_poverty", names_from = "category") %>%
mutate(relative_poverty_gap = top_20 - bottom_20)
relative_poverty_map_final <- relative_poverty_map_summary %>%
select(FIPS, year, relative_poverty_geo_gap = relative_poverty_gap)
save(relative_poverty_map_final, relative_poverty_map_summary, relative_poverty_map_lou, file= "intermediate_data/relative_poverty.RData")
}
metro_monitor %<>%
left_join(relative_poverty_map_final, by = c("FIPS", "year"))
relative_poverty_map <- glptools::map_tract %>%
left_join(relative_poverty_map_lou, by = c("tract"))
pal <- leaflet::colorNumeric(
palette = RColorBrewer::brewer.pal(9, "BuPu"),
domain = range(relative_poverty_map$relative_poverty, na.rm=T))
pal2 <- leaflet::colorFactor(
palette = c("#0e4a99", "#f58021"),
domain = c("Highest poverty", "Lowest poverty"))
m <- leaflet()
top <- relative_poverty_map %>%
filter(category %in% c("top_20"))
bottom <- relative_poverty_map %>%
filter(category %in% c("bottom_20"))
m <- m %>% addPolygons(
data = relative_poverty_map,
color = "#444444", weight = 1, smoothFactor = 0.5, opacity = 1.0, fillOpacity = 0.5,
fillColor = ~pal(relative_poverty)) %>%
addTiles(urlTemplate = '//{s}.basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}.png')
m <- m %>%
addLegend(pal = pal, values = range(relative_poverty_map$relative_poverty, na.rm = T), opacity = 0.7,
title = "Relative Poverty (%)", position = "bottomright") %>%
addLegend(pal = pal2, values = c("Highest poverty", "Lowest poverty"), opacity = 0.7, title = "", position = "bottomright")
m <- m %>% addPolygons(
data = top,
color = "#0e4a99", weight = 2, smoothFactor = 0.5, opacity = 1.0, fill = FALSE)
m <- m %>% addPolygons(
data = bottom,
color = "#f58021", weight = 2, smoothFactor = 0.5, opacity = 1.0, fill = FALSE)
m
png("images/neighborhood_income_trend.png", 3000, 2400, res = 200, bg = "transparent", type = "cairo")
trend(
select(hh_income_summary, -middle_60),
bottom_20:top_20,
plot_title = "Neighborhood income gap, 2019",
cat = c("Top 20% of Neighborhoods" = "top_20", "Bottom 20% of Neighborhoods" = "bottom_20"),
subtitle_text = "Household Income")
invisible(dev.off())
Overall Employment Rate
metro_monitor %<>%
select(FIPS, year,
GDP, jobs, young_jobs,
productivity, wages, standard_of_living,
employment, median_earnings, relative_poverty,
employment_race_gap, earnings_race_gap, relative_poverty_race_gap,
employment_gender_gap, earnings_gender_gap, relative_poverty_gender_gap,
employment_geography_gap, income_geo_gap, relative_poverty_geo_gap)
metro_monitor_change <- metro_monitor %>%
group_by(FIPS) %>%
summarize(
GDP = (GDP[year == 2019] - GDP[year == 2009]) / GDP[year == 2009] * 100,
jobs = (jobs[year == 2019] - jobs[year == 2009]) / jobs[year == 2009] * 100,
young_jobs = (young_jobs[year == 2019] - young_jobs[year == 2009]) / young_jobs[year == 2009] * 100,
productivity = (productivity[year == 2019] - productivity[year == 2009]) / productivity[year == 2009] * 100,
wages = (wages[year == 2019] - wages[year == 2009]) / wages[year == 2009] * 100,
standard_of_living = (standard_of_living[year == 2019] - standard_of_living[year == 2009]) / standard_of_living[year == 2009] * 100,
employment = employment[year == 2019] - employment[year == 2009],
median_earnings = (median_earnings[year == 2019] - median_earnings[year == 2009]) / median_earnings[year == 2009] * 100,
relative_poverty = relative_poverty[year == 2019] - relative_poverty[year == 2009],
employment_race_gap = employment_race_gap[year == 2019] - employment_race_gap[year == 2009],
earnings_race_gap = earnings_race_gap[year == 2019] - earnings_race_gap[year == 2009],
relative_poverty_race_gap = relative_poverty_race_gap[year == 2019] - relative_poverty_race_gap[year == 2009],
employment_gender_gap = employment_gender_gap[year == 2019] - employment_gender_gap[year == 2009],
earnings_gender_gap = earnings_gender_gap[year == 2019] - earnings_gender_gap[year == 2009],
relative_poverty_gender_gap = relative_poverty_gender_gap[year == 2019] - relative_poverty_gender_gap[year == 2009],
employment_geography_gap = employment_geography_gap[year == 2019] - employment_geography_gap[year == 2009],
income_geo_gap = income_geo_gap[year == 2019] - income_geo_gap[year == 2009],
relative_poverty_geo_gap = relative_poverty_geo_gap[year == 2019] - relative_poverty_geo_gap[year == 2009])
# Create long data frames to calculate z scores
regular_index <- metro_monitor %>%
pull_peers(add_info = T) %>%
filter(current == 1) %>%
select(-FIPS, -baseline, -current) %>%
pivot_longer(GDP:relative_poverty_geo_gap, names_to = "variable", values_to = "value") %>%
filter(year %in% 2009:2019)
growth_index <- metro_monitor_change %>%
pull_peers(add_info = T) %>%
filter(current == 1) %>%
select(-FIPS, -baseline, -current) %>%
pivot_longer(GDP:relative_poverty_geo_gap, names_to = "variable", values_to = "value")
city_regular <- regular_index
city_change <- growth_index
# Calculate z scores
regular_z <- regular_index %<>%
group_by(variable, year) %>%
summarize(
mean = mean(value, na.rm = T),
sd = sd(value, na.rm = T),
.groups = "drop")
growth_z <- growth_index %<>%
group_by(variable) %>%
summarize(
mean = mean(value, na.rm = T),
sd = sd(value, na.rm = T),
.groups = "drop")
# Calculate Z scores across all variables
regular_final <- city_regular %>%
left_join(regular_z, by = c("variable", "year")) %>%
mutate(
value_z = (value - mean) / sd,
variable = variable %p% "_index") %>%
select(-mean, -sd, -value) %>%
pivot_wider(names_from = "variable", values_from = "value_z") %>%
mutate(across(c(relative_poverty_index, employment_race_gap_index, earnings_race_gap_index, relative_poverty_race_gap_index,
employment_gender_gap_index, earnings_gender_gap_index, relative_poverty_gender_gap_index,
employment_geography_gap_index, income_geo_gap_index, relative_poverty_geo_gap_index), ~ . * -1)) %>%
mutate(
growth_index = (GDP_index + jobs_index + young_jobs_index) / 3,
prosperity_index = (wages_index + productivity_index + standard_of_living_index) / 3,
overall_inclusion = (employment_index + median_earnings_index + relative_poverty_index) / 3,
racial_inclusion = (employment_race_gap_index + earnings_race_gap_index + relative_poverty_race_gap_index) / 3,
gender_inclusion = (employment_gender_gap_index + earnings_gender_gap_index +
relative_poverty_gender_gap_index) / 3,
geographic_inclusion = (employment_geography_gap_index + income_geo_gap_index + relative_poverty_geo_gap_index) / 3)
growth_final <- city_change %>%
left_join(growth_z, by = "variable") %>%
mutate(
value_z = (value - mean) / sd,
variable = variable %p% "_index") %>%
select(-mean, -sd, -value) %>%
pivot_wider(names_from = "variable", values_from = "value_z") %>%
mutate(across(c(relative_poverty_index, employment_race_gap_index, earnings_race_gap_index, relative_poverty_race_gap_index,
employment_gender_gap_index, earnings_gender_gap_index, relative_poverty_gender_gap_index,
employment_geography_gap_index, income_geo_gap_index, relative_poverty_geo_gap_index), ~ . * -1)) %>%
mutate(
growth_index = (GDP_index + jobs_index + young_jobs_index) / 3,
prosperity_index = (wages_index + productivity_index + standard_of_living_index) / 3,
overall_inclusion = (employment_index + median_earnings_index + relative_poverty_index) / 3,
racial_inclusion = (employment_race_gap_index + earnings_race_gap_index + relative_poverty_race_gap_index) / 3,
gender_inclusion = (employment_gender_gap_index + earnings_gender_gap_index +
relative_poverty_gender_gap_index) / 3,
geographic_inclusion = (employment_geography_gap_index + income_geo_gap_index + relative_poverty_geo_gap_index) / 3)
regular_df <- regular_final %>%
pivot_longer(GDP_index:geographic_inclusion, names_to = "variable", values_to = "value") %>%
group_by(variable, year) %>%
nest() %>%
mutate(data = map(data, ~arrange(., desc(value)))) %>%
mutate(data = map(data, ~mutate(., ranking = row_number()))) %>%
unnest() %>%
ungroup()
change_df <- growth_final %>%
pivot_longer(GDP_index:geographic_inclusion, names_to = "variable", values_to = "value") %>%
group_by(variable) %>%
nest() %>%
mutate(data = map(data, ~arrange(., desc(value)))) %>%
mutate(data = map(data, ~mutate(., ranking = row_number()))) %>%
unnest() %>%
ungroup()
ranking_df <- change_df %>%
select(-value) %>%
pivot_wider(values_from = ranking, names_from = "variable")
values_df <- change_df %>%
select(-ranking) %>%
pivot_wider(values_from = value, names_from = "variable")
write_csv(ranking_df, "ranking.csv")
write_csv(values_df, "values.csv")