R Ggplot2 - How to Specify Out of Bounds Values' Colour

R ggplot2 - How do I specify out of bounds values' colour

As you said youself, you want the oob argument in the scale_fill_gradient. To clamp values, you can use squish from the scales package (scales is installed when ggplot2 is installed):

library(scales)

and later

scale_fill_gradient(low = "red", high = "green", limits=c(0.6, 1), oob=squish)

Colouring out of bounds as limits in ggplot

The reason oob = squish doesn't seem to work anymore is that some of the functionality from ggplot2 has been transferred to the scales package. Load scales first, or type oob = scales::squish instead.

Using oob to change value to a another color in ggplot2

It's probably easiest to just use two geom_tiles and rely on overplotting for the true NA values:

ggplot(dat.m, aes(factor(Var1), factor(Var2))) +
geom_tile(aes(fill = value)) +
geom_tile(fill = 'grey', data = subset(dat.m, is.na(value))) +
geom_text(aes(label = round(value, 1))) +
scale_fill_continuous("",limits=c(1,2), low = "#d73027",
high = "#4575b4", na.value = "lightgreen") +
coord_fixed()

Sample Image

How can I add triangles to a ggplot2 colorbar in R to indicate out of bound values?

As far as I'm aware there is not a package that implements triangle ends for colourbars in ggplot2 (but please let me know if there is!). However, we can implement our own. We'd need a constructor for our custom guide and a way to draw it. Most of the stuff is already implemented in guide_colourbar() and methods for their class, so what we need to do is just tag on our own class and expand the guide_gengrob method. The code below should work for vertically oriented colourbars. You'd need to know some stuff about the grid package and gtable package to follow along.

library(ggplot2)
library(gtable)
library(grid)

my_triangle_colourbar <- function(...) {
guide <- guide_colourbar(...)
class(guide) <- c("my_triangle_colourbar", class(guide))
guide
}

guide_gengrob.my_triangle_colourbar <- function(...) {
# First draw normal colourbar
guide <- NextMethod()
# Extract bar / colours
is_bar <- grep("^bar$", guide$layout$name)
bar <- guide$grobs[[is_bar]]
extremes <- c(bar$raster[1], bar$raster[length(bar$raster)])
# Extract size
width <- guide$widths[guide$layout$l[is_bar]]
height <- guide$heights[guide$layout$t[is_bar]]
short <- min(convertUnit(width, "cm", valueOnly = TRUE),
convertUnit(height, "cm", valueOnly = TRUE))
# Make space for triangles
guide <- gtable_add_rows(guide, unit(short, "cm"),
guide$layout$t[is_bar] - 1)
guide <- gtable_add_rows(guide, unit(short, "cm"),
guide$layout$t[is_bar])

# Draw triangles
top <- polygonGrob(
x = unit(c(0, 0.5, 1), "npc"),
y = unit(c(0, 1, 0), "npc"),
gp = gpar(fill = extremes[1], col = NA)
)
bottom <- polygonGrob(
x = unit(c(0, 0.5, 1), "npc"),
y = unit(c(1, 0, 1), "npc"),
gp = gpar(fill = extremes[2], col = NA)
)
# Add triangles to guide
guide <- gtable_add_grob(
guide, top,
t = guide$layout$t[is_bar] - 1,
l = guide$layout$l[is_bar]
)
guide <- gtable_add_grob(
guide, bottom,
t = guide$layout$t[is_bar] + 1,
l = guide$layout$l[is_bar]
)

return(guide)
}

You can then use your custom guide as the guide argument in a scale.

g <- ggplot(mtcars, aes(mpg, wt)) +
geom_point(aes(colour = drat))

g + scale_colour_viridis_c(
limits = c(3, 4), oob = scales::oob_squish,
guide = my_triangle_colourbar()
)

Sample Image

There isn't really a natural way to colour out-of-bounds values differently, but you can make very small slices near the extremes a different colour.

g + scale_colour_gradientn(
colours = c("red", scales::viridis_pal()(255), "hotpink"),
limits = c(3, 4), oob = scales::oob_squish,
guide = my_triangle_colourbar()
)

Sample Image

Created on 2021-07-19 by the reprex package (v1.0.0)

Remove grey fill for out of bounds values when using limits in scale_

Add the argument na.value="transparent" to scale_fill_gradient:

p + theme_bw() + geom_tile(aes(fill=z)) +
scale_fill_gradient(low="green", high="red",
limits=c(-0.1, 0.1), na.value="transparent")

Sample Image

ggplot scale color gradient to range outside of data range

It's very important to remember that in ggplot, breaks will basically never change the scale itself. It will only change what is displayed in the guide or legend.

You should be changing the scale's limits instead:

ggplot(data=t, aes(x=x, y=y)) +
geom_tile(aes(fill=z)) +
scale_fill_gradientn(limits = c(-3,3),
colours=c("navyblue", "darkmagenta", "darkorange1"),
breaks=b, labels=format(b))

And now if you want the breaks that appear in the legend to extend further, you can change them to set where the tick marks appear.

A good analogy to keep in mind is always the regular x and y axes. Setting "breaks" there will just change where the tick marks appear. If you want to alter the extent of the x or y axes, you'd typically change a setting like their "limits".

Scale color gradient and AND outside the limits

You can specify how to deal with out-of-bounds values via argument oob; for example, scales::squish "squishes" values into the range specified by limits:

ggplot(data.frame(a=1:10), aes(1, a, color = a)) +
geom_point(size = 6) +
scale_colour_gradientn(
colours=c('red','yellow','green'),
limits=c(2,8),
oob = scales::squish);

Sample Image

how to change / specify fill color which exceeds the limits of a gradient bar?

If you want to keep the gradient scale and have two additional discrete values for off limits above and below, I think the easiest way would be to have separate fill scales for "in-limit" and "off-limit" values. This can be done with separate calls to geom_tile on subsets of your data and with packages such as {ggnewscale}.

I think it then would make sense to place the discrete "off-limits" at the respective extremes of your gradient color bar. You need then three geom_tile calls and three scale_fill calls, and you will need to specify the guide order within each scale_fill call. You will then need to play around with the legend margins, but it's not a big problem to make it look OK.

library(tidyverse)
library(RColorBrewer)
tile_data <- data.frame(
category = letters[1:5],
region = paste0("region_", 1:5),
sales = c(1, 2, 5, 0.1, 300)
)


ggplot(tile_data, aes(
x = category,
y = region,
fill = sales
)) +
geom_tile(data = filter(tile_data, sales <= 11 & sales >=1)) +
scale_fill_gradientn(NULL,
limits = c(1, 11),
colors = brewer.pal(11, "Spectral"),
guide = guide_colorbar(order = 2)
) +
ggnewscale::new_scale_fill() +
geom_tile(data = filter(tile_data, sales > 11), mapping = aes(fill = sales > 11)) +
scale_fill_manual("Sales", values = "black", labels = "> 11", guide = guide_legend(order = 1)) +
ggnewscale::new_scale_fill() +
geom_tile(data = filter(tile_data, sales < 1), mapping = aes(fill = sales < 1)) +
scale_fill_manual(NULL, values = "darkblue", labels = "< 1", guide = guide_legend(order = 3)) +
theme_minimal() +
theme(legend.spacing.y = unit(-6, "pt"),
legend.title = element_text(margin = margin(b = 10)))

Sample Image

Created on 2021-11-22 by the reprex package (v2.0.1)

How to customise the colour for middle and maximum value in ggplot2?

You can use arguments limits and oob in scale_fill_gradientn to achieve what you're after:

ggplot(ran_melt, aes(Var1, Var2)) +
geom_tile(aes(fill = value), color = "white") +
scale_fill_gradientn(
colours = c("red", "white", "blue"),
limits = c(-2, 2),
oob = squish) +
labs(fill = 'legend')

Sample Image

Explanation: oob = squish gives values that lie outside of limits the same colour/fill value as the min/max of limits. See e.g. ?scale_fill_gradientn for details on oob.


Update

If you have asymmetric limits you can use argument values with rescale:

ggplot(ran_melt, aes(Var1, Var2)) +
geom_tile(aes(fill = value), color = "white") +
scale_fill_gradientn(
colours = c("red", "white", "blue"),
limits = c(-1, 2),
values = rescale(c(-1, 0, 2)),
oob = squish) +
labs(fill = 'legend')

Sample Image

Output loop Ggplot2 figures to pdf: subscript out of bounds

There is no p in the first for loop so plot_list[[i]] = p doesn't work. If you assign output of ggplot to p it should work.

Alternatively you don't need to save the output to a list and then write a separate loop to print them, you can do this in the same for loop.

You can do something like this that does not require to create plot_list at all.

model <- y~x
pdf("plots.pdf")

for(i in 4:ncol(test)) {
print(ggplot(test, aes(x= time, y= test[ , i])) +
geom_point(alpha = 0.3) +
ggtitle(colnames(test)[i])+
facet_wrap(~Group, scales = "free_y") +
geom_smooth(method = "lm", formula = model, se = F) +
stat_poly_eq(aes(label = paste(..rr.label..)),
label.x.npc = "right", label.y.npc = 0.15,
formula = model, parse = TRUE, size = 3)+
stat_fit_glance(method = 'lm',
method.args = list(formula = model),
geom = 'text',
aes(label = paste("P-value = ", signif(..p.value.., digits = 4), sep = "")),
label.x = 'right', label.y = 0.35, size = 3))
}
dev.off()


Related Topics



Leave a reply



Submit