'How do I limit the range of the viridis colour scale?

I have two sets of data, which I want to present using a heat map with the viridis color scale. For the first data set, my values range from 0 to 1.2 and I can easily see the differences I want to see. However my second data set has some outliers, resulting in a range from 0 to 2. Now it's harder to see the differences in the interesting range between 0 and 1 and it's more diffucult to compare the two images directly. Is there a possibility to show the data from 0 to 1.2 using the viridis colour scale while showing the higher values in yellow ("highest" colour of the viridis scale)? Here is an example:

library(viridis)

#Create Data
DataSet1 <- expand.grid(x = 0:5, y = 0:5)
DataSet1$z <- runif(36, 0, 1.2)

DataSet2 <- expand.grid(x = 0:5, y = 0:5)
DataSet2$z <- runif(36, 0, 2)

#Plot Data
ggplot(DataSet1, aes(x, y, fill = z)) + 
  geom_tile() +
  scale_fill_viridis() +
  geom_text(aes(label = round(z, 2)), size = 2)

DataSet1: Differences between 0.5 and 0.7 are easy to see

enter image description here

ggplot(DataSet2, aes(x, y, fill = z)) + 
  geom_tile() +
  scale_fill_viridis() +
  geom_text(aes(label = round(z, 2)), size = 2)

DataSet2: Differences between 0.5 and 0.7 are diffucult to see

enter image description here



Solution 1:[1]

You can define an arbitrary rescaling function. Not sure this looks that great, would likely need some work with the legend, but in principle this mechanism allows you to map data values onto the scale in any way you want.

ggplot(DataSet2, aes(x, y, fill = z)) + 
  geom_tile() +
  scale_fill_viridis(rescaler = function(x, to = c(0, 1), from = NULL) {
    ifelse(x<1.2, 
           scales::rescale(x,
                           to = to,
                           from = c(min(x, na.rm = TRUE), 1.2)),
           1)}) +
  geom_text(aes(label = round(z, 2)), size = 2)

enter image description here

Solution 2:[2]

EDIT 2022-05-03: The scale function is called scale_fill_viridis_c() these days.

@ClausWilke's solution is better because it shows in the legend, but sometimes one just needs a quick solution without having to write too much specific code. This one also relies on the scales package

ggplot(DataSet2, aes(x, y, fill = z)) + 
  geom_tile() +
  scale_fill_viridis_c(limits = c(0.2, 1), oob = scales::squish) +
  geom_text(aes(label = round(z, 2)), size = 2)

enter image description here

Solution 3:[3]

Are you looking for something like this?

ggplot(DataSet2, aes(x, y, fill = z)) + 
  geom_tile() +
  scale_fill_gradient(low="green", high="red", limits=c(0, 1.2), 
                      na.value = "yellow") +
  geom_text(aes(label = round(z, 2)), size = 2)

enter image description here

Using the viridis colors, asper jazzurro recommendation.

ggplot(DataSet2, aes(x, y, fill = z)) + 
  geom_tile() + 
  scale_fill_gradientn(colors = viridis_pal()(9), limits=c(0, 1.2), 
                       na.value = "#FDE725FF") + 
  geom_text(aes(label = round(z, 2)), size = 2)

enter image description here

Solution 4:[4]

It's not necessarily an improvement, but you could do something like this to show the higher values in yellow:

DataSet2A <- DataSet2 %>% filter(z <= 1.2)
DataSet2B <- DataSet2 %>% filter(z > 1.2)

ggplot(DataSet2A, aes(x, y, fill = z)) +
  geom_tile() +
  scale_fill_viridis(begin = 0, end = .75) +
  geom_text(aes(label = round(z, 2)), size = 2) +
  geom_tile(data = DataSet2B, aes(x, y), fill = "yellow")

enter image description here

Maybe if you play around with the cutoff as well as the begin= and end= parameters in the scale, which control the portion of the viridis scale that you're employing, you can achieve the result you want. (Note that you can only have one fill scale per plot, but you can set additional constant fills as I've done here with yellow.)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Claus Wilke
Solution 2
Solution 3
Solution 4 jtr13