Search code examples
rggplot2geom-raster

ggplot2 combine continuous variable and discrete variable in geom_raster


I have a 100 x 100 matrix with two types of elements. The first are continuous variables between 0 and 100 (actually discrete in the program but they represent something that is continuous and should thus have a continuously scaled legend), the other type is a discrete variable with three possible values (-1, -2, -3). I used this matrix in this question.

The goal is to make a heatmap of the continuous variables while distinguishing the areas with a discrete negative value. Currently, I am using ggplot with geom_raster (see code snippet at bottom of this question) to plot the following heat map.

Current heat map

However, The uniformly gray-colored area at the top and right consist of the negative discrete values and should have a different color/pattern than the other part of the graph. For example, the areas should be white with a label indicating the value (see the second image). Is there a way to do this with ggplot? In the ideal world, the graph would have a legend for the continuous range and one guide for the three discrete values.

Bonus question: is it possible to draw a line at the borders, i.e., draw a line if the next element in the matrix has a different value. Now I do this manually (see second code snippet) by just plotting many segments but this cannot be the way to go (and I do not succeed in combining this with the ggplot heat map).

enter image description here

The first code snippet (which makes the heat map)

  minRate = 0;
  maxRate = 100;

  colnames(df) = NULL
  df = melt(df)
  colnames(df) = c("col", "row", "value")

  # geom_raster takes the center as input argument
  df[,"col"] = df[,"col"] - 0.5
  df[,"row"] = df[,"row"] - 0.5

  # Without labels
  ggplot(df, aes(x = col, y = row, fill = value)) + 
    geom_raster() + 
    theme_bw() +
    labs(fill="Rate (%)") + 
    theme(plot.margin=unit(c(3,3,3,2),"mm"), legend.position = "right") +
    scale_fill_gradient(low="black", high="white", limits=c(minRate, maxRate)) +
    scale_x_continuous("state 1", expand = c(0,0), limits=c(0,100)) + 
    scale_y_continuous("state 2", expand = c(0,0), limits=c(0,100))

And the second code snippet (which only plots the borders):

printPolicy <- function(df, title)
{
  n = nrow(df)

  plot(NA, xlim=c(0, n), ylim=c(0, n), 
       xlab="Machine 0", ylab="Machine 1", main=title,
       las=1, yaxs='i', xaxs='i')


  for (x0 in 1:(n-1))
  {
    for (x1 in 1:(n-1))
    {
      # Horizontal lines
      if (df[x0, x1] != df[x0+1, x1])
        segments(x1-1, x0, x1, x0)

      # Vertical lines
      if (df[x0, x1] != df[x0, x1+1])
        segments(x1, x0-1, x1, x0)
    }
  }
}

Solution

  • This is relatively easy to do with the ggnewscale package, see example below. Assume dat is the output of read.csv(the_data_you_posted).

    library(ggplot2)
    library(ggnewscale)
    dat <- as.matrix(dat)
    dimnames(dat) <- NULL
    
    mdat <- reshape2::melt(dat)
    
    conti <- mdat[mdat$value >= 0,]
    discr <- mdat[mdat$value < 0,]
    
    ggplot(mapping = aes(Var1, Var2)) +
      geom_raster(data = conti, aes(fill = value), 
                  hjust = 0, vjust = 0) +
      scale_fill_continuous() + # scale for continuous values need to be 
                                # defined before call to  new_scale_fill()
      new_scale_fill() +
      geom_raster(data = discr, aes(fill = as.factor(value)),
                  hjust = 0, vjust = 0)
    

    enter image description here

    Decorate the plot as you see fit.

    Unfortunately, I don't know the answer to your bonus question but I'm quite interested to find out if anyone has an automated solution for that question.