Skip to content

Commit 47e8818

Browse files
author
Christopher Prener
committed
add lesson materials
1 parent 2f51704 commit 47e8818

2 files changed

Lines changed: 123 additions & 149 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ This repository contains the third lesson for our series on GIS work in `R`. We'
1010
### Objectives
1111
At the end of this lesson, participants should be able to:
1212

13-
1.
13+
1. Compose map static layouts using `ggplot2` and `tmap`
1414

1515
### Lesson Resources
1616
* The [`notebook/`](/notebook) directory contains the materials for this lesson.
@@ -26,7 +26,7 @@ If you participated in the first session, there is no new software for today. Ho
2626
```r
2727
install.packages(c("tidyverse", "here", "knitr",
2828
"mapview", "rmarkdown", "sf",
29-
"tmap", "usethis"))
29+
"tigris", "tmap", "usethis"))
3030
```
3131

3232
### Access Lesson

notebook/gis-03-complete.Rmd

Lines changed: 121 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,21 @@ output:
88
---
99

1010
## Introduction
11-
This notebook extends our `leaflet` experience to include both multi-layer maps as well as thematic choropleth maps.
11+
This notebook expands mapping to two new packages - `ggplot2` and `tmap`. We'll focus on thematic choropleth mapping today.
1212

1313
## Dependencies
1414
This notebook requires a variety of packages for working with spatial data:
1515

1616
```{r load-packages}
1717
# tidyverse packages
1818
library(dplyr) # data wrangling
19+
library(ggplot2) # plotting
1920
2021
# spatial packages
21-
library(leaflet) # interactive maps
2222
library(mapview) # preview spatial data
2323
library(sf) # spatial data tools
24-
library(tigris) # TIGER/Line data access
24+
library(tigris) # access TIGER/Line shapefile data
25+
library(tmap) # mapping
2526
2627
# other packages
2728
library(here) # file path management
@@ -32,14 +33,6 @@ library(RColorBrewer) # color palettes
3233
First, we need to load up our data. This is review from the first session, and so we've pre-filled the code for you. We'll automatically re-project the data as well (also review):
3334

3435
```{r load-data}
35-
# point data 1 - Violent Crime in the Shaw Neighborhood
36-
shawCrime <- st_read(here("data", "SHAW_Violent_2018", "SHAW_Violent_2018.shp")) %>%
37-
st_transform(crs = 4326)
38-
39-
# point data 2 - Food Retail in St. Louis
40-
grocery <- st_read(here("data", "STL_FOOD_Grocery.geojson"))
41-
cornerStore <- st_read(here("data", "STL_FOOD_Convenience.geojson"))
42-
4336
# polygon data 1 - St. Louis Neighborhood Population
4437
nhood <- st_read(here("data", "STL_DEMOS_Nhoods", "STL_DEMOS_Nhoods.shp")) %>%
4538
st_transform(crs = 4326)
@@ -48,20 +41,8 @@ nhood <- st_read(here("data", "STL_DEMOS_Nhoods", "STL_DEMOS_Nhoods.shp")) %>%
4841
covid <- st_read(here("data", "daily_snapshot_regional.geojson"))
4942
```
5043

51-
Now, take a few moments using your *console* and explore each of these data sets using the `mapview()` function!
52-
5344
## Download a Bit of Extra Data
54-
We'll supplement the data we've loaded with some boundary data for a four county region in the St. Louis area (the City, the County, St. Charles County, and Jefferson County).
55-
56-
We'll use the `tigris` package to do this. Using `tigris`, you can download most of the common spatial data sets published by the U.S. Census Bureau that reflect both administrative boundaries as well as elements of our physical and human geography. However, this package cannot be used to download population data (that is the purpose of `tidycensus`).
57-
58-
The pipelines read as follows:
59-
60-
1. We'll assign to our new object `region` the result of the following pipeline.
61-
2. First, we'll download county boundaries for Missouri (FIPS Code 29), *then*
62-
3. We'll select the `GEOID` and `NAMELSAD` columns using the `dplyr` `select()` function, *then*
63-
4. We'll keep only the observations that match the GEOID codes for the select counties using the `dplyr` `filter()` function, *then*
64-
5. We'll re-project our data.
45+
We'll use the same process as last week to create boundary data for both the city and the region:
6546

6647
```{r load-counties}
6748
region <- counties(state = 29) %>%
@@ -72,136 +53,129 @@ region <- counties(state = 29) %>%
7253
city <- filter(region, GEOID == "29510")
7354
```
7455

75-
## More with Point Data
76-
Last session, we made some simple point maps. Today, we want to extend those skills by mapping multiple point layers. We're going to start with the Shaw crime data and build a map together. First, we need to separate out two categories of crimes - homicides and aggravated assaults. We'll use the `dplyr` `filter()` function to do this:
56+
## Simple Maps with `ggplot2`
57+
### Basic Mapping of Geometric Objects
58+
`ggplot2` is the premier graphics package for `R`. It is an incredibly powerful visualization tool that increasingly supports spatial work and mapping. The basic `ggplot2` workflow requires chaining together functions with the `+` sign.
59+
60+
We'll start by creating a `ggplot2` object with the `ggplot()` function, and then adding a "geom", which provides `ggplot2` instructions on how our data should be visualized. We can read these like paragraphs:
61+
62+
1. First, we create an empty `ggplot2` object, **then**
63+
2. we add the `nhood` data and visualize its geometry.
64+
65+
```{r ggplot2-nhoodSimple}
66+
ggplot() +
67+
geom_sf(data = nhood, fill = "#bababa")
68+
```
69+
70+
You can see empty spaces where there are major parks - if we wanted to give these a background color, we could add the `city` layer under our `nhood` layer. We can also add the `city` layer again on top to give the city border a pronounced outline. `ggplot2` relies on layering different geoms to produce complicated plots. We can assign each geom a specific set of aesthetic characteristics and use data from different objects.
7771

78-
```{r subset-shaw}
79-
shawHomicide <- filter(shawCrime, crimeCt == "Homicide")
80-
shawAssault <- filter(shawCrime, crimeCt == "Aggravated Assault")
72+
```{r ggplot2-nhoodSimple2}
73+
ggplot() +
74+
geom_sf(data = city, fill = "#ffffff", color = NA) +
75+
geom_sf(data = nhood, fill = "#bababa") +
76+
geom_sf(data = city, fill = NA, color = "#000000", size = .75)
8177
```
78+
Now it is your turn - re-create this process but map zip codes using the `covid` data and use the regional county boundaries in `region`:
8279

83-
Then, we'll use a number of different `leaflet` functions to build our map.
84-
85-
Within `addCircleMarkers`, we we'll use the following arguments:
86-
87-
* `radius` - the size of the marker
88-
* `opacity` - the inner part of the marker
89-
* `color` - the color of the outer part of the marker
90-
* `fillColor` - the color of the inner part of the marker
91-
92-
```{r map-crime}
93-
leaflet() %>%
94-
addProviderTiles(providers$Esri.WorldStreetMap) %>%
95-
addCircleMarkers(data = shawAssault,
96-
radius = 8,
97-
opacity = 1,
98-
color = "#ff6500",
99-
fillColor = "#ff6500",
100-
popup = paste0("<b>Crime</b>: ", shawAssault$crimeCt, "<br>",
101-
"<b>Address</b>: ", shawAssault$ILEADSA, " ",
102-
shawAssault$ILEADSS)) %>%
103-
addCircleMarkers(data = shawHomicide,
104-
radius = 8,
105-
opacity = 1,
106-
color = "#ff0000",
107-
fillColor = "#ff0000",
108-
popup = paste0("<b>Crime</b>: ", shawHomicide$crimeCt, "<br>",
109-
"<b>Address</b>: ", shawHomicide$ILEADSA, " ",
110-
shawHomicide$ILEADSS))
80+
```{r ggplot2-covidSimple}
81+
ggplot() +
82+
geom_sf(data = region, fill = "#ffffff", color = NA) +
83+
geom_sf(data = covid, fill = "#bababa") +
84+
geom_sf(data = region, fill = NA, color = "#000000", size = .75)
11185
```
11286

113-
Now it is your turn, use the same basic process to map grocery stores and corner stores in St. Louis City and St. Louis County:
114-
115-
```{r map-stores}
116-
leaflet() %>%
117-
addProviderTiles(providers$Esri.WorldStreetMap) %>%
118-
addCircleMarkers(data = cornerStore,
119-
radius = 8,
120-
opacity = 1,
121-
color = "#3b0076",
122-
fillColor = "#3b0076",
123-
popup = paste0("<b>Name</b>: ", cornerStore$title, "<br>",
124-
"<b>Category</b>: Convenience Store <br>",
125-
"<b>Address:</b><br>",
126-
cornerStore$address, "<br>",
127-
cornerStore$address2, "<br>",
128-
"<b>County</b>: ", cornerStore$county)) %>%
129-
addCircleMarkers(data = grocery,
130-
radius = 8,
131-
opacity = 1,
132-
color = "#006700",
133-
fillColor = "#006700",
134-
popup = paste0("<b>Name</b>: ", grocery$title, "<br>",
135-
"<b>Category</b>: Grocery <br>",
136-
"<b>Address:</b><br>",
137-
grocery$address, "<br>",
138-
grocery$address2, "<br>",
139-
"<b>County</b>: ", grocery$county))
87+
### Mapping Quantities with `ggplot2`
88+
If we wanted to start to map data instead of just the geometric properties, we would specify an "aesthetic mapping" using `mapping= aes()` in the geom of interest. Here, we create a fill that is the product of taking the population in 2017 and normalizing it by square kilometers as we did in the `leaflet` section above. We provide additional instructions about how our data should be colored with the `scale_fill_distiller()` function, which gives us access to the `RColorBrewer` palettes.
89+
90+
```{r ggplot2-nhoodFull}
91+
ggplot() +
92+
geom_sf(data = city, fill = "#ffffff", color = NA) +
93+
geom_sf(data = nhood, mapping = aes(fill = pop17/(AREA/1000000))) +
94+
geom_sf(data = city, fill = NA, color = "#000000", size = .75) +
95+
scale_fill_distiller(palette = "Greens", trans = "reverse", name = "Population per\nSquare Kilometer") +
96+
labs(
97+
title = "Population Density (2017)",
98+
subtitle = "Neighborhoods in St. Louis, MO",
99+
caption = "Map by Christopher Prener, Ph.D."
100+
) +
101+
theme_minimal()
140102
```
141103

142-
## Making Choropleth Maps
143-
We can also create thematic choropleth maps that map quantities using `leaflet`. We'll use this as a way to demonstrate how to overlay polygons on top of each other as well.
144-
145-
Instead of `addCircleMarkers()` or `addMarkers()`, we'll use `addPolygons()` with some of the following options:
146-
147-
* `color` - outline ("stroke") color for each polygon
148-
* `weight` - stroke width
149-
* `opacity` - stroke opacity
150-
* `smoothFactor` - allows `leaflet` to simplify polygons depending on zoom
151-
* `fillOpacity` - fill opacity
152-
* `fillColor` - creates the fill itself
153-
* `highlightOptions` - creates effect when mouse drags over specific polygons
154-
155-
```{r map-population}
156-
# create color palette
157-
npal <- colorNumeric("YlOrRd", nhood$pop17)
158-
159-
# create leaflet object
160-
leaflet() %>%
161-
addProviderTiles(providers$CartoDB.Positron) %>%
162-
addPolygons(
163-
data = nhood,
164-
color = "#444444",
165-
weight = 1,
166-
opacity = 1.0,
167-
smoothFactor = 0.5,
168-
fillOpacity = 0.5,
169-
fillColor = ~npal(pop17),
170-
highlightOptions = highlightOptions(color = "white", weight = 2, bringToFront = TRUE),
171-
popup = paste("<b>Name:</b> ", nhood$NHD_NAME, "<br>",
172-
"<b>2017 Population:</b> ", round(nhood$pop17, digits = 0))) %>%
173-
addPolylines(
174-
data = city,
175-
color = "#000000",
176-
weight = 3
177-
) %>%
178-
addLegend(pal = npal, values = nhood$pop17, opacity = .5, title = "Population (2017)")
104+
Replicate this process, using the `case_rate` column in `covid` to plot the already normalized numbers of cases per 1,000 people in each zip code:
105+
106+
```{r ggplot2-covidFull}
107+
ggplot() +
108+
geom_sf(data = region, fill = "#ffffff", color = NA) +
109+
geom_sf(data = covid, mapping = aes(fill = case_rate)) +
110+
geom_sf(data = region, fill = NA, color = "#000000", size = .75) +
111+
scale_fill_distiller(palette = "RdPu", trans = "reverse", name = "Cases Per 1,000 People") +
112+
labs(
113+
title = "Reported COVID-19 Cases",
114+
subtitle = "ZIP Codes in St. Louis, MO",
115+
caption = "Map by Christopher Prener, Ph.D."
116+
) +
117+
theme_minimal()
179118
```
180119

181-
Now it is your turn. Apply the same principles to mapping COVID cases in St. Louis:
182-
183-
```{r map-covid}
184-
# create color palette
185-
npal <- colorNumeric("RdPu", covid$cases)
186-
187-
# create leaflet object
188-
leaflet() %>%
189-
addProviderTiles(providers$CartoDB.Positron) %>%
190-
addPolygons(
191-
data = covid,
192-
color = "#444444",
193-
weight = 1,
194-
opacity = 1.0,
195-
smoothFactor = 0.5,
196-
fillOpacity = 0.5,
197-
fillColor = ~npal(cases),
198-
highlightOptions = highlightOptions(color = "white", weight = 2, bringToFront = TRUE),
199-
popup = paste("<b>Zip:</b> ", covid$GEOID_ZCTA, "<br>",
200-
"<b>Cases:</b> ", covid$cases)) %>%
201-
addPolylines(
202-
data = region,
203-
color = "#000000",
204-
weight = 3
205-
) %>%
206-
addLegend(pal = npal, values = covid$cases, opacity = .5, title = "COVID Cases")
120+
## Map Layouts with `tmap`
121+
`tmap` uses a similar logic to `ggplot2` - it layers elements on top of each other to produce maps. It is dedicated to working with spatial data, however, and has some features that `ggplot2` does not.
122+
123+
### Basic Mapping of Geometric Objects
124+
We'll start with a basic map that, like we have previously, just display the geometry of the city's neighborhoods. Similar to `ggplot2`, functions are chained together with the `+` sign. We can read these like paragraphs:
125+
126+
1. First, we take the `nhood` data, **then**
127+
2. we create our `tmap` layer out of its shape, **then**
128+
3. we add a fill using our layer, **then**
129+
4. we add borders using our layer.
130+
131+
```{r tmap-simple}
132+
nhood %>%
133+
tm_shape() +
134+
tm_fill() +
135+
tm_borders()
207136
```
137+
138+
### Mapping Quantities with `tmap`
139+
Like `ggplot2`, we can plot quantities using the `tm_polygons()` function. The `palette` argument accepts names of both `RColorBrewer` and `viridis` palettes. `tmap` also contains a number of tools for creating map layouts that "feel" more like desktop GIS outputs.
140+
141+
```{r tmap-nhoodFull}
142+
tm_shape(city) +
143+
tm_fill(fill = "#ebebeb") +
144+
tm_shape(nhood) +
145+
tm_polygons(col = "pop17",
146+
palette = "viridis",
147+
style = "jenks",
148+
convert2density = TRUE,
149+
title = "Population per\nSquare Kilometer") +
150+
tm_shape(city) +
151+
tm_borders(lwd = 2) +
152+
tm_scale_bar() +
153+
tm_layout(
154+
title = "Population Density (2017)",
155+
frame = FALSE,
156+
legend.outside = TRUE,
157+
legend.position = c("left", "bottom"))
158+
```
159+
Now, it is your turn to repeat this process. Create a map layout for the `covid` data, mapping `case_rate` again. (Hint: you won't need to set `convert2density` to `TRUE`)
160+
161+
```{r tmap-covidFull}
162+
tm_shape(region) +
163+
tm_fill(fill = "#ebebeb") +
164+
tm_shape(covid) +
165+
tm_polygons(col = "case_rate",
166+
palette = "RdPu",
167+
style = "jenks",
168+
convert2density = FALSE,
169+
title = "Cases per 1,000 Per Residents") +
170+
tm_shape(region) +
171+
tm_borders(lwd = 2) +
172+
tm_scale_bar() +
173+
tm_layout(
174+
title = "Reported COVID-19 Cases\nby ZIP Code",
175+
frame = FALSE,
176+
legend.outside = TRUE,
177+
legend.position = c("left", "bottom"))
178+
```
179+
180+
## Saving Maps
181+
There are two processes for saving maps, one for `ggplot2` and one for `tmap`. `ggplot2` uses `ggsave()` while `tmap` uses `tmap_save()`.

0 commit comments

Comments
 (0)