Combining maps and patterns with {ggplot2}

[This article was first published on Albert Rapp, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

In this post, I will show you how to combine the two cool packages {usmap} and {ggpattern} to create US maps with patterns like this:

And before we dive in, let me tell you that there’s also a video version of this blog post available. You can find it on YouTube:

Get the data

Alright, the first thing that we need to do is to get the data for our US map. We can do that with the {usmap} package. Here’s how this package works. At the core it has two functions: plot_usmap() and us_map(). The first one can – in one line of code – create a US map for you.

library(tidyverse)
library(usmap)
# US States
plot_usmap(regions = 'states')

# US Counties
plot_usmap(regions = 'counties')

The ease that this function provides is pretty incredible if you ask me. But this function is not what we need here. We want to get the underlying data. That can be done with us_map().

us_map(regions = 'states')
## Simple feature collection with 51 features and 3 fields
## Geometry type: MULTIPOLYGON
## Dimension:     XY
## Bounding box:  xmin: -2590847 ymin: -2608151 xmax: 2523583 ymax: 731405.7
## Projected CRS: NAD27 / US National Atlas Equal Area
## # A tibble: 51 × 4
##    fips  abbr  full                                                         geom
##    <chr> <chr> <chr>                                          <MULTIPOLYGON [m]>
##  1 02    AK    Alaska               (((-2396840 -2547726, -2393291 -2546396, -2…
##  2 01    AL    Alabama              (((1093779 -1378539, 1093270 -1374227, 1092…
##  3 05    AR    Arkansas             (((483066 -927786.9, 506063 -926262.2, 5315…
##  4 04    AZ    Arizona              (((-1388677 -1254586, -1389182 -1251858, -1…
##  5 06    CA    California           (((-1719948 -1090032, -1709613 -1090025, -1…
##  6 08    CO    Colorado             (((-789537.1 -678772.6, -789536.6 -678768.3…
##  7 09    CT    Connecticut          (((2161728 -83727.16, 2177177 -65210.71, 21…
##  8 11    DC    District of Columbia (((1955475 -402047.2, 1960230 -393564, 1964…
##  9 10    DE    Delaware             (((2042501 -284358, 2043073 -279990.9, 2044…
## 10 12    FL    Florida              (((1855614 -2064805, 1860160 -2054368, 1867…
## # ℹ 41 more rows

This function returns a so-called simple feature collection with the geometry of the US states in the geom column of a tibble. For the purpose of this blog post, you can treat this object like any other tibble. And if your output looks a little bit different and gives you just a very long data.frame, then you may have to update your {usmap} package or even use the dev-version via:

devtools::install_github("pdil/usmap")

Plot the data

The really cool thing about our data set is that it works perfectly with the geom_sf() layer. You can just pass the data to ggplot and use geom_sf(). See:

us_map(regions = 'states') |> 
  ggplot() +
  geom_sf() +
  theme_minimal(base_size = 18, base_family = 'IBM Plex Mono')

Neat how that works, isn’t it? The geom_sf() layer automatically picks out the geom column and does its thing. And since this is a ggplot, we can do all the usual things. Like map the fill color to a variable in our data set. Here, let’s use the full column as it corresponds to the full name of a state.

us_map(regions = 'states') |> 
  ggplot() +
  geom_sf(aes(fill = full)) +
  theme_minimal(base_size = 18, base_family = 'IBM Plex Mono')

That’s a lot of colors. So let’s remove the legend. This is normally not a good strategy to deal with too many colors. But for this blog post it’s okay.

us_map(regions = 'states') |> 
  ggplot() +
  geom_sf(aes(fill = full)) +
  theme_minimal(base_size = 18, base_family = 'IBM Plex Mono') +
  theme(legend.position = 'none')


Sidenote: If you’re looking for map-related content, I have a video where I build an elaborate chart that uses a lot of maps of the US. You can find the video here:


Group the data

So now that we know how the geom_sf() layer works, let us group our states. These groups will then be colored differently. And we’ll add patterns to the groups too.

set.seed(2522)
grouped_data <- us_map() |> 
  mutate(
    group = sample(
      c('A', 'B', 'C', 'D'), 
      size = 51, 
      replace = TRUE
    )
  )
grouped_data
## Simple feature collection with 51 features and 4 fields
## Geometry type: MULTIPOLYGON
## Dimension:     XY
## Bounding box:  xmin: -2590847 ymin: -2608151 xmax: 2523583 ymax: 731405.7
## Projected CRS: NAD27 / US National Atlas Equal Area
## # A tibble: 51 × 5
##    fips  abbr  full                                                   geom group
##  * <chr> <chr> <chr>                                    <MULTIPOLYGON [m]> <chr>
##  1 02    AK    Alaska               (((-2396840 -2547726, -2393291 -25463… A    
##  2 01    AL    Alabama              (((1093779 -1378539, 1093270 -1374227… D    
##  3 05    AR    Arkansas             (((483066 -927786.9, 506063 -926262.2… C    
##  4 04    AZ    Arizona              (((-1388677 -1254586, -1389182 -12518… C    
##  5 06    CA    California           (((-1719948 -1090032, -1709613 -10900… B    
##  6 08    CO    Colorado             (((-789537.1 -678772.6, -789536.6 -67… A    
##  7 09    CT    Connecticut          (((2161728 -83727.16, 2177177 -65210.… D    
##  8 11    DC    District of Columbia (((1955475 -402047.2, 1960230 -393564… B    
##  9 10    DE    Delaware             (((2042501 -284358, 2043073 -279990.9… D    
## 10 12    FL    Florida              (((1855614 -2064805, 1860160 -2054368… D    
## # ℹ 41 more rows

So with this new data and our code from before, we can create a map with different colors for each group. All we have to do is map the group variable to the fill aesthetic.

grouped_data  |> 
  ggplot() +
  geom_sf(aes(fill = group)) +
  theme_minimal(base_size = 18, base_family = 'IBM Plex Mono') +
  theme(legend.position = 'none')

And while we’re at it, we might as well use nicer columns.

grouped_data  |> 
  ggplot() +
  geom_sf(aes(fill = group)) +
  theme_minimal(base_size = 18, base_family = 'IBM Plex Mono') +
  theme(legend.position = 'none') +
  scale_fill_manual(
    values = c(
      A = '#ef476f',
      B = '#ffd166',
      C = '#06d6a0',
      D = '#118ab2'
    )
  ) 

Add patterns

Finally, we can add patterns to our map. This works by using the {ggpattern} package. Basically, all we have to do is replace the geom_sf() layer with geom_sf_pattern().

library(ggpattern)
grouped_data  |> 
  ggplot() +
  geom_sf_pattern(aes(fill = group)) +
  theme_minimal(base_size = 18, base_family = 'IBM Plex Mono') +
  theme(legend.position = 'none') +
  scale_fill_manual(
    values = c(
      A = '#ef476f',
      B = '#ffd166',
      C = '#06d6a0',
      D = '#118ab2'
    )
  ) 

Now, what we get is one pattern for the whole of the US. To change that all we have to do is use the pattern aesthetic from {ggpattern} and map our group column to it.

grouped_data  |> 
  ggplot() +
  geom_sf_pattern(
    aes(
      fill = group,
      pattern = group
    )
  ) +
  theme_minimal(base_size = 18, base_family = 'IBM Plex Mono') +
  theme(legend.position = 'none') +
  scale_fill_manual(
    values = c(
      A = '#ef476f',
      B = '#ffd166',
      C = '#06d6a0',
      D = '#118ab2'
    )
  ) 

Finally, just as with the fill aesthetic, we can set other values via a scale_*_manual() layer. Here, that’s scale_pattern_manual().

grouped_data  |> 
  ggplot() +
  geom_sf_pattern(
    aes(
      fill = group,
      pattern = group
    )
  ) +
  theme_minimal(base_size = 18, base_family = 'IBM Plex Mono') +
  theme(legend.position = 'none') +
  scale_fill_manual(
    values = c(
      A = '#ef476f',
      B = '#ffd166',
      C = '#06d6a0',
      D = '#118ab2'
    )
  ) +
  scale_pattern_manual(
    values = c(
      A = 'stripe',
      B = 'crosshatch',
      C = 'circle',
      D = 'none'
    )
  ) 

Now that’s a pretty cool map, isn’t it? We could use one of ggpattern’s many extra options like pattern_angle to modify the patterns even further.

grouped_data  |> 
  ggplot() +
  geom_sf_pattern(
    aes(
      fill = group,
      pattern = group,
      pattern_angle = group
    )
  ) +
  theme_minimal(base_size = 18, base_family = 'IBM Plex Mono') +
  theme(legend.position = 'none') +
  scale_fill_manual(
    values = c(
      A = '#ef476f',
      B = '#ffd166',
      C = '#06d6a0',
      D = '#118ab2'
    )
  ) +
  scale_pattern_manual(
    values = c(
      A = 'stripe',
      B = 'crosshatch',
      C = 'circle',
      D = 'none'
    )
  ) 

Conclusion

Nice. We have successfully added patterns to our US map. If you found this helpful, here are some other ways I can help you:

To leave a comment for the author, please follow the link and comment on their blog: Albert Rapp.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)