19

I have got a map with a legend gradient and I would like to add a box for the NA values. My question is really similar to this one and this one. Also I have read this topic, but I can't find a "nice" solution somewhere or maybe there isn't any?

Here is an reproducible example:

library(ggplot2)
map <- map_data("world")
map$value <- setNames(sample(-50:50, length(unique(map$region)), TRUE), 
                      unique(map$region))[map$region]
map[map$region == "Russia", "value"] <- NA
ggplot() +
  geom_polygon(data = map,
               aes(long, lat, group = group, fill = value)) +
  scale_fill_gradient2(low = "brown3", mid = "cornsilk1", high = "turquoise4",
                       limits = c(-50, 50),
                       na.value = "black")

So I would like to add a black box for the NA value for Russia. I know, I can replace the NA's by a number, so it will appear in the gradient and I think, I can write a workaround like the following, but all this workarounds do not seem like a pretty solution for me and also I would like to avoid "senseless" warnings:

ggplot() +
  geom_polygon(data = map,
               aes(long, lat, group = group, fill = value)) +
  scale_fill_gradient2(low = "brown3", mid = "cornsilk1", high = "turquoise4",
                       limits = c(-50, 50),
                       na.value = "black") +
  geom_point(aes(x = -100, y = -50, size = "NA"), shape = NA, colour = "black") +
  guides(size = guide_legend("NA", override.aes = list(shape = 15, size = 10)))
Warning messages:
1: Using size for a discrete variable is not advised. 
2: Removed 1 rows containing missing values (geom_point). 
Carl
  • 4,232
  • 2
  • 12
  • 24
user5514978
  • 374
  • 5
  • 12
  • 1
    I unfortunately miss reputation points to comment posted answers, but I think there is a little mistake in the top answer from @bdemarest: for the "**Another solution**", in the code line `guides(colour=guide_legend("No data", override.aes=list(colour="black")))`. in `override.aes`, `colour` should not be used. It should be `fill` otherwise the color of the box will be black by default I guess, and with `colour`you just change the colorbox contour to black. So I suggest a line replacement in the answer as followed: `guides(colour=guide_legend("No data", override.aes=list(fill="black")))` – Yoann Pageaud Feb 11 '19 at 12:58
  • 1
    @YoannPageaud, Yes, good point. I did some experimenting while testing your proposed change. I found that the entire line `guides(...)` is not really needed. The same plot is created after removing that line and changing the previous line to `scale_colour_manual(values=NA, name="No data")`. It's possible that I was just mistaken, or that changes to `ggplot2` in the last 2 years are responsible. – bdemarest Apr 08 '19 at 03:19

2 Answers2

31

One approach is to split your value variable into a discrete scale. I have done this using cut(). You can then use a discrete color scale where "NA" is one of the distinct colors labels. I have used scale_fill_brewer(), but there are other ways to do this.

map$discrete_value = cut(map$value, breaks=seq(from=-50, to=50, length.out=8))

p = ggplot() +
    geom_polygon(data=map, aes(long, lat, group=group, fill=discrete_value)) +
    scale_fill_brewer(palette="RdYlBu", na.value="black") +
    coord_quickmap()

ggsave("map.png", plot=p, width=10, height=5, dpi=150)   

enter image description here

Another solution

Because the original poster said they need to retain the color gradient scale and the colorbar-style legend, I am posting another possible solution. It has 3 components:

  1. We need to trick ggplot into drawing a separate color scale by using aes() to map something to color. I mapped a column of empty strings using aes(colour="").
  2. To ensure that we do not draw a colored boundary around each polygon, I specified a manual color scale with a single possible value, NA.
  3. Finally, guides() along with override.aes is used to ensure the new color legend is drawn as the correct color.

p2 = ggplot() +
     geom_polygon(data=map, aes(long, lat, group=group, fill=value, colour="")) +
     scale_fill_gradient2(low="brown3", mid="cornsilk1", high="turquoise4",
                     limits=c(-50, 50), na.value="black") +
     scale_colour_manual(values=NA) +              
     guides(colour=guide_legend("No data", override.aes=list(colour="black")))

ggsave("map2.png", plot=p2, width=10, height=5, dpi=150)   

enter image description here

bdemarest
  • 14,397
  • 3
  • 53
  • 56
  • Thanks for your answer, but unfortunately it's not what i wanted. I tried this by myself before but it is important for me to have a gradient in the legend and no groups. – user5514978 Feb 22 '17 at 11:46
  • @user5514978, I posted another approach that should be pretty close to what you are looking for. – bdemarest Feb 23 '17 at 01:27
  • 1
    Hey, that's cool. Thank's a lot for your effort! Especially for your second look, even when you already gave an answer. – user5514978 Feb 24 '17 at 08:33
  • Hey, that's cool, but when I reproduce your second example the "No data" box sometimes appears below and sometimes above the legend gradient. I have no idea how to control it. Any suggestion? – MsGISRocker Jul 31 '20 at 15:46
0

It's possible, but I did it years ago. You can't use guides. You have to set individually the continuous scale for the values as well as the discrete scale for the NAs. This is what the error is telling you and this is how ggplot2 works. Did you try using both scale_continuous and scale_discrete since your set up is rather awkward, instead of simply using guides which is basically used for simple plot designs?

user2502338
  • 889
  • 1
  • 8
  • 10
  • Do you have an example? The warning also appears without guides, because of `size = "NA"`, but when I remove this or change it to a number, my legend doesn't look like it should as before. – user5514978 Feb 22 '17 at 11:51
  • Sorry for the delay...are you treating your NA as a factor? – user2502338 Feb 26 '17 at 15:55
  • In addition to the example given above see: https://www.r-bloggers.com/custom-legend-in-r/amp/ – user2502338 Feb 26 '17 at 15:58