7

I'm looking to create multiple density graphs, to make an "animated heat map."

Since each frame of the animation should be comparable, I'd like the density -> color mapping on each graph to be the same for all of them, even if the range of the data changes for each one.

Here's the code I'd use for each individual graph:

ggplot(data= this_df, aes(x=X, y=Y) ) + 
    geom_point(aes(color= as.factor(condition)), alpha= .25) +
    coord_cartesian(ylim= c(0, 768), xlim= c(0,1024)) + scale_y_reverse() +
    stat_density2d(mapping= aes(alpha = ..level..), geom="polygon", bins=3, size=1)

Imagine I use this same code, but 'this_df' changes on each frame. So in one graph, maybe density ranges from 0 to 4e-4. On another, density ranges from 0 to 4e-2.

By default, ggplot will calculate a distinct density -> color mapping for each of these. But this would mean the two graphs-- the two frames of the animation--aren't really comparable. If this were a histogram or density plot, I'd simply make a call to coord_cartesian and change the x and y lim. But for the density plot, I have no idea how to change the scale.

The closest I could find is this:

Overlay two ggplot2 stat_density2d plots with alpha channels

But I don't have the option of putting the two density plots on the same graph, since I want them to be distinct frames.

Any help would be hugely appreciated!

EDIT:

Here's a reproducible example:

set.seed(4)
g = list(NA,NA)
for (i in 1:2) {

  sdev = runif(1)
  X = rnorm(1000, mean = 512, sd= 300*sdev)
  Y = rnorm(1000, mean = 384, sd= 200*sdev)

  this_df = as.data.frame( cbind(X = X,Y = Y, condition = 1:2) )

  g[[i]] = ggplot(data= this_df, aes(x=X, y=Y) ) + 
    geom_point(aes(color= as.factor(condition)), alpha= .25) +
    coord_cartesian(ylim= c(0, 768), xlim= c(0,1024)) + scale_y_reverse() +
    stat_density2d(mapping= aes(alpha = ..level.., color= as.factor(condition)), geom="contour", bins=4, size= 2) 

}
print(g) # level has a different scale for each
Community
  • 1
  • 1
jwdink
  • 4,824
  • 5
  • 18
  • 20
  • You're mapping `alpha` to the level, not color. You can control the alpha scale by adding `scale_alpha_continuous(limits=...)` where `limits` is a vector specifying the limits in units of `..level..`, so (0,4e-2) I suppose. If you provide your dataset, someone might be willing to give you more help. – jlhoward Jul 19 '14 at 22:45
  • Thanks for the response! I've added reproducible code with fake data (can't share the real data). Note that if you add, say `+ scale_alpha_continuous(limits= c(0, 2e-6))` to the end of this code, it makes the alpha scaling continuous, but does NOT fix the contours. How can I make the contour scaling consistent across the two graphs? – jwdink Jul 19 '14 at 23:10
  • Are you saying that you want to plot contours for the same values of ..level.. in both plots? If so, then use `breaks=...` in `stat_density2d(...)`. – jlhoward Jul 19 '14 at 23:37

3 Answers3

12

I would like to leave an update for this question. As of July 2016, stat_density2d is not taking breaks any more. In order to reproduce the graphic, you need to move breaks=1e-6*seq(0,10,by=2) to scale_alpha_continuous().

set.seed(4)
g = list(NA,NA)
for (i in 1:2) {
    sdev = runif(1)
    X = rnorm(1000, mean = 512, sd= 300*sdev)
    Y = rnorm(1000, mean = 384, sd= 200*sdev)
    this_df = as.data.frame( cbind(X = X,Y = Y, condition = 1:2) )

g[[i]] = ggplot(data= this_df, aes(x=X, y=Y) ) +
         geom_point(aes(color= as.factor(condition)), alpha= .25) +
         coord_cartesian(ylim= c(0, 768), xlim= c(0,1024)) +
         scale_y_reverse() +
         stat_density2d(mapping= aes(alpha = ..level.., color= as.factor(condition)),
         geom="contour", bins=4, size= 2) +
         scale_alpha_continuous(limits=c(0,1e-5), breaks=1e-6*seq(0,10,by=2))+
         scale_color_discrete("Condition")
    }

do.call(grid.arrange,c(g,ncol=2))
jazzurro
  • 23,179
  • 35
  • 66
  • 76
9

So to have both plots show contours with the same levels, use the breaks=... argument in stat_densit2d(...). To have both plots with the same mapping of alpha to level, use scale_alpha_continuous(limits=...).

Here is the full code to demonstrate:

library(ggplot2)
set.seed(4)
g = list(NA,NA)
for (i in 1:2) {
  sdev = runif(1)
  X = rnorm(1000, mean = 512, sd= 300*sdev)
  Y = rnorm(1000, mean = 384, sd= 200*sdev)
  this_df = as.data.frame( cbind(X = X,Y = Y, condition = 1:2) )

  g[[i]] = ggplot(data= this_df, aes(x=X, y=Y) ) + 
    geom_point(aes(color= as.factor(condition)), alpha= .25) +
    coord_cartesian(ylim= c(0, 768), xlim= c(0,1024)) + scale_y_reverse() +
    stat_density2d(mapping= aes(alpha = ..level.., color= as.factor(condition)), 
                   breaks=1e-6*seq(0,10,by=2),geom="contour", bins=4, size= 2)+
    scale_alpha_continuous(limits=c(0,1e-5))+
    scale_color_discrete("Condition")
}
library(gridExtra)
do.call(grid.arrange,c(g,ncol=2))

And the result...

jlhoward
  • 58,004
  • 7
  • 97
  • 140
  • 1
    This is the solution that I needed today. I ran the code and found that `stat_density2d` is not taking `breaks` with the current ggplot2 (ggplot2_2.1.0). Can you think of any other way to achieve the same effect? – jazzurro Jul 12 '16 at 00:33
  • Is it possible to assign your own computed densities elsewhere outside the stat_density function and use it in ggplot? – Adel Aug 16 '21 at 10:32
0

Not sure how useful this is, but I found it easier to either use:

scale_fill_gradient(low = "purple", high = "yellow", limits = c(0, 1000))

Where you can overwrite the limits of the plot easily, choose colors etc. and you can just add it at the end of your code so it'll overwrite most things it needs to, so it's easy to use

or a similar solution using: library(viridis)#colors for heat map

  scale_fill_viridis(option = 'inferno')+  
  scale_fill_viridis_c(limits = c(0, 1000))
Douwe
  • 112
  • 10
  • thanks for your contribution. It's not quite clear to me how this answers the question. Would you mind adding a complete plot code and embed the result please? give me a shout when done – tjebo May 30 '22 at 16:25
  • @tjebo in my case, I had 1 map (city of Chicago) and I wanted to show how often something happened. But I had different dataframes that had the data. Since their is a default scale if you don't specify, the heat maps would not be great for comparison. For example one would have a scale of 0-500 and another 0-1500. So the heat maps would mean something different. In this case adding ```scale_fill_viridis(option = 'inferno')+ scale_fill_viridis_c(limits = c(0, 1000))``` would give me the same scale for all the heatmaps. – Douwe Jun 05 '22 at 20:51
  • To add, I found it easier because I didn't have to deal with the `breaks=1e-6*seq(0,10,by=2)` stuff. But now I am curious if you'd agree, or if you'd say the other way is clearer/cleaner/more flexible or something like that – Douwe Jun 05 '22 at 20:53