Spatial Data Mapping
- Geographic Maps
- Interlocking Marks
- Coded attributes, unavailable for coding additional information
- Thematic Maps
- Attributes (themes) have spatial variability
- geographic / reference map & tabular data
- combines
- region: interlocking marks (could also be point makrs, eg: [lat lon])
- region: categorical attributes in tabular data (for looking up attributes)
- Major idioms
- Choropleth
- Symbol maps
- Cartograms
Choropleth US States’ Population
- Given geometry as area mark boundaries
- Quantitative attribute as coloring value (sequential segmented color below)
#install.packages(c("usmap", "ggplot2", "dplyr", "cartogram", "geofacet", "sf", "tigris"))
library(usmap)
library(ggplot2)
library(dplyr)
data("statepop")
head(statepop) #take a look at first 5 rows
#draw choropleth map
plot_usmap(data = statepop, values = "pop_2022", regions = "states") +
scale_fill_continuous(
low = "white", high = "blue", name = "Population (2022)", label = scales::comma
) +
theme(legend.position = "right")

However…
- Spurious correlations: maps only show how many people in each state
- Think about when to normalize population density
- The conflicts of:
- Absolute counts vs. relative/normalized value
Symbol map
- Represent aggregated data
- Size and shape as visual cue
- Proportional symbolmaps / graduated symbol maps
- Good alternative to choropleth maps
library(usmap)
library(dplyr)
library(ggplot2)
library(sf)
# Load population data
data("statepop")
# Calculate centroids for symbol locating
us_states <- us_map(regions = "states")
# Compute centroids for each state
state_centroids <- us_states %>%
st_as_sf() %>%
st_centroid() %>%
st_coordinates() %>%
as.data.frame() %>%
rename(long = X, lat = Y) %>%
bind_cols(us_states %>% select(abbr))
# Join population data
state_data <- statepop %>%
left_join(state_centroids, by = c("abbr"))
# Plot symbol map
plot_usmap(regions = "states") +
geom_point(data = state_data, aes(x = long, y = lat, size = pop_2022),
color = "red", alpha = 0.7) +
scale_size_continuous(name = "Population (2022)", range = c(1, 12), labels = scales::comma) +
labs(title = "US State Population (2022)", subtitle = "Dot size represents population") +
theme(legend.position = "right")

Contiguous Cartogram
- Update old interlocking marks with new quantitative attributes
- Change the size of each state based on population while maintain neighboring boundaries
# Load libraries
library(cartogram)
library(sf)
library(tigris)
# Get US states shape data (simplified)
us_states <- states(cb = TRUE, resolution = "20m", year = 2022) %>%
filter(!STUSPS %in% c("PR", "GU", "VI", "AS", "MP", "AK", "HI")) %>% # Exclude territories
st_transform(crs = 5070) # Project to Albers Equal Area for US
# Load state population data
state_pop <- data.frame(
state = datasets::state.name,
STUSPS = datasets::state.abb,
population = datasets::state.x77[, "Population"]
)
# Join shape data with population
us_states_pop <- us_states %>%
left_join(state_pop, by = "STUSPS") %>%
na.omit()
# Generate contiguous cartogram based on population
us_cartogram <- cartogram_cont(us_states_pop, weight = "population", itermax = 7)
# Plot the cartogram with ggplot
ggplot(us_cartogram) +
geom_sf(aes(fill = population), color = "white", size = 0.3) +
scale_fill_gradient(low = "pink", high = "red", name = "Population",
labels = scales::comma) +
labs(title = "US Contiguous Cartogram by State Population",
subtitle = "State size and color correspond to population") +
theme_void() +
theme(legend.position = "right")

Now this map…
# Load libraries
library(cartogram)
library(sf)
library(tigris)
library(ggplot2)
library(dplyr)
# Load US state boundaries
us_states <- states(cb = TRUE, resolution = "20m", year = 2022) %>%
filter(!STUSPS %in% c("PR", "GU", "VI", "AS", "MP", "AK", "HI")) %>% # exclude territories
st_transform(crs = 5070)
# Load state population data
state_pop <- data.frame(
state = datasets::state.name,
STUSPS = datasets::state.abb,
population = datasets::state.x77[, "Population"]
)
# Join data
us_states_pop <- us_states %>%
left_join(state_pop, by = "STUSPS") %>%
na.omit()
# Create non-contiguous cartogram
us_noncont_cartogram <- cartogram_ncont(us_states_pop, weight = "population")
# Plot non-contiguous cartogram
ggplot(us_noncont_cartogram) +
geom_sf(aes(fill = population), color = "white", size = 0.3) +
scale_fill_gradient(low = "pink", high = "red", name = "Population", labels = scales::comma) +
labs(title = "US Non-Contiguous Cartogram by Population",
subtitle = "State size and color represent population") +
theme_void() +
theme(legend.position = "right")

Grid cartogram
# Load libraries
library(geofacet)
library(ggplot2)
library(dplyr)
# Prepare population data
state_pop <- data.frame(
state = datasets::state.name,
state_abb = datasets::state.abb,
population = datasets::state.x77[, "Population"]
)
# Merge data with geofacet grid
state_pop_grid <- state_pop %>%
left_join(us_state_grid1, by = c("state_abb" = "code"))
# Draw the grid cartogram
ggplot(state_pop_grid, aes(x = col, y = -row)) +
geom_tile(aes(fill = population), color = "white") +
geom_text(aes(label = state_abb), color = "black", size = 3) +
scale_fill_gradient(low = "pink", high = "red", name = "Population", labels = scales::comma) +
coord_equal() +
labs(title = "US Grid Cartogram by Population",
subtitle = "Grid colored by population with state abbreviations") +
theme_void() +
theme(legend.position = "right",
plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
plot.subtitle = element_text(hjust = 0.5, size = 12))

What are the pros / cons
- How easy to read them?
- How clear the info is delivered?
- Intuitive or not?
- Suitable for what topic?