Visualizing US Continental growth from 1783 - today
On September 3, 1783, the United States and Great Britain signed the Treaty of Paris. The Treaty ended the American Revolution, with Great Britain recognizing the United States as an independent nation. While the United States has existed for less than 250 years, its political boundaries have increased over 360%. We can use R to animate the growth of US borders over time.
To present the borders of the US over time, we can use shapefiles to store geospatial information that can be plotted and modified. Luckily for us, the Newberry Library stores an atlas of the historical county boundaries of the US. You can either download the files on their website or use their package USAboundaries
for direct access.
install.packages("USAboundaries")
## Installing package into '/home/badbayesian/R/x86_64-pc-linux-gnu-library/4.0'
## (as 'lib' is unspecified)
# Or
# devtools::install_github("ropensci/USAboundaries")
# devtools::install_github("ropensci/USAboundariesData")
This post will focus on just the continental US. With a simple lapply
call (or map
in other languages), we can download the US’ state boundaries for every year in the data set.
library(USAboundaries)
library(dplyr)
library(sf)
continental_us <- state_codes$state_name[-c(2, 12, 52:69)]
dates <- seq(as.Date("1783-09-03"), as.Date("2000-12-31"), by = "years")
maps <- lapply(dates, function(date) {
map <- us_states(map_date = date, resolution = "high", states = continental_us)
map$year <- date
map$states <- TRUE
map
}) %>%
bind_rows()
The final object, maps
, is a spatial dataframe allowing for all operations of both dataframes and spatial objects. In our case, each row corresponds to information of a state at some point in time. The geometry column stores the polygon which defines the boundaries.
head(maps)
## Simple feature collection with 6 features and 20 fields
## geometry type: MULTIPOLYGON
## dimension: XY
## bbox: xmin: -91.65403 ymin: 30.35712 xmax: -66.94993 ymax: 47.4597
## geographic CRS: WGS 84
## id_num name id version start_date end_date
## 1 22 Connecticut ct_state 1 1783-09-03 1786-09-13
## 2 28 Delaware de_state 1 1783-09-03 2000-12-31
## 3 42 Georgia ga_state 1 1783-09-03 1798-04-06
## 4 76 Massachusetts ma_state 1 1783-09-03 1804-12-30
## 5 81 Maryland md_state 1 1783-09-03 1791-03-29
## 6 116 North Carolina nc_state 1 1783-09-03 1790-04-01
## change
## 1 Connecticut became an independent state on 4 July 1776. The map depicts state boundaries as of 3 September 1783.
## 2 The three Lower Counties, of KENT, NEW CASTLE, and SUSSEX became an independent state on 4 July 1776. The name Delaware was formally adopted on 20 September 1776. The map depicts state boundaries as of 3 September 1783.
## 3 Georgia became an independent state on 4 July 1776. The map depicts state boundaries as of 3 September 1783.
## 4 Massachusetts, including the area of Maine, became an independent state on 4 July 1776. The map depicts state boundaries as of 3 September 1783.
## 5 Maryland became an independent state on 4 July 1776. The map depicts state boundaries as of 3 September 1783.
## 6 North Carolina became an independent state on 4 July 1776. The map depicts state boundaries as of 3 September 1783.
## citation start_n end_n area_sqmi
## 1 (Declaration of Independence) 17830903 17860913 4982
## 2 (Declaration of Independence; Swindler, 2:197) 17830903 20001231 2013
## 3 (Declaration of Independence) 17830903 17980406 152080
## 4 (Declaration of Independence) 17830903 18041230 40682
## 5 (Declaration of Independence) 17830903 17910329 10065
## 6 (Declaration of Independence) 17830903 17900401 91507
## terr_type full_name abbr_name name_start state_abbr
## 1 State Connecticut CT Connecticut (1783-09-03) CT
## 2 State Delaware DE Delaware (1783-09-03) DE
## 3 State Georgia GA Georgia (1783-09-03) GA
## 4 State Massachusetts MA Massachusetts (1783-09-03) MA
## 5 State Maryland MD Maryland (1783-09-03) MD
## 6 State North Carolina NC North Carolina (1783-09-03) NC
## state_name state_code year states geometry
## 1 Connecticut 09 1783-09-03 TRUE MULTIPOLYGON (((-73.5055 41...
## 2 Delaware 10 1783-09-03 TRUE MULTIPOLYGON (((-75.07024 3...
## 3 Georgia 13 1783-09-03 TRUE MULTIPOLYGON (((-81.50318 3...
## 4 Massachusetts 25 1783-09-03 TRUE MULTIPOLYGON (((-70.81094 4...
## 5 Maryland 24 1783-09-03 TRUE MULTIPOLYGON (((-76.01758 3...
## 6 North Carolina 37 1783-09-03 TRUE MULTIPOLYGON (((-78.52302 3...
Before plotting our maps, there is one last important concept – projection. A projection is a method of mapping a 3D object (the Earth) onto a 2D sheet (a map). Regardless of the projection used, the image is unfortunately distorted in some manner. Usually, projections trade-off distortions between scale (or size) with shape. Projections may also create uneven distortions where some areas on the map have little distortion while others are heavily distorted. Many American maps use the Albers projection, as the distortions are minimal for the US area.
Plotting maps with spatial dataframes is easy with ggplot!
With geom_sf()
, plotting maps in ggplot is identical to using regular ggplot.
library(ggplot2)
albers_projection <- paste0(
"+proj=aea +lat_1=29.5 +lat_2=45.5 +lat_0=37.5 ",
"+lon_0=-96 +x_0=0 +y_0=0 +ellps=GRS80 ",
"+datum=NAD83 +units=m +no_defs"
)
filter(maps, year=="1784-09-03") %>%
ggplot() +
geom_sf(aes(fill = name)) +
coord_sf(crs = albers_projection) +
theme_bw() +
labs(title = "United States (1784)",
fill = "States",
caption = "Thirteen Colonies + Vermont Republic")
Animating the map
Since we stored our data in a geospatial dataframe, we can filter on year to animate the plots. The filtering happens with transition_manual(year)
, which creates a new frame for the animation for each different year. With {frame}
you can reference the frame if you want to change text to reflect the year of the map. For example the function, historical_events(frame)
adds a subtitle with some historical events for that year. gganimate
lazily builds the animation i.e. does not create the animation until animated_map
or animate()
is called. Depending on the complexity of the shapefile and the strength of your computer, generating the animation may take a couple of minutes.
library(gganimate)
library(ggspatial)
historical_events <- function(frame) {
year <- 1783 + (frame - 1)
event <- case_when(
year <= 1803 ~ "Organization of territory after Revolutionary War",
year <= 1819 ~ "Purchase of Louisiana and Spanish Cession",
year <= 1845 ~ "Northwest expansion (inc. Trail of Tears)",
year <= 1861 ~ "Mexican-American War and Southwest expansion",
year <= 1865 ~ "Civil War",
year <= 1897 ~ "Reconstruction and western statehood",
year <= 1945 ~ "Pacific and Caribbean expansion (not included)",
year <= 2000 ~ "Present day"
)
return(event)
}
## Full size US
usa <- filter(maps, year == "2000-9-3")
usa$geometry <- st_union(usa)
usa <- slice(usa, 1)
usa[,-length(colnames(usa))] = NA
usa[nrow(usa)+length(dates)-1,] <- NA
usa$year <- dates
usa$geometry <- usa$geometry[1]
usa$states <- FALSE
maps <- bind_rows(maps, usa)
animated_map <- ggplot(maps) +
geom_sf(aes(color = states, fill = terr_type)) +
coord_sf(crs = albers_projection) +
theme_bw() +
transition_manual(year) +
labs(
title = "United States ({1783 + (frame - 1)})",
subtitle = "{historical_events(frame)}",
caption = paste(
"Continental US (48 states); Data from",
"Newberry Library’s Atlas of Historical County Boundaries,"
)
) +
annotation_north_arrow(
location = "bl", which_north = "true",
pad_x = unit(0.75, "in"), pad_y = unit(0.5, "in"),
style = north_arrow_fancy_orienteering
) +
annotation_scale(location = "bl", width_hint = 0.5) +
scale_color_manual(values = c("grey", "black")) +
guides(fill = guide_legend(title = "Territory Type"),
color = FALSE)
gif <- animate(animated_map, renderer = gifski_renderer(),
nframes = length(dates), duration = 75)
anim_save("us_expansion.gif", gif)