4

I wonder if there is the possibility to change the fill main colour according to a categorical variable

Here is a reproducible example

df = data.frame(x = c(rnorm(10, mean = 0),
                  rnorm(10, mean = 3)),
            y = c(rnorm(10, mean = 0),
                  rnorm(10, mean = 3)),
            grp = c(rep('a', times = 10),
                    rep('b', times = 10)),
            val = rep(1:10, times = 2))

ggplot(data = df,
       aes(x = x,
           y = y)) +
  geom_point(pch = 21,
             aes(color = grp,
                 fill = val,
                 size = val))

And this is the output

Of course it is easy to change the circle colour/shape, according to the variable grp, but I'd like to have the a group in shades of red and the b group in shades of blue. I also thought about using facets, but don't know if the fill gradient can be changed for the two panels.

Anyone knows if that can be done, without gridExtra?

Thanks!

Emiliano
  • 149
  • 1
  • 9
  • You could try to plot first group in blue as it is, then add whole new layer like: `+geom_point(data=df[group==b],aes(...),fill=val, col="red")` roughly :) so idea (most likely suboptimal) is to plot new data from new data and then set the colour to be red.. – Jan Sila Aug 11 '16 at 11:11
  • Not working: the `col` argument defines the color of the shape, not the main fill theme – Emiliano Aug 11 '16 at 11:18

2 Answers2

3

I think there are two ways to do this. The first is using the alpha aesthetic for your val column. This is a quick and easy way to accomplish your goal but may not be exactly what you want:

ggplot(data = df,
       aes(x = x,
           y = y)) +
  geom_point(pch = 21,
             aes(alpha=val,
                 fill = grp,
                 size = val)) + theme_minimal()

enter image description here

The second way would be to do something similar to this post: Vary the color gradient on a scatter plot created with ggplot2. I edited the code slightly so its not a range from white to your color of interest but from a lighter color to a darker color. This requires a little bit of work and using the scale_fill_identity function which basically takes a variable that has the colors you want and maps them directly to each point (so it doesn't do any scaling).

This code is:

#Rescale val to [0,1]
df$scaled_val <- rescale(df$val)
low_cols <- c("firebrick1","deepskyblue")
high_cols <- c("darkred","deepskyblue4")

df$col <- ddply(df, .(grp), function(x)
  data.frame(col=apply(colorRamp(c(low_cols[as.numeric(x$grp)[1]], high_cols[as.numeric(x$grp)[1]]))(x$scaled_val),
                       1,function(x)rgb(x[1],x[2],x[3], max=255)))
)$col
df

ggplot(data = df,
       aes(x = x,
           y = y)) +
  geom_point(pch = 21,
             aes(
                 fill = col,
                 size = val)) + theme_minimal() +scale_fill_identity()

enter image description here

Community
  • 1
  • 1
Mike H.
  • 13,960
  • 2
  • 29
  • 39
  • The former solution is genial, and perfectly in line with the ggplot2 style. However it makes me wonder what would happen whit different ranges of val, say 1-10 for grp = a and 20-50 for grp = 50. Of course, comparing similar objects, this shouldn't happen and, if so, that would be a correct visualization. Another "issue" might be the absence of a legend: here you use the size as proxy of the fill (alpha) value. The very same problem you have in the second option, since using `guide = 'legend'` returns the entire list of hexadecimal values in the column `df$col`. – Emiliano Aug 11 '16 at 14:29
  • I'm using the `size` since you use it in your original code. It can easily be removed. In terms of the legend, what you're trying to do is not encouraged by `ggplot` (hence why its so hard to do) so if you wanted to do the latter, you would likely need to override the legend aesthetic or create one on your own - either way its very manual – Mike H. Aug 11 '16 at 14:42
1

Thanks to this other post I found a way to visualize the fill bar in the legend, even though that wasn't what I meant to do.

Here's the ouptup

enter image description here

And the code

df = data.frame(x = c(rnorm(10, mean = 0),
                      rnorm(10, mean = 3)),
                y = c(rnorm(10, mean = 0),
                      rnorm(10, mean = 3)),
                grp = factor(c(rep('a', times = 10),
                               rep('b', times = 10)),
                             levels = c('a', 'b')),
                val = rep(1:10, times = 2)) %>%
  group_by(grp) %>%
  mutate(scaledVal = rescale(val)) %>%
  ungroup %>%
  mutate(scaledValOffSet = scaledVal + 100*(as.integer(grp) - 1))

scalerange <- range(df$scaledVal)
gradientends <- scalerange + rep(c(0,100,200), each=2)

ggplot(data = df,
       aes(x = x,
           y = y)) +
  geom_point(pch = 21,
             aes(fill = scaledValOffSet,
                 size = val)) + 
  scale_fill_gradientn(colours = c('white',
                                   'darkred',
                                   'white',
                                   'deepskyblue4'),
                       values = rescale(gradientends))

Basically one should rescale fill values (e.g. between 0 and 1) and separate them using another order of magnitude, provided by the categorical variable grp. This is not what I wanted though: the snippet can be improved, of course, to make the whole thing less manual, but still lacks the simple usual discrete fill legend.

Community
  • 1
  • 1
Emiliano
  • 149
  • 1
  • 9