Socio-economic factors
The following graphs show the association between poverty, unemployment, and median household income, respectively, with energy burden. For each measure, we have grouped each census tract in the region into terciles, meaning a low rank (1) includes the tracts with the lowest rate of each variable and high (3) includes those with the highest.
As we can see from these graphs, higher poverty rates, higher unemployment rates, and lower median household income are all associated with a higher percentage of energy-burdened households.
dat <- all
## poverty plot
dat1 <- bi_class(dat, x = povrateE, y = percentburdened, style = "quantile", dim = 3)
dat1$povrank <- stri_extract(dat1$bi_class, regex = '^\\d{1}(?=-\\d)')
dat2 <- dat1 %>%
dplyr::select(allhseE, percentburdened, povrank) %>%
filter(!is.na(percentburdened), !is.na(povrank)) %>%
mutate(mh_num = allhseE*(percentburdened/100)) %>%
group_by(povrank) %>%
summarize(mh_num = sum(mh_num),
num = sum(allhseE),
mh_per = round((mh_num/num)*100,1))
povplot <- dat2 %>%
ggplot(aes(povrank, mh_per, fill=povrank)) +
geom_bar(position = "dodge", stat = "identity") +
scale_fill_manual(labels = c("Lowest", "Middle", "Highest"),
values = c("#a5add3", "#dfd0d6", "#5698b9"),
name = "Rank") +
scale_y_continuous(labels = label_percent(scale = 1),
limits = c(0,40)) +
scale_x_discrete(labels = paste0(c('Lowest ', 'Middle ', 'Highest '))) +
labs(x = "% in poverty", y = "% of burdened households") + theme_bw()
## unemployment plot
dat6 <- bi_class(dat, x = unempE, y = percentburdened, style = "quantile", dim = 3)
dat6$unemployrank <- stri_extract(dat6$bi_class, regex = '^\\d{1}(?=-\\d)')
dat6 <- dat6 %>%
dplyr::select(allhseE, percentburdened, unemployrank) %>%
filter(!is.na(percentburdened), !is.na(unemployrank)) %>%
mutate(ph_num = allhseE*(percentburdened/100)) %>%
group_by(unemployrank) %>%
summarize(ph_num = sum(ph_num),
num = sum(allhseE),
ph_per = round((ph_num/num)*100,1))
employplot <- dat6 %>%
ggplot(aes(unemployrank, ph_per, fill=unemployrank)) +
geom_bar(position = "dodge", stat = "identity") +
scale_fill_manual(labels = c("Lowest", "Middle", "Highest"),
values = c("#a5add3", "#dfd0d6", "#5698b9"),
name = "Rank",
guide = "none") +
scale_y_continuous(labels = label_percent(scale = 1),
limits = c(0,40)) +
scale_x_discrete(labels = paste0(c('Lowest ', 'Middle ', 'Highest '))) +
labs(x = "% unemployed", y = "") + theme_bw()
## income plot
dat7 <- bi_class(dat, x = hhincE, y = percentburdened, style = "quantile", dim = 3)
dat7$incrank <- stri_extract(dat7$bi_class, regex = '^\\d{1}(?=-\\d)')
dat7 <- dat7 %>%
dplyr::select(allhseE, percentburdened, incrank) %>%
filter(!is.na(percentburdened), !is.na(incrank)) %>%
mutate(ph_num = allhseE*(percentburdened/100)) %>%
group_by(incrank) %>%
summarize(in_num = sum(ph_num),
num = sum(allhseE),
in_per = round((in_num/num)*100,1))
incomeplot <- dat7 %>%
ggplot(aes(incrank, in_per, fill=incrank)) +
geom_bar(position = "dodge", stat = "identity") +
scale_fill_manual(labels = c("Lowest", "Middle", "Highest"),
values = c("#a5add3", "#dfd0d6", "#5698b9"),
name = "Median household income rank",
guide = "none") +
scale_y_continuous(labels = label_percent(scale = 1),
limits = c(0,40)) +
scale_x_discrete(labels = paste0(c('Lowest ', 'Middle ', 'Highest '))) +
labs(x = "Median household income", y = "") + theme_bw()
ggarrange(povplot, employplot, incomeplot, ncol = 3,
legend = "bottom", common.legend = TRUE)
Racial demographics
The following graphs show the association between the racial composition of census tracts and energy burden. Just like in the graphs above, in each graph, a low rank (1) includes the tracts with the lowest rate of each variable and high (3) includes those with the highest.
The graphs show that percent of energy burdened households is much higher in tracts with higher percent non-White (particularly Black) residents, but much lower in tracts with higher percent White populations.
all <- all %>%
mutate(bipoc = 100 - whiteE)
# percent non-white plot
dat8 <- bi_class(all, x = bipoc, y = percentburdened, style = "quantile", dim = 3)
dat8$bipocrank <- stri_extract(dat8$bi_class, regex = '^\\d{1}(?=-\\d)')
dat8 <- dat8 %>%
dplyr::select(allhseE, percentburdened, bipocrank) %>%
filter(!is.na(percentburdened), !is.na(bipocrank)) %>%
mutate(ph_num = allhseE*(percentburdened/100)) %>%
group_by(bipocrank) %>%
summarize(in_num = sum(ph_num),
num = sum(allhseE),
in_per = round((in_num/num)*100,1))
bipocplot <- dat8 %>%
ggplot(aes(bipocrank, in_per, fill=bipocrank)) +
geom_bar(position = "dodge", stat = "identity") +
scale_fill_manual(labels = c("low", "mid", "high"),
values = c("#a5add3", "#dfd0d6", "#5698b9"),
name = "Rank") +
scale_y_continuous(labels = label_percent(scale = 1),
limits = c(0,40)) +
scale_x_discrete(labels = paste0(c('Lowest ', 'Middle ', 'Highest '))) +
labs(x = "% BIPOC", y = "% of burdened households") + theme_bw()
# percent Black plot
dat4 <- bi_class(dat, x = blackE, y = percentburdened, style = "quantile", dim = 3)
dat4$perBlackrank <- stri_extract(dat4$bi_class, regex = '^\\d{1}(?=-\\d)')
dat4 <- dat4 %>%
dplyr::select(allhseE, percentburdened, perBlackrank) %>%
filter(!is.na(percentburdened), !is.na(perBlackrank)) %>%
mutate(ca_num = allhseE*(percentburdened/100)) %>%
group_by(perBlackrank) %>%
summarize(ca_num = sum(ca_num),
num = sum(allhseE),
ca_per = round((ca_num/num)*100,1))
raceplot <- dat4 %>%
ggplot(aes(perBlackrank, ca_per, fill=perBlackrank)) +
geom_bar(position = "dodge", stat = "identity") +
scale_fill_manual(labels = c("low", "mid", "high"),
values = c("#a5add3", "#dfd0d6", "#5698b9"),
name = "Rank",
guide = "none") +
scale_y_continuous(labels = label_percent(scale = 1),
limits = c(0,40)) +
scale_x_discrete(labels = paste0(c('Lowest ', 'Middle ', 'Highest '))) +
labs(x = "% Black", y = "") + theme_bw()
# percent White plot
dat9 <- bi_class(dat, x = whiteE, y = percentburdened, style = "quantile", dim = 3)
dat9$perWhiterank <- stri_extract(dat9$bi_class, regex = '^\\d{1}(?=-\\d)')
dat9 <- dat9 %>%
dplyr::select(allhseE, percentburdened, perWhiterank) %>%
filter(!is.na(percentburdened), !is.na(perWhiterank)) %>%
mutate(ca_num = allhseE*(percentburdened/100)) %>%
group_by(perWhiterank) %>%
summarize(ca_num = sum(ca_num),
num = sum(allhseE),
ca_per = round((ca_num/num)*100,1))
whiteplot <- dat9 %>%
ggplot(aes(perWhiterank, ca_per, fill=perWhiterank)) +
geom_bar(position = "dodge", stat = "identity") +
scale_fill_manual(labels = c("low", "mid", "high"),
values = c("#a5add3", "#dfd0d6", "#5698b9"),
name = "Rank",
guide = "none") +
scale_y_continuous(labels = label_percent(scale = 1),
limits = c(0,40)) +
scale_x_discrete(labels = paste0(c('Lowest ', 'Middle ', 'Highest '))) +
labs(x = "% White", y = "") + theme_bw()
ggarrange(bipocplot, raceplot, whiteplot, ncol = 3,
legend = "bottom", common.legend = TRUE)
Home ownership
The following graph shows the percent of energy burdened households, broken down by owners and renters, across localities. We see that in general, renters face far greater energy burdens than homeowners.
There are a few plausible reasons for this pattern. Renters may, on average, have lower incomes than homeowners. Additionally, homeowners often have a greater incentive to make upgrades to their homes that improve energy efficiency and reduce their energy costs. In contrast, improvements made by landlords mostly serve to lower energy costs for tenants. Both of these phenomena may be influencing local trends.
# filter to just variables of interest
bar <- all %>%
group_by(county) %>%
summarize(pctrenters = mean(percent_burdened_renters),
pctowners = mean(percent_burdened_owners))
bar <- bar %>% arrange(pctrenters)
# convert from wide to long
bar_long <- gather(bar, type, percent, pctrenters:pctowners)
bar_long$county <- ifelse(bar_long$county == "Charlottesville city", "Charlottesville", bar_long$county)
# graph
ggplot(bar_long, aes(x = county, y = percent, fill = type)) +
geom_col(position = "dodge") +
scale_fill_manual(labels = c("% burdened (owners)", "% burdened (renters)"),
values = c("#a5add3", "#5698b9"),
name = "Resident type") +
labs(x = "Locality", y = "% of burdened households") + theme_bw()
Temperature
This map shows the correlation between the number of energy-burdened households and average maximum July temperatures. Tracts are divided into groups based on their relative ranking on percent energy burdened households and the average maximum July temperatures.
Mapping both variables together allows one to see where tracts that experience higher average summer temperatures also experience a higher rate of energy-burdened households. This combination is particularly stark in Louisa County.
# manually define palette
bipal <- c("#e8e8e8", "#dfd0d6", "#be64ac", # A-1, A-2, A-3,
"#ace4e4", "#a5add3", "#8c62aa", # B-1, B-2, B-3
"#5ac8c8", "#5698b9", "#3b4994") # C-1, C-2, C-3
cvlshapes <- cvlshapes %>%
mutate(burdenrank = ntile(percentburdened, 3),
#lsatrank = ntile(med_dev, 3)) %>%
temprank = ntile(July_AvgMaxTF, 3)) %>%
# oldhouse = 100-ba2000E,
# oldhouserank = ntile(oldhouse, 3)) %>%
mutate(burdenrank = if_else(burdenrank == 1, 'A',
if_else(burdenrank == 2, 'B', 'C')),
biclass = paste0(burdenrank, temprank))
# make legend
bipal3 <- tibble(
"3-3" = "#3b4994", # high average burden, high temperature
"2-3" = "#8c62aa",
"1-3" = "#be64ac", # low average burden, high temp
"3-2" = "#5698b9",
"2-2" = "#a5add3", # medium avg burden, medium temp
"1-2" = "#dfd0d6",
"3-1" = "#5ac8c8", # high avg burden, low temp
"2-1" = "#ace4e4",
"1-1" = "#e8e8e8" # low avg burden, low temp
) %>%
gather("group", "fill")
bipal3 <- bipal3 %>%
separate(group, into = c("burdenrank", "temprank"), sep = "-") %>%
mutate(burdenrank = as.integer(burdenrank),
temprank = as.integer(temprank))
legend2 <- ggplot() +
geom_tile(
data = bipal3,
mapping = aes(
x = burdenrank,
y = temprank,
fill = fill)
) +
scale_fill_identity() +
labs(x = expression("% energy burdened" %->% ""),
y = expression("July temps" %->% "")) +
theme_void() +
# make font small enough
theme(
axis.title = element_text(size = 6),
axis.title.y = element_text(angle = 90)
) +
# quadratic tiles
coord_fixed()
# Jacob's fix for the legend
ggsave(plot = legend2, filename = "bivariate_legend.svg",
width = 1, height = 1)
addLocalLogo <- function(map,
img,
alpha = 1,
src = c("remote", "local"),
url,
position = c("topleft", "topright",
"bottomleft", "bottomright"),
offset.x = 50,
offset.y = 13,
width = 60,
height = 60) {
if (inherits(map, "mapview")) map <- mapview2leaflet(map)
stopifnot(inherits(map, c("leaflet", "leaflet_proxy")))
position <- position[1]
src <- src[1]
div_topleft <- paste0("newDiv.css({
'position': 'absolute',
'top': '", offset.y, "px',
'left': '", offset.x, "px',
'background-color': 'transparent',
'border': '0px solid black',
'width': '", width, "px',
'height': '", height, "px',
});")
div_topright <- paste0("newDiv.css({
'position': 'absolute',
'top': '", offset.y, "px',
'right': '", offset.x, "px',
'background-color': 'transparent',
'border': '0px solid black',
'width': '", width, "px',
'height': '", height, "px',
});")
div_bottomleft <- paste0("newDiv.css({
'position': 'absolute',
'bottom': '", offset.y, "px',
'left': '", offset.x, "px',
'background-color': 'transparent',
'border': '0px solid black',
'width': '", width, "px',
'height': '", height, "px',
});")
div_bottomright <- paste0("newDiv.css({
'position': 'absolute',
'bottom': '", offset.y, "px',
'right': '", offset.x, "px',
'background-color': 'transparent',
'border': '0px solid black',
'width': '", width, "px',
'height': '", height, "px',
});")
div <- switch(position,
topleft = div_topleft,
topright = div_topright,
bottomleft = div_bottomleft,
bottomright = div_bottomright)
div_funk <- paste0("function(el, x, data) {
// we need a new div element because we have to handle
// the mouseover output seperately
// debugger;
function addElement () {
// generate new div Element
var newDiv = $(document.createElement('div'));
// append at end of leaflet htmlwidget container
$(el).append(newDiv);
//provide ID and style
newDiv.addClass('logo');\n",
div,
"return newDiv;
}")
div_add <- paste0("// check for already existing logo class to not duplicate
var logo = $(el).find('.logo');
if(!logo.length) {
logo = addElement();")
style <- paste0(', style="opacity:',
alpha,
';filter:alpha(opacity=',
alpha * 100, ');"')
div_html <- paste0("logo.html('<img src=", '"', img, '"',
", width=", width, ", height=", height, style,
", ></a>');
var map = HTMLWidgets.find('#' + el.id).getMap();
};
}")
render_stuff <- paste0(div_funk, div_add, div_html)
map <- htmlwidgets::onRender(map, render_stuff)
map
}
#df_4326 <- st_transform(cvlshapes, 4326)
factpal <- colorFactor(bipal, domain = cvlshapes$biclass)
leaflet() %>%
addProviderTiles("CartoDB.Positron") %>%
addPolygons(data = cvlshapes,
fillColor = ~factpal(biclass),
weight = 1,
opacity = 1,
color = "white",
fillOpacity = 0.8,
highlight = highlightOptions(
weight = 2,
fillOpacity = 0.8,
bringToFront = T),
popup = paste0("County: ", cvlshapes$county, "<br>",
"Percent Burdened: ",
round(cvlshapes$percentburdened, 2), "<br>",
"Average Max July Temp: ",
round(cvlshapes$July_AvgMaxTF, 1))) %>%
addLocalLogo("bivariate_legend.svg", src = "local",
position = "topright", width = 100, height = 100,
alpha = 0.8)
Click here info on the data sources presented here