Making maps in R

2017/10/10

I sometimes want to show maps, usually political maps, with some sections highlighted. For example, I recently wanted to show a map of Europe with France and the Netherlands highlighted, and I wanted to crop the map closely.

It turns out that ggplot has some commands that interact with the maps package. Start by installing both of those, and we’ll load up the world map:

library(dplyr)
library(ggplot2)
world = map_data('world')
world %>% head(3) %>% kable
long lat group order region subregion
-69.89912 12.45200 1 1 Aruba NA
-69.89571 12.42300 1 2 Aruba NA
-69.94219 12.43853 1 3 Aruba NA

The subregion is for when a country has multiple, non-contiguous parts. For example, the UK has its England-Scotland-Wales mainland as well as a bunch of islands:

world %>% filter(region=='UK') %>%
  head(3) %>% kable
long lat group order region subregion
-1.065576 50.69024 570 40057 UK Isle of Wight
-1.149365 50.65571 570 40058 UK Isle of Wight
-1.175830 50.61523 570 40059 UK Isle of Wight

You can just plot this data straight up with ggplot:

world %>%
  ggplot(aes(x=long, y=lat, group=group)) +
  geom_polygon(fill=NA, color='black')

But I only wanted Europe. Filtering out points outside this region leads to bad effects, because it messes up the polygons. Check out what happens to Morocco near Gibraltar:

world %>%
  filter(between(long, -10, 25),
         between(lat, 35, 65)) %>%
  ggplot(aes(x=long, y=lat, group=group)) +
  geom_polygon(fill=NA, color='black')

One sensible approach would be to change the axes so that all the points are drawn and we just hide some of them. It turns out that xlim and ylim (and setting limits as an option to scale_x_continuous) also do weird cropping. Check out Ireland and Morocco:

world %>%
  ggplot(aes(x=long, y=lat, group=group)) +
  geom_polygon(fill=NA, color='black') +
  xlim(-10, 25) + ylim(35, 65)

To do good cropping, it has to be inside a coord function. Incidentally, I’ll use this as an opportunity to fix the aspect ratio:

world %>%
  ggplot(aes(x=long, y=lat, group=group)) +
  geom_polygon(fill=NA, color='black') +
  coord_fixed(xlim=c(-10, 25),
              ylim=c(35, 65),
              ratio=1.3)

This worked, but it’s not a good map projection. It becomes pretty obvious to me when looking at the US, because I’m more used to looking at that map. Things are too square, Texas looks squeezed, Florida is weird, etc.:

state = map_data('state')
state %>%
  ggplot(aes(x=long, y=lat, group=group)) +
  geom_polygon(fill=NA, color='black') +
  coord_fixed(ratio=1.3)

Instead, we want to use ggplot’s coord_map function, which allows all kinds of cool projections. I’m just going to use the default (Mercator), because it’s familiar:

state %>%
  ggplot(aes(x=long, y=lat, group=group)) +
  geom_polygon(fill=NA, color='black') +
  coord_map()

Aah, now that looks right! But something weird happens when I try to use this back in the Europe map:

world %>%
  ggplot(aes(x=long, y=lat, group=group)) +
  geom_polygon(fill=NA, color='black') +
  coord_map(xlim=c(-10, 25),
            ylim=c(35, 65))

I think this could be considered a bug in how coord_map works. The solution I found is to explicity clip the country shapes to the size of the map, which requires a polygons manipulation package:

library(PBSmapping)
world %>%
  # the PBSmapping package expects a data frame with these names
  rename(X=long, Y=lat, PID=group, POS=order) %>%
  clipPolys(xlim=c(-10, 25), ylim=c(35, 65)) %>%
  ggplot(aes(x=X, y=Y, group=PID)) +
  geom_polygon(fill=NA, color='black') +
  coord_map()

And so finally I could make the map I really wanted. Note the keepExtra option in clipPolys, which ensures that the region column isn’t throw out:

world %>%
  rename(X=long, Y=lat, PID=group, POS=order) %>%
  clipPolys(xlim=c(-10, 25), ylim=c(35, 65), keepExtra=TRUE) %>%
  mutate(highlight=region %in% c('France', 'Netherlands')) %>%
  ggplot(aes(x=X, y=Y, group=PID, fill=highlight)) +
  geom_polygon(color='black') +
  coord_map() +
  scale_fill_manual(values=c('floralwhite', 'gray')) +
  theme_classic()

There’s a bunch of stuff you can do with these maps: highlighting, adding points and lines, etc. This post has some good examples. Happy mapping!