7 Leaflet Maps
Main functions and concepts covered in this BP chapter:
get_acs()
leaflet()
Packages used in this chapter:
## Load all packages used in this chapter
library(tidyverse) #includes dplyr, ggplot2, and other common packages
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.2 ──
## ✔ ggplot2 3.4.0 ✔ purrr 0.3.5
## ✔ tibble 3.1.8 ✔ dplyr 1.0.10
## ✔ tidyr 1.2.1 ✔ stringr 1.5.0
## ✔ readr 2.1.3 ✔ forcats 0.5.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
library(tidycensus)
library(viridis)
## Loading required package: viridisLite
Datasets used in this chapter:
## Load datasets used in this chapter
#census_api_key("ca24a38ee3604f744dbdf2e64bc93a3ac214a7d7")
Some of you might find it helpful to check out DataCamp’s Leaflet course, but it is not assigned. They also have other courses on spatial data). If you do any (or parts of…you don’t need to do entire courses, other than the ones I assigned) of these DC courses, you are free to add notes to this chapter, but I won’t be looking for that when checking off this chapter. Instead, this chapter will be based off the tutorial I give you and your own notes.
For two of the projects, you will be required to make maps (optional for the RP). I will give you a separate GitHub repo to help you learn how to make interactive maps using https://rstudio.github.io/leaflet. For this BP chapter, you will write your own short tutorial on how to make a leaflet map. You can copy/paste from what I give you and what we discuss as part of the course. You are also free to use content from the internet or the DataCamp course on leaflet (but as mentioned above, these notes are meant to be your own tutorial, not something that just follows the DC course).
So what should this chapter include? Suppose in a year you want to make a leaflet map, but you don’t remember anything about how to do so. You should be able to open up this chapter and follow along to create an interactive map using leaflet. Include enough details so you understand what’s going on, but not more than you think will be helpful. I might give you 8 maps in my tutorial, but that doesn’t mean yours should include 8 maps (in fact, I suggest only including one or two since otherwise the file size might get too big for GitHub). Since no two people find the same explanations/examples equally clear, I don’t expect multiple people in the class to have the same chapter (and won’t give credit if that’s what I find). You can talk to each other, but write your own.
This chapter might be graded somewhat differently from the previous chapters.
7.0.1 get_acs
The following link goes through the different arguments of get_acs
. To get variables, you have to find their code instead of their name. To do this, use the load_variables()
function. The function takes two required arguments: the year of the Census or endyear of the ACS sample, and the dataset name, which varies in availability by year. Once you view the dataset, you can use the filter option in the top left corner to search for specific variables. Make sure to pay attention to the geography column, as different variables have different labels for geography.
<- load_variables(2020, "acs5", cache = TRUE)
v20
View(v20)
## Warning in View(v20): unable to open display
ERROR: in .External2(C_dataviewer, x, title): unable to start data viewer
You can also rename variables by what their actual name is rather than their code.
<- get_acs(geography = "tract", variables = (population = "B01003_001"),
texaspop state = "TX", geometry = TRUE, year = 2020)
## Getting data from the 2016-2020 5-year ACS
## Warning: • You have not set a Census API key. Users without a key are limited to 500
## queries per day and may experience performance limitations.
## ℹ For best results, get a Census API key at
## http://api.census.gov/data/key_signup.html and then supply the key to the
## `census_api_key()` function to use it throughout your tidycensus session.
## This warning is displayed once per session.
## Downloading feature geometry from the Census website. To cache shapefiles for use in future sessions, set `options(tigris_use_cache = TRUE)`.
##
|
| | 0%
|
| | 1%
|
|= | 1%
|
|= | 2%
|
|== | 2%
|
|== | 3%
|
|=== | 4%
|
|=== | 5%
|
|==== | 5%
|
|==== | 6%
|
|===== | 7%
|
|===== | 8%
|
|====== | 8%
|
|====== | 9%
|
|======= | 9%
|
|======= | 10%
|
|======== | 11%
|
|========= | 12%
|
|========= | 13%
|
|========== | 14%
|
|========== | 15%
|
|=========== | 15%
|
|=========== | 16%
|
|============ | 17%
|
|============= | 18%
|
|============== | 19%
|
|============== | 20%
|
|=============== | 21%
|
|================ | 22%
|
|================= | 24%
|
|================== | 25%
|
|=================== | 27%
|
|==================== | 28%
|
|===================== | 29%
|
|====================== | 32%
|
|======================= | 33%
|
|========================= | 36%
|
|========================== | 36%
|
|================================ | 45%
|
|==================================== | 51%
|
|==================================== | 52%
|
|===================================== | 53%
|
|======================================== | 57%
|
|============================================ | 62%
|
|=============================================== | 68%
|
|================================================ | 69%
|
|=================================================== | 73%
|
|==================================================== | 74%
|
|==================================================== | 75%
|
|===================================================== | 76%
|
|======================================================= | 78%
|
|======================================================= | 79%
|
|======================================================== | 80%
|
|========================================================== | 83%
|
|=========================================================== | 84%
|
|============================================================ | 85%
|
|=============================================================== | 90%
|
|=================================================================== | 95%
|
|======================================================================| 100%
ggplot(texaspop, aes(fill = estimate, color = estimate)) +
geom_sf() +
coord_sf(crs = 26914) +
scale_fill_viridis(option = "magma") +
scale_color_viridis(option = "magma")
library(tidyverse)
library(leaflet)
library(rmapshaper)
library(tidycensus)
options(tigris_use_cache = TRUE)
## turn off scientific notation
options(scipen = 999)
## Download GIS data for maps
## geometry = TRUE --> include GIS shapefile data to create maps
## B01001_001: total population
## NOTE: When you download the county data for the regressions, use options: geometry = False, keep_geo_vars = FALSE
# Median household income
<- get_acs(geography = "county",
countyGIS variables = "B01001_001",
geometry = TRUE,
keep_geo_vars = TRUE)
# State data (for displaying state borders on map)
<- get_acs(geography = "state",
stateGIS variables = "B01001_001",
geometry = TRUE,
keep_geo_vars = FALSE)
<- countyGIS
countyGISsave <- stateGIS stateGISsave
<- stateGISsave
stateGIS <- countyGISsave
countyGIS
## Simplify GIS data to make file sizes smaller. This essentially removes some details along coastlines and very-not-straight borders.
<- ms_simplify(stateGIS, keep = 0.01)
stateGIS <- ms_simplify(countyGIS, keep = 0.01)
countyGIS
<- countyGIS %>%
countyGIS select(FIPS = GEOID,
stFIPS = STATEFP,
coFIPS = COUNTYFP,
coNAME = NAME.x,
pop = estimate,
geometry)
## For mapping, let's drop the following:
## Puerto Rico (ST FIPS 72) (no election data)
## Alaska (ST FIPS 02) (voting data isn't reported by county...we could also map the legislative districts, but we're not going to since we'd rather have smaller maps without those extra details)
## Hawaii (ST FIPS 15) (so our map can zoom in on continental 48 states)
<- countyGIS %>% filter(stFIPS != "72" & stFIPS != "02" & stFIPS != "15")
countyGIS <- stateGIS %>% filter(GEOID != "72" & GEOID != "02" & GEOID != "15")
stateGIS
## join 2-character state abbreviation and create name = "county, St" for labeling maps (e.g., Outagamie, WI)
<- fips_codes %>% select(stFIPS = state_code, stNAME = state) %>% unique()
fipsToSTcode
<- inner_join(countyGIS,fipsToSTcode,by="stFIPS")
countyGIS
<- countyGIS %>% mutate(name = paste0(coNAME,", ", stNAME))
countyGIS
## NOTE: If you don't use keep_geo_vars = TRUE, you don't get separate STATEFP and COUNTYFP, but you can use mutate() and create stFIPS = substr(GEOID,1,2) and coFIPS = substr(GEOID,3,5)
7.0.2 Election Data
Now we’re going to download 2020 county-level election results from a GitHub repo. You can read more about the data in the repo.
## 2020 Election data
<- read_csv("https://raw.githubusercontent.com/tonmcg/US_County_Level_Election_Results_08-20/master/2020_US_County_Level_Presidential_Results.csv")
dta2020
## Calculate percentages based on total votes for Trump and Biden (GOP and Dem) only
## In some years there have been ties, so we're allowing for that
## stdVotes and stdVotesLog will be used to scale color opacity from 0 to 1 based on total votes
<- dta2020 %>%
dta2020 mutate(pctGOP = votes_gop/(votes_gop + votes_dem),
totalVotes = votes_gop + votes_dem,
winner = ifelse(dta2020$votes_gop > dta2020$votes_dem,"Trump",
ifelse(dta2020$votes_gop < dta2020$votes_dem,"Biden",
"Tie")),
pctWinner = ifelse(dta2020$votes_gop > dta2020$votes_dem,pctGOP,1-pctGOP),
FontColorWinner = ifelse(dta2020$votes_gop > dta2020$votes_dem,"red",
ifelse(dta2020$votes_gop < dta2020$votes_dem,"blue",
"purple")),
pctGOPcategories = ifelse(pctGOP<0.48,"0-48%",
ifelse(pctGOP<0.5,"48-50%",
ifelse(pctGOP<0.52, "50-52%",
"52-100%"))),
stdVotes = (totalVotes-min(totalVotes))/(max(totalVotes)-min(totalVotes)),
stdVotesLog = (log(totalVotes)-min(log(totalVotes)))/(max(log(totalVotes))-min(log(totalVotes)))
)
<- dta2020 %>%
dta2020 select(FIPS = county_fips, pctGOP, totalVotes, winner, pctWinner, pctGOPcategories, FontColorWinner, stdVotes, stdVotesLog)
## merge GIS data with voting data using FIPS code
<- left_join(countyGIS,dta2020,by="FIPS") countyGISelection
7.0.3 Creating Pop-Up Labels
# county name (zip code)
# winner of county in the color of the winner and percentage of votes won
# total votes in county
<- paste0("<b>",countyGISelection$name," (",countyGISelection$FIPS,")</b>",
popupLabels "<br><font color='",countyGISelection$FontColorWinner,"'>",countyGISelection$winner,
": ",
format(countyGISelection$pctWinner*100,digits=4, trim=TRUE),
"%</font>",
"<br>Total votes: ", format(countyGISelection$totalVotes,big.mark=",", trim=TRUE)) %>%
lapply(htmltools::HTML)
7.0.4 Creating Graphic
## create color palette used by map
# "n =" is the number of bins
# we can either have evenly spaced bins or use n = c(0,0.2,0.3,.4,.45,.49,.51,.55,.6,0.7,0.8, 1) to create custom spacing (can input own numbers obviously)
<- colorBin("RdBu", countyGISelection$pctGOP, n = , reverse=TRUE) pal
One of the arguments of leaflet()
is leafletOptions(crsClass = )
. CRS stands for coordinate reference system, a term used by geographers to explain what the coordinates mean in a coordinate vector. L.CRS.EPSG3857 is the most common and should be the default.
# leaflet(dataset, leafletOptions(crsClass = ), width = "width you want graphic%") <- usually just do 100% unless you need it to be bigger/smaller
leaflet(countyGISelection, options = leafletOptions(crsClass = "L.CRS.EPSG3857"), width="100%") %>%
# add the outlines of the county, can specify thickness (weight =), color, and opacity
addPolygons(weight = 0.9, color = "white", opacity = 0.2,
# add in the fill color for the counties that you already created along with the variable that it represents
fillColor = ~pal(pctGOP), fillOpacity = 1, smoothFactor = 0.5,
#add popupLabels
label = popupLabels,
labelOptions = labelOptions(direction = "auto")) %>%
#add state borders
addPolygons(data = stateGIS,fill = FALSE,color="black",weight = 1) %>%
#add legend
addLegend(pal = pal,values = ~countyGISelection$pctGOP, opacity = 0.7, title = "% Trump",position = "bottomright")
## Warning: sf layer has inconsistent datum (+proj=longlat +datum=NAD83 +no_defs).
## Need '+proj=longlat +datum=WGS84'
## Warning: sf layer has inconsistent datum (+proj=longlat +datum=NAD83 +no_defs).
## Need '+proj=longlat +datum=WGS84'
Instead of bins you can use a continuous scale. The map below uses a continuous scale with 2 colors. The two colors, red and blue, become become purple in the middle where they mix. You could also do a 3 color scale and add another color, like white, in between red and blue. Instead of purple, you would have white where the votes are pretty evenly split.
<- colorNumeric(
pal palette = colorRampPalette(c('red', 'blue'))(length(countyGISelection$pctGOP)),
domain = countyGISelection$pctGOP, reverse=TRUE)
leaflet(countyGISelection, options = leafletOptions(crsClass = "L.CRS.EPSG3857"), width="100%") %>%
addPolygons(weight = 0.5, color = "gray", opacity = 0.7,
fillColor = ~pal(pctGOP), fillOpacity = 1, smoothFactor = 0.5,
label = popupLabels,
labelOptions = labelOptions(direction = "auto")) %>%
addPolygons(data = stateGIS,fill = FALSE,color="black",weight = 1) %>%
addLegend(pal = pal,values = ~countyGISelection$pctGOP, opacity = 0.7, title = "% Trump",position = "bottomright")
This map uses 4 custom-labeled factors. This map is similar to what you often see on the news, displaying either red or blue. However, this one highlights close counties in slightly different colors. As you can see, there aren’t very many close counties (something that’s impossible to see on a two-color map).
<- colorNumeric(
pal palette = colorRampPalette(c('red', 'white', 'blue'))(length(countyGISelection$pctGOP)),
domain = countyGISelection$pctGOP, reverse=TRUE)
<- colorFactor(c("blue", "cyan", "magenta", "red"), countyGISelection$pctGOPcategories )
factorPal
leaflet(countyGISelection, options = leafletOptions(crsClass = "L.CRS.EPSG3857"), width="100%") %>%
addPolygons(weight = 0.5, color = "gray", opacity = 0.7,
fillColor = ~factorPal(pctGOPcategories), fillOpacity = 1, smoothFactor = 0.5,
label = popupLabels,
labelOptions = labelOptions(direction = "auto")) %>%
addPolygons(data = stateGIS,fill = FALSE,color="black",weight = 1) %>%
addLegend(pal = factorPal,values = ~countyGISelection$pctGOPcategories, opacity = 0.7, title = "% Trump",position = "bottomright")
7.0.5 Median Income
Now we’ll create a map that shows the median income of each county. First we need to pull in the data from the census data.
<- get_acs(geography = "county",
countyincome variables = "B07411_001",
keep_geo_vars = TRUE)
## Getting data from the 2017-2021 5-year ACS
<- countyincome %>%
countyincome select(FIPS = GEOID,
COUNTY = NAME,
median_income = estimate)
countyincome
## # A tibble: 3,221 × 3
## FIPS COUNTY median_income
## <chr> <chr> <dbl>
## 1 01001 Autauga County, Alabama 29733
## 2 01003 Baldwin County, Alabama 31323
## 3 01005 Barbour County, Alabama 19813
## 4 01007 Bibb County, Alabama 23520
## 5 01009 Blount County, Alabama 28027
## 6 01011 Bullock County, Alabama 21580
## 7 01013 Butler County, Alabama 22335
## 8 01015 Calhoun County, Alabama 25835
## 9 01017 Chambers County, Alabama 23855
## 10 01019 Cherokee County, Alabama 25537
## # … with 3,211 more rows
<- stateGISsave
stateGIS <- countyGISsave
countyGIS
## Simplify GIS data to make file sizes smaller. This essentially removes some details along coastlines and very-not-straight borders.
<- ms_simplify(stateGIS, keep = 0.01)
stateGIS <- ms_simplify(countyGIS, keep = 0.01)
countyGIS
<- countyGIS %>%
countyGIS select(FIPS = GEOID,
stFIPS = STATEFP,
coFIPS = COUNTYFP,
coNAME = NAME.x,
pop = estimate,
geometry)
## For mapping, let's drop the following:
## Puerto Rico (ST FIPS 72) (no election data)
## Alaska (ST FIPS 02) (voting data isn't reported by county...we could also map the legislative districts, but we're not going to since we'd rather have smaller maps without those extra details)
## Hawaii (ST FIPS 15) (so our map can zoom in on continental 48 states)
<- countyGIS %>% filter(stFIPS != "72" & stFIPS != "02" & stFIPS != "15")
countyGIS <- stateGIS %>% filter(GEOID != "72" & GEOID != "02" & GEOID != "15")
stateGIS
## join 2-character state abbreviation and create name = "county, St" for labeling maps (e.g., Outagamie, WI)
<- fips_codes %>% select(stFIPS = state_code, stNAME = state) %>% unique()
fipsToSTcode
<- inner_join(countyGIS,fipsToSTcode,by="stFIPS")
countyGIS
<- countyGIS %>% mutate(name = paste0(coNAME,", ", stNAME))
countyGIS
## NOTE: If you don't use keep_geo_vars = TRUE, you don't get separate STATEFP and COUNTYFP, but you can use mutate() and create stFIPS = substr(GEOID,1,2) and coFIPS = substr(GEOID,3,5)
<- left_join(countyGIS,countyincome,by="FIPS")
countyGISincome head(countyGISincome)
## Simple feature collection with 6 features and 9 fields
## Geometry type: MULTIPOLYGON
## Dimension: XY
## Bounding box: xmin: -111.6345 ymin: 39.04326 xmax: -91.5291 ymax: 45.61104
## Geodetic CRS: NAD83
## FIPS stFIPS coFIPS coNAME pop stNAME name
## 1 20161 20 161 Riley 72602 KS Riley, KS
## 2 19159 19 159 Ringgold 4739 IA Ringgold, IA
## 3 30009 30 009 Carbon 10488 MT Carbon, MT
## 4 16007 16 007 Bear Lake 6327 ID Bear Lake, ID
## 5 55011 55 011 Buffalo 13314 WI Buffalo, WI
## 6 31185 31 185 York 14164 NE York, NE
## COUNTY median_income geometry
## 1 Riley County, Kansas 26604 MULTIPOLYGON (((-96.95872 3...
## 2 Ringgold County, Iowa 28058 MULTIPOLYGON (((-94.47078 4...
## 3 Carbon County, Montana 30885 MULTIPOLYGON (((-109.7987 4...
## 4 Bear Lake County, Idaho 27500 MULTIPOLYGON (((-111.0467 4...
## 5 Buffalo County, Wisconsin 35320 MULTIPOLYGON (((-91.65025 4...
## 6 York County, Nebraska 34014 MULTIPOLYGON (((-97.82629 4...
<- paste0("<b>",countyGISincome$name," (",countyGISincome$FIPS,")</b>",
popupLabels "<br>", "Median Income: ",
"$",countyGISincome$median_income) %>%
lapply(htmltools::HTML)
# leaflet(dataset, leafletOptions(crsClass = ), width = "width you want graphic%") <- usually just do 100% unless you need it to be bigger/smaller
<- colorNumeric(
pal palette = colorRampPalette(c('green', 'red'))(length(countyGISincome$median_income)),
domain = countyGISincome$median_income, reverse=TRUE)
leaflet(countyGISincome, options = leafletOptions(crsClass = "L.CRS.EPSG3857"), width="100%") %>%
# add the outlines of the county, can specify thickness (weight =), color, and opacity
addPolygons(weight = 0.9, color = "white", opacity = 0.2,
# add in the fill color for the counties that you already created along with the variable that it represents
fillColor = ~pal(median_income), fillOpacity = 1.8, smoothFactor = 0.5,
#add popupLabels
label = popupLabels,
labelOptions = labelOptions(direction = "auto")) %>%
#add state borders
addPolygons(data = stateGIS,fill = FALSE,color="black",weight = 1) %>%
#add legend
addLegend(pal = pal,values = ~countyGISincome$median_income, opacity = 0.7, title = "Median Income (2020$)",position = "bottomright")
## Warning: sf layer has inconsistent datum (+proj=longlat +datum=NAD83 +no_defs).
## Need '+proj=longlat +datum=WGS84'
## Warning: sf layer has inconsistent datum (+proj=longlat +datum=NAD83 +no_defs).
## Need '+proj=longlat +datum=WGS84'
<- get_acs(geography = "tract",
medianrent2bed variables = "B25031_004",
keep_geo_vars = TRUE)
## Getting data from the 2017-2021 5-year ACS
ERROR:: Your API call has errors. The API message returned is error: unknown/unsupported geography heirarchy.