Robin Lindström
Robin Lindström

Reputation: 682

Centering color around 0 with RColorBrewer

So I want to visualise a matrix with colors like this

library(RColorBrewer)
vec = rbinom(10000,1,0.1)
n = sum(vec)
vec = ifelse(vec == 1, rnorm(n), 0)
mat = matrix(vec,100,100)
image(t(mat)[,nrow(mat):1],
      col=brewer.pal(8,"RdBu"),
      xaxt= "n", yaxt= "n", frame.plot=T,
      useRaster = TRUE
)

Which gives me the plot

Example plot

But I want the colors to be "centered around 0". By that I mean that I want the value zero to be white and positive/negative values be red/blue (or blue/red it doesn't matter). Any ideas if this is possible?

Upvotes: 5

Views: 4985

Answers (4)

Tung
Tung

Reputation: 28391

Here is a solution using the ggplot2 package with manual scaling (Ref)

library(RColorBrewer)
library(ggplot2)
library(reshape2)

set.seed(2020)
vec <- rbinom(10000, 1, 0.1)
n <- sum(vec)
vec <- ifelse(vec == 1, rnorm(n), 0)
mat <- matrix(vec, 100, 100)

# convert to long format
df <- melt(mat)
summary(df)
#>       Var1             Var2            value          
#>  Min.   :  1.00   Min.   :  1.00   Min.   :-2.916137  
#>  1st Qu.: 25.75   1st Qu.: 25.75   1st Qu.: 0.000000  
#>  Median : 50.50   Median : 50.50   Median : 0.000000  
#>  Mean   : 50.50   Mean   : 50.50   Mean   : 0.000772  
#>  3rd Qu.: 75.25   3rd Qu.: 75.25   3rd Qu.: 0.000000  
#>  Max.   :100.00   Max.   :100.00   Max.   : 3.214787

### default
p1 <- ggplot(df, aes(x = Var1, y = Var2, fill = value)) +
  geom_raster() +
  theme_minimal(base_size = 16)

Rescale

# set the limits of the palette so that zero is in the middle of the range.
limit <- max(abs(df$value)) * c(-1, 1)

p1 + 
  scale_fill_distiller(palette = 'RdBu', limit = limit)

# test with the scico package 
# https://github.com/thomasp85/scico
library(scico)
p1 + 
  scale_fill_scico(palette = "roma", limit = limit) 

# test with the rcartocolor package 
# https://github.com/Nowosad/rcartocolor
library(rcartocolor)
p1 + 
  scale_fill_carto_c(palette = 'Earth', limit = limit) 

Created on 2020-02-07 by the reprex package (v0.3.0)

Upvotes: 1

Dave2e
Dave2e

Reputation: 24089

Here is a solution without any additional packages. In your code you did not assign the values from the vec variable to any of the eight color bins. You need to cut the vec array into your eight bins and then assign each bin to a color and then plot:

library(RColorBrewer)
vec = rbinom(10000,1,0.1)
n = sum(vec)
vec = ifelse(vec == 1, rnorm(n), 0)
mat = matrix(vec,100,100)

#cut the original data into 9 groups
cutcol<-cut(vec, 9)
#Create color palette with white as the center color
colorpal<-brewer.pal(8,"RdBu")
colorpal<-c(colorpal[1:4], "#FFFFFF", colorpal[5:8])

#assign the data to the 9 color groups
color<-colorpal[cutcol]
#create the color matrix to match the original data
colormat<-matrix(color,100,100)

#plot with the assigned colors
image(t(mat)[,nrow(mat):1],
      col=colormat,
      xaxt= "n", yaxt= "n", frame.plot=T,
      useRaster = TRUE
)

#check color assignment
#hist(vec)
#hist(as.numeric(cutcol), breaks=8)

enter image description here

Upvotes: 2

csgroen
csgroen

Reputation: 2551

Alternatively to heatmap2, you could use pheatmap:

library(pheatmap)
pheatmap(mat,
         color = brewer.pal(7,"RdBu"),
         border_color = NA,
         cluster_rows = FALSE,
         cluster_cols = FALSE)

You can also hide the legend if you wish with legend = FALSE, which would yield a similar result to your image call, but with white being 0.

Upvotes: 1

Melissa Key
Melissa Key

Reputation: 4551

The function bluered in the gplots package does this. You can make your color palette as:

library(gplots) # not to be confused with `ggplot2`, which is a very different package
color_palette <- bluered(9) # change the number to adjust how many shades of blue/red you have.  Even numbers will assign white to two bins in the middle.

To force them to be centered in the middle, you might use the heatmap.2 function, also in gplots - just don't have it do any clustering:

heatmap.2(mat,
  Rowv = FALSE,
  Colv = FALSE, 
  dendrogram = 'none',
  trace = 'none',
  col = bluered, # this can take a function
  symbreaks = TRUE, # this is the key  value for symmetric breaks
)

to stick with the image function, you need to manually set the breaks. The following code will get that for you:

pos_breaks <- quantile(abs(mat), probs = seq(0, 1, length.out = 5))
centered_breaks <- c(rev(-pos_breaks[-1]), pos_breaks)

Upvotes: 2

Related Questions