1

I'm trying to separate a legend into two distinct groups.

The available answer : ggplot2: Divide Legend into Two Columns, Each with Its Own Title seemed to be the right one but doesn't work with the geom_sf....

Here is a reproducible example:

nc <- sf::st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE) %>% 
  mutate(var_test=case_when(AREA<=0.05~"G1",
                            AREA<=0.10~"G2",
                            AREA>0.10~"G3"))
    ggplot(nc,aes(x=1)) +
  geom_bar(aes(fill = var_test))+ 
  scale_fill_manual(aesthetics = "fill", values = c("#ffffa8","#69159e","#f2794d"),
                    breaks =  c("G1","G2"), name = "First Group:") +
  new_scale_fill() +
  geom_bar(aes(fill2 = var_test)) %>% rename_geom_aes(new_aes = c(fill = "fill2")) +
  scale_fill_manual(aesthetics = "fill2", values = c("#ffffa8","#69159e","#f2794d"),
                    breaks = c("G3"), name = "Second Group:")

Works with geom_bar

Doesn't work with geom_sf

ggplot(nc,aes(x=1)) +
  geom_sf(aes(fill = var_test))+ 
  scale_fill_manual(aesthetics = "fill", values = c("#ffffa8","#69159e","#f2794d"),
                    breaks =  c("G1","G2"), name = "First Group:") +
  new_scale_fill() +
  geom_sf(aes(fill2 = var_test)) %>% rename_geom_aes(new_aes = c(fill = "fill2")) +
  scale_fill_manual(aesthetics = "fill2", values = c("#ffffa8","#69159e","#f2794d"),
                    breaks = c("G3"), name = "Second Group:")
Error: Can't add `o` to a ggplot object.
Run `rlang::last_error()` to see where the error occurred.

Thanks for your help.

MurielleL
  • 13
  • 2

1 Answers1

2

Bit of a hack but instead of using the ggnewscale and relayer packages this is a pure ggplot2 approach. The trick is to map the group also on the color aesthetic, use the second scale_fill_manual to make a legend for the color aesthetic and do some warngling with guide_legend so that colors are right and the second legend showsd up as a nice fill legend. Try this:

library(ggplot2)
library(dplyr)
library(sf)

nc <- sf::st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE) %>% 
  mutate(var_test=case_when(AREA<=0.05~"G1",
                            AREA<=0.10~"G2",
                            AREA>0.10~"G3"))

ggplot(nc,aes(x=1)) +
  geom_bar(aes(fill = var_test, color = var_test))+ 
  scale_fill_manual(aesthetics = "fill", values = c("#ffffa8","#69159e","#f2794d"),
                    breaks =  c("G1","G2"), name = "First Group:") +
  scale_fill_manual(aesthetics = "color", values = c("#ffffa8","#69159e","#f2794d"),
                    breaks = c("G3"), name = "Second Group:") +
  guides(fill = guide_legend(override.aes = list(color = c(G1 = "#ffffa8", G2 = "#69159e"))),
         color = guide_legend(override.aes = list(fill = c(G3 = "#f2794d"))))

ggplot(nc,aes(x=1)) +
  geom_sf(aes(fill = var_test, color = var_test))+ 
  scale_fill_manual(aesthetics = "fill", values = c("#ffffa8","#69159e","#f2794d"),
                    breaks =  c("G1","G2"), name = "First Group:") +
  scale_fill_manual(aesthetics = "color", values = c("#ffffa8","#69159e","#f2794d"),
                    breaks = c("G3"), name = "Second Group:") +
  guides(fill = guide_legend(override.aes = list(color = c(G1 = "#ffffa8", G2 = "#69159e"))),
         color = guide_legend(override.aes = list(fill = c(G3 = "#f2794d"))))

Created on 2020-05-28 by the reprex package (v0.3.0)

EDIT With more than two groups it's not impossible but gets even more hack-ish. And probably no feasible solution e.g. if you want to have even more groups. For this reason I would recommend to think about trying facetting by groups or ... . But have a look:

library(ggplot2)
library(dplyr)
library(sf)

nc <- sf::st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE) %>% 
  mutate(var_test=case_when(AREA <= 0.05 ~ "G1",
                            AREA <= 0.10 ~ "G2",
                            AREA > 0.10 & AREA <= 0.2 ~"G3",
                            AREA > 0.2 ~ "G4"))

col_values <- c(G1 = "#ffffa8", G2 = "#69159e", G3 = "#f2794d", G4 = "red")
col_black <- rep("black", length(col_values)) 
alpha_values <- rep(1, length(col_values)) 

fill_group1 <- c("G1", "G2")
fill_group2 <- c("G3")
fill_group3 <- c("G4")

ggplot(nc) +
  geom_sf(aes(fill = var_test, shape = var_test, color = var_test)) +
  scale_fill_manual(aesthetics = "fill", values = col_values,
                    breaks =  fill_group1, name = "First Group:") +
  scale_color_manual(breaks = fill_group2, values = col_black, name = "Second Group:") +
  scale_shape_discrete(breaks = fill_group3, name = "Third Group:") +
  guides(fill = guide_legend(override.aes = list(fill = col_values[fill_group1], color = col_values[fill_group1])),
         color = guide_legend(override.aes = list(fill = col_values[fill_group2], color = col_values[fill_group2])),
         shape = guide_legend(override.aes = list(fill = col_values[fill_group3], color = col_values[fill_group3])))

Created on 2020-05-28 by the reprex package (v0.3.0)

stefan
  • 90,330
  • 6
  • 25
  • 51
  • Thank you very much for your response. It actually works very well with 2 groups. But how can we do it with more groups (for example 3)? – MurielleL May 28 '20 at 12:30
  • Hi @MurielleL. With more than two groups it's not impossible but get even more hack-ish. See my edit. – stefan May 28 '20 at 19:49
  • Thank you very much, it works very well. Hackiss!, but it works! – MurielleL May 29 '20 at 08:00
  • Great! On occassion we sholud raise an issue that `ggnewscale` breaks with `geom_sf`. – stefan May 29 '20 at 08:29
  • BTW: If you want do me a favor: Mark the question as answered and upvote it. Besides giving me some credit it shows others with a similar problem that the solution worked and removes the question from the queue of questions still waiting for an answer. – stefan May 30 '20 at 08:37