9

I would like to plot a heatmap like this

enter image description here

I know how to do a normal heatmap in R but am unsure how the 3D component can be introduced. I thought about just using a 3d bar chart but then I am not sure how to conditionally set the bar colour. Can somebody recommend a tool to do something like this? Another example would be here

but there it is not coloured according to the heatmap colours.

This might go also by the name of 3D histogram. Is there a way to produce such a figure in R (where the hight of the boxes is given by 1 variable and the colour formatting indicated by another ?) like here

My problem with JTT solution is that I would need to be able to colour the 3D Bars independently of the VA Deaths variable. I have a 2D heatmap (which do already set the colours for each 3D bar). The height of the bar is then set by another variable. This means the colour is not related to the height.

9
  • 3
    do you really want that? It's a terrible plot, you're hiding some data behind the taller bars, and it's almost impossible to compare the heights in this perspective. Commented Jul 30, 2014 at 15:12
  • 1
    although that is a valid point I do have to present it in that way...or say I was told to do it in that way by a superior, what would be equal alternatives? Or is there a way to do this like I outlined? Commented Jul 30, 2014 at 15:19
  • 2
    although I agree with your point I am wondering whether there is a way to do that in R. In my data set most of the bars will be quite low except for a couple of bars in the middle which will be very high. Hence I thought it wouldn't be too bad Commented Jul 30, 2014 at 15:35
  • 1
    @baptiste: it is absolutely NOT a terrible plot because the idea is not to obtain accurate numerical comparisons but to get an idea of the topology of a multidimensional data set. The workflow for such data sets inevitably involves first getting an idea of the interesting bits, before "zooming in" with more accurate plots by slicing and projecting back into 2d, and it is only at that later stage that we want visual accuracy. First we need to figure out what's going on at a high "unaccurate" level. If we don't perform this crucial first step, we'll be looking for a needle in a haystack. Commented Jul 14, 2016 at 19:27
  • 1
    @baptiste yes ultimately we are working on a 2d medium (the screen) so the 3d plot is a projection onto 2d and is unable, mathematically, to convey more information than the colour map. However, the human brain is much more sensitive to height information than colour information, and so my experience is that rotatable 3-d barplot is a useful tool as it has a more intuitive mapping to the magnitudes. This is especially true when the task at hand is to convey information to non-scientists, a frequent requirement in industry and business. Colour is a more distant abstraction than size. Commented Jul 16, 2016 at 13:25

2 Answers 2

5

3D barchart might be a way to go. There's panel.3dbars() in the package latticeExtra that you might want to test. See the function's help page for more examples, but here's one example modified from one of the examples on the help page:

library(latticeExtra) # A function generating colors cols<-function(n) { colorRampPalette(c("#FFC0CB", "#CC0000"))(20) # 20 distinct colors } # The plot cloud(VADeaths, panel.3d.cloud = panel.3dbars, col="white", # white borders for bars xbase = 1, ybase = 1, zlim = c(0, max(VADeaths)), # No space around the bars scales = list(arrows = FALSE, just = "right"), xlab = NULL, ylab = NULL, col.facet = level.colors(VADeaths, at = do.breaks(range(VADeaths), 20), col.regions = cols, # color ramp for filling the bars colors = TRUE), colorkey = list(col = cols, at = do.breaks(range(VADeaths), 20)), screen = list(z = 65, x = -65)) # Adjust tilting 

The resulting is similar to:

enter image description here

Note that the data to be plotted needs to be turned into a matrix for this to work. If you have measurement from X*Y grid, where Z is the intensity of the measurement, this should be rather straightforward to pull off. The functions here (e.g., level.colors()) automatically decides the color according to the data range, but you can also generate the colors yourself, before plotting.

Sign up to request clarification or add additional context in comments.

8 Comments

hey...that looks nice but I dont really see a way how to incorporate the heatmap colour coding? In your example the colour of the 3D Bars depends on the age axis. How could I just let the colour of a specific 3D bar be determined by a vector of colours or something like this?
The colors in this example are dependent on the z variable (death rates) that is dependent on age. The higher the death rate, the darker the red color. You can define the colors by hand, if you must, but in general it works best to let R decide the colors for the bars. A notable exception is the case where z takes only few distinct values. See the second example in ?panel.3dbars. It uses terrain.colors for coloring the bars. If you want the "heatmap colors" you might want to substitute that by heat.colors.
my problem is that I would need to be able to colour the 3D Bars independently of the third variable. I have a 2D heatmap (which do already set the colours for each 3D bar). The height of the bar is then set by another variable. This means the colour is not related to the height.
If you need to set the colors by hand, then you can give argument col.facet just a simple vector of colors. Figuring the right order of the colors in the vector is sometimes a bit painful (or then its just me), but in this particular example substituting the existing argument with col.facet = rep(1:4, each=5) would color each column of the data with a separate color.
By the way, there needs to be exactly one color per bar in the plot. If there are less colors than bars, the colors will get reused , which creates "nice" anomalies in the plot.
|
5

Here's another solution using persp to generate a 3d perspective and then drawing rectangles to generate bars. A lot of lines, but pretty flexible. You need to provide a data matrix (data) and a color matrix ( colmat).

# generate data, random + linear trend in x + linear trend in y data = matrix(data = runif(n = 100, min = 0, max = 1), nrow=10, ncol = 10, dimnames=list(paste0('x',1:10),paste0('y',1:10))) data = sweep(x = data, MARGIN = 1, 10:1, FUN = '+') data = sweep(x = data, MARGIN = 2, 1:10, FUN = '+') # generate 'empty' persp plot pmat = persp(x=c(0,10), y=c(0,10), z=matrix(c(0,.1,0,.1), nrow=2), xlim=c(0,10), ylim=c(0,10), zlim=c(0,20), xlab='x', ylab='y', zlab='z', theta=60, phi=20, d=2, box=F) # define color ramp my_cols = heat.colors(10) # generate color matrix (values between 1 and 10, corresponding to 10 values my_cols colmat = matrix(data = 1, ncol = 10, nrow = 10) colmat[1,1:10] <- 5 colmat[5,2:4] <- 8 colmat[6,8] <- 3 # draw each bar: from left to right ... for (i in 1:nrow(data)){ # ... and back to front for (j in ncol(data):1){ xy = which(data == data[i,j], arr.ind=TRUE) # side facing y x = rep(xy[1],4) y = c(xy[2]-1,xy[2],xy[2],xy[2]-1) z = c(0,0,data[i,j],data[i,j]) polygon(trans3d(x, y, z, pmat), col=my_cols[colmat[i,j]], border=1) # side facing x x = c(xy[1]-1,xy[1],xy[1],xy[1]-1) y = rep(xy[2]-1,4) z = c(0,0,data[i,j],data[i,j]) polygon(trans3d(x, y, z, pmat), col=my_cols[colmat[i,j]], border=1) # top side x = c(xy[1]-1,xy[1],xy[1],xy[1]-1) y = c(xy[2]-1,xy[2]-1,xy[2],xy[2]) z = rep(data[i,j],4) polygon(trans3d(x, y, z, pmat), col=my_cols[colmat[i,j]], border=1) } } # define axis ranges etc x.axis <- 1:ncol(data) - 0.5 min.x <- 0 max.x <- 10 y.axis <- 1:nrow(data) - 0.5 min.y <- 0 max.y <- 10 z.axis <- seq(0, 10, by=10) min.z <- 0 max.z <- 10 # add some distance between tick labels and the axis xoffset = 1 yoffset = 0.5 zoffset = 0.5 ticklength = 0.2 # x axis ticks tick.start <- trans3d(x.axis, min.y, min.z, pmat) tick.end <- trans3d(x.axis, (min.y - ticklength), min.z, pmat) segments(tick.start$x, tick.start$y, tick.end$x, tick.end$y) # y axis ticks tick.start <- trans3d(max.x, y.axis, min.z, pmat) tick.end <- trans3d(max.x + ticklength, y.axis, min.z, pmat) segments(tick.start$x, tick.start$y, tick.end$x, tick.end$y) # z axis ticks tick.start <- trans3d(min.x, min.y, z.axis, pmat) tick.end <- trans3d(min.x, (min.y - ticklength), z.axis, pmat) segments(tick.start$x, tick.start$y, tick.end$x, tick.end$y) # x labels labels <- rownames(data) label.pos <- trans3d(x.axis, (min.y - xoffset), min.z, pmat) text(label.pos$x, label.pos$y, labels=labels, adj=c(0, NA), srt=0, cex=0.6) # y labels labels <- colnames(data) label.pos <- trans3d((max.x + yoffset), y.axis, min.z, pmat) text(label.pos$x, label.pos$y, labels=labels, adj=c(0, NA), srt=0, cex=0.6) # z labels labels <- as.character(z.axis) label.pos <- trans3d(min.x, (min.y - zoffset), z.axis, pmat) text(label.pos$x, label.pos$y, labels=labels, adj=c(1, NA), srt=0, cex=0.6) 

enter image description here

4 Comments

is it somehow possible to label the axes with the row and column names of the data matrix (x an y ) and the z with something like a scale from 0 to 1?
what do you mean with offsets, when I replicate your code I have the labels with the ticks on the lin of the box in which the box plot is drawn as well as additional lines parallel to the x and y and z of the box pic-upload.de/view-24082805/Rplot05.png.html . Is there a way to remove the large box (as in the example you show above). I just re-entered your code ]
in the x .is there a way for the x labels to be not angeled but just to be straight away from the x ? So that they do not overlap with each other
Sorry, my mistake, had to do edits in a hurry. Now axis labels should be fine and box should be gone. Angles can be changed by adjusting the srt argument (String RoTation, I guess). I updated the values so they're all horizontal now. You can change them to any value between 0 and 360 to see what's best.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.