rggplot2geom-sf

How to Layer two geom_sf layers in ggplot with two different scale_fill_gradientn?


I have a dataset that looks like the following:

library(tidyverse)
library(sf)
library(giscoR)

temp <- data.frame(Country = c("United States", "Brazil", "Mexico", "Canada", "Argentina", "Chile",
                               "Peru", "Guatemala", 'Ecuador', "United Kingdom"),
                   Count = c(3000000, 300000, 250000, 135000, 100000, 99000, 98000, 47000, 40000, 39000))

and I'm able to successfully generate a map using the following code:

all_countries <- gisco_get_countries()
symbol_pos <- st_centroid(all_countries, of_largest_polygon = TRUE)
temp2 <- temp %>%
  left_join(symbol_pos, by=c("Country" = "NAME_ENGL")) %>%
  arrange(desc(Count))

ggplot() +
  geom_sf(data = all_countries, fill = "white") +
  geom_sf(data = temp2, pch = 21, mapping = aes(size = Count, geometry = geometry, fill = Count)) +  
  scale_size( range = c(1, 20), guide = guide_legend(direction = "horizontal", nrow = 1, label.position = "bottom")) +
  scale_fill_gradientn(colours = hcl.colors(10, "Blues 3", rev = TRUE, alpha = 0.9)) +
  guides(fill = guide_legend(title = "")) +
  theme_void() +
  theme(legend.position = "bottom")

My main issue is that the US number is so much larger than everything else that all other points are much smaller and basically white. I'd really like a visualization where I can have a layer with the same exact code, but removing the US observation, and then another layer with the US observation but in Red with a size roughly the same as the next biggest. Brazil. So it would look like the following (but with a red circle on US)

tempNoUS <- temp2 %>%
filter(Country != "United States")
ggplot() +
  geom_sf(data = all_countries, fill = "white") +
  geom_sf(data = tempNoUS, pch = 21, mapping = aes(size = Count, geometry = geometry, fill = Count)) +
  scale_size( range = c(1, 20), guide = guide_legend(direction = "horizontal", nrow = 1, label.position = "bottom")) +
  scale_fill_gradientn(colours = hcl.colors(10, "Blues 3", rev = TRUE, alpha = 0.9)) +
  guides(fill = guide_legend(title = "")) +
  theme_void() +
  theme(legend.position = "bottom")

I looked at library(ggnewscale) to see if I could add the following lines as part of my testing:

+   new_scale("color") +
  geom_sf(data =temp2 %>% filter(Country == "United States"), pch = 21, mapping = aes(size = Count, geometry = geometry, fill = Count))

and it didnt seem to really do anything.

To summarize, I'd like two geom_sf() layers, one for non-US countries with a scale_fill_gradientn of Blue3, and then the other layer just for the US with a fill of Red.


Solution

  • Is this what you're looking for?

    ggplot() +
      geom_sf(data = all_countries, fill = "white") +
      geom_sf(data = tempNoUS, pch = 21, mapping = aes(size = Count, geometry = geometry, fill = Count)) +
      scale_size(range = c(1, 20), guide = guide_legend(direction = "horizontal", nrow = 1, label.position = "bottom")) +
      scale_fill_gradientn(colours = hcl.colors(10, "Blues 3", rev = TRUE, alpha = 0.9)) +
      guides(fill = guide_legend(title = "")) +
      new_scale("fill") +
      new_scale("size") +
      geom_sf(data =temp2 %>% 
                filter(Country == "United States"), pch = 21, 
              mapping = aes(size = Count, geometry = geometry, fill = Count)) +
      scale_fill_gradientn(colours = c("red")) +
      scale_size(range = c(1, 20)) +
      theme_void() +
      theme(legend.position = "right")
    

    You might need to play around with legend sizes, scale limits to get exactly what you want. It does also sound as though doing something like a log transform on the count might b appropriate and then you wouldn't need to split the data.

    enter image description here