ggplot: Overlaying Points (geom_point) on Polygons (geom_poly)

Saturday, Feb 23, 2019| Tags: rgdal, r, ggplot, map, shapefile

Goal

The goal of this post is to demonstrate how to overlay geographic points onto geographic polygons. Specifically, I’ll demonstrate how map the location of Chicago Divvy bike stations (points) onto the Chicago community map (polygons from a shapefile).

Data

Here is the data I used:

Example

library(rgdal)
library(readr)
library(broom)
library(RColorBrewer)
library(ggplot2)
library(dplyr)
theme_set(theme_void())

To read the shapefile, I use readOGR {rgdal}.

NOTE: I extracted the .dbf, .prj, and .shx files to the data directory.

comm <- readOGR("./data/.")

Here is the code to create a plot of the Chicago communities:

# transform the shapefile data to a data.frame
comm.points <- tidy(comm)

# create a color palette, specifically a data.frame
# that maps a different color to each community id
colorCount = length(unique(comm.points$id))
getPalette = colorRampPalette(brewer.pal(9, "Set2"))
colors <- data.frame(
  id = unique(comm.points$id), 
  color = sample(getPalette(colorCount), colorCount))

# join colors to community ids
comm.points <- inner_join(comm.points, colors, by = "id")

# finally, plot the communities
ggplot(comm.points, aes(long, lat, group = group, fill = color)) + 
  geom_polygon(alpha = 0.5) +
  geom_path(color = "white") +
  coord_equal() +
  scale_fill_identity() +
  theme(axis.title = element_blank(), 
        legend.position = "none")

The tidy {broom} call simply extracts the polygon points into a data.frame. I had to add the special getPalette code because there were 77 different community ids and I wanted them to each have a separate color. So I created the colors data.frame to serve as a “lookup table” for each community’s color, which I used in the ggplot code.

Now, bring in the Divvy station data.

divvy <- read_csv("./data/Divvy_Bicycle_Stations.csv")

Now the tricky part. Plot the station locations on top of the community boundary map.

ggplot(comm.points, aes(long, lat, group = group, fill = color)) + 
  geom_polygon(alpha = 0.5) +
  geom_path(color = "white") +
  coord_equal() +
  scale_fill_identity() +
  theme(legend.position = "none") +
  geom_point(data=divvy, aes(Longitude, Latitude), inherit.aes = FALSE, alpha = 0.5, size = 0.5) + coord_equal()

It was important to tell geom_point not to inherit the aesthetics defined in the base layer. This is because geom_poly needed the fill and color aesthetics defined, but there was no grouping or color in the divvy data.

The coordinate_equal keeps the plot proportioned.

And, that’s it!

QUESTIONS?

I'd be happy to sit down with you for a free consultation.

Contact me