Search code examples
rplot

R stack plot based on value


I want to make a picture with R. I have data like this: 3 Samples and 4 categories, with value A, B and C

      Sam1 Sam2 Sam3
Cat1  A    B;C  C
Cat2  B    A    A;B
Cat3  A;C  C    A;B;C
Cat4  B    A    C

I want to make a plot, with stack colour for the multiple value. The output that I want is in the picture.

enter image description here

Can anyone help? Thank you.

(This solved by Robert Hacken, Thanks).. Another add-on that I want is add notes on the side For G1 has note D1 on the side, and so on... Is it possible?

enter image description here

Thank you.


Solution

  • With visualizations like this, I usually find it easiest to just draw them:

    dat <- read.table(text='Sam1 Sam2 Sam3
    Cat1  A    B;C  C
    Cat2  B    A    A;B
    Cat3  A;C  C    A;B;C
    Cat4  B    A    C')
    
    let.col <- c(A='red', B='#5b9bd5', C='#70ad47')
    # width of the space between squares; square side + sep = 1, so sep=.2 means
    #   that the square's side will be four times the empty space
    sep <- .2
    
    par(mar = rep(0, 4))
    frame()
    plot.window(c(0, ncol(dat) + 2), c(nrow(dat) + 1, 0), asp=1)
    
    for (i in 0:nrow(dat)) {
      for (j in 0:ncol(dat)) {
        # first row -- Sx labels
        if (i == 0) {
          if (j > 0) text(j + .5, .5, paste0('S', j))
        # first column -- Gx labels
        } else if (j == 0) {
          if (i > 0) text(.5, i + .5, paste0('G', i))
        # stacked colours
        } else {
          # colors for the current stack
          cols.ij <- let.col[unlist(strsplit(dat[i, j], ';'))]
          for (k in seq_along(cols.ij)) {
            rect(j + sep/2, 
                 # rectangle's top goes down for subsequent colors in the stack
                 i + (1-sep) * (k-1) / length(cols.ij) + sep/2, 
                 j + 1 - sep/2, 
                 i + 1 - sep/2, 
                 col=cols.ij[k])
          }
        }
      }
    }
    

    We could use legend() for legend but its customizability is limited and doing it by hand is not so much work:

    # legend top left coordinates
    leg.x <- ncol(dat) + 1.4
    leg.y <- 1 + mar/2
    # size of the square in legend
    leg.sq <- .4
    # space between squares in legend
    leg.sep <- mar
    
    for (i in seq_along(let.col)) {
      rect(leg.x, leg.y, leg.x + leg.sq, leg.y + leg.sq, col=let.col[i])
      text(leg.x + leg.sq*1.4, leg.y + leg.sq/2, names(let.col)[i])
      leg.y <- leg.y + leg.sq + leg.sep
    }
    

    Stacked colors in a grid

    To add a column with notes you can modify the code like this:

    par(mar = rep(0, 4))
    frame()
    plot.window(c(0, ncol(dat) + 3), c(nrow(dat) + 1, 0), asp=1)
    col.labels <- c(paste0('S', seq_len(ncol(dat))), 'Note')
    notes <- c('D1', 'note2%', 'D3', 'D4')
    
    for (i in 0:nrow(dat)) {
      for (j in 0:(ncol(dat)+1)) {
        # first row -- Sx labels
        if (i == 0) {
          if (j > 0) text(j + .5, .5, col.labels[j])
        # first column -- Gx labels
        } else if (j == 0) {
          if (i > 0) text(.5, i + .5, paste0('G', i))
        # stacked colours
        } else {
          # colors for the current stack
          if (j <= ncol(dat)) {
            cols.ij <- let.col[unlist(strsplit(dat[i, j], ';'))]
            for (k in seq_along(cols.ij)) {
              rect(j + sep/2, 
                   # rectangle's top goes down for subsequent colors in the stack
                   i + (1-sep) * (k-1) / length(cols.ij) + sep/2, 
                   j + 1 - sep/2, 
                   i + 1 - sep/2, 
                   col=cols.ij[k])
            }
          # notes
          } else {
            text(j + .3, i + .5, notes[i], adj=0)
          }
        }
      }
    }
    

    with notes column

    This is without the legend but it can be added the same way as before, just increase leg.x to make space for the notes.