7

When using ggplot on objects with large number of groups (e.g. n > 14), most palettes are not able to cope. The function colorRampPalette is able to extend the range, but to embed it in the scale_fill_manual, you would need to know the number of unique groups.

Is there a way to automatically extract the number of colours required from ggplot directly? I have provided an example below, and to make the palette work, I have had to add the number of groups (14) as an argument to scale_fill_manual(values = palette_Dark2(14)).

Plot using scale_fill_brewer

df <- data.frame(group = paste0("Category", 1:14),
             value = 1:28)

library(ggplot2)
ggplot(df, aes(x = group, y = value, fill = group)) + 
  geom_bar(stat = "identity") +
  scale_fill_brewer(palette = "Set1")

Warning message: In RColorBrewer::brewer.pal(n, pal) : n too large, allowed maximum for palette Set1 is 9 Returning the palette you asked for with that many colors

enter image description here

Plot using custom palette

Here I have specified a new colour palette before the plot, which is a colour ramp with 14 steps.

library(RColorBrewer)
palette_Dark2 <- colorRampPalette(brewer.pal(14, "Dark2"))

I then use this in the plot as follows:

ggplot(df, aes(x = group, y = value, fill = group)) + 
  geom_bar(stat = "identity") +
  scale_fill_manual(values = palette_Dark2(14))

enter image description here

Update:

I tried to use length(unique(group)) to extract the number of unique groups but obtained an error message

Error in unique(group) : object 'group' not found

In the attempt of using length(unique(df$group)), the error message is:

Error: Insufficient values in manual scale. 14 needed but only 1 provided. In addition: Warning message: In brewer.pal(length(unique(df$group)), "Dark2") :n too large, allowed maximum for palette Dark2 is 8 Returning the palette you asked for with that many colors

Michael Harper
  • 14,721
  • 2
  • 60
  • 84
Phil Wu
  • 141
  • 1
  • 6
  • 2
    Couldn't you put a `length(unique(df$group))` into the `brewer.pal()` command? Like `colorRampPalette(brewer.pal(length(unique(df$group)), "Dark2"))` – Felix Mar 05 '18 at 15:40
  • I have to second Felix's comment above. This method is feasible and straightforward. Also worth noting, however, that in these examples, the color is actually redundant information (groups are already discriminated based on the x aesthetic), which should typically be avoided in figures such as these. That might not be the case with your real data, but just an observation. – C-x C-c Mar 05 '18 at 15:46
  • I feel there must be a more robust way of doing this within ggplot. This would work well but would need changing if you ever updated the variable used for the grouping. – Michael Harper Mar 05 '18 at 15:47
  • the `length(unique(group))` would return error message of "object 'group' not found". – Phil Wu Mar 05 '18 at 15:51

4 Answers4

4

I wasn't 100% happy with my last answer, so this one is done in a more ggplot fashion. Digging through the code for scales within the ggplot package, there is the discrete_scale function which can be used:

library(ggplot2)
ggplot(df, aes(x = group, y = value, fill = group)) + 
  geom_bar(stat = "identity") +
  discrete_scale("fill", "manual", palette_Dark2)

This code will automatically extract the number of groups from the specified aesthetic, in this case "fill". There is no need to manually specify the number of groups, and seems fairly robust. For example, increasing to include 20 group:

df <- data.frame(group = paste0("Category", 1:20),
                 value = 10) 

ggplot(df, aes(x = group, y = value, fill = group)) + 
  geom_bar(stat = "identity") +
  discrete_scale("fill", "manual", palette_Dark2)

enter image description here

Michael Harper
  • 14,721
  • 2
  • 60
  • 84
2

This is an option, adapting the suggestion from Felix above. First, the palette is made before the plot, with the number of groups extracted from the dataframe:

palette_Dark2 <- colorRampPalette(brewer.pal(14, "Dark2"))
pal <- palette_Dark2(length(unique(df$group)))

ggplot(df, aes(x = group, y = value, fill = group)) + 
  geom_bar(stat = "identity") +
  scale_fill_manual(values = pal)

It still is not perfect, as the palette has to be specified before and requires that the group is selected before the plot. I am hoping that there is a more elegant way that ggplot could just pick this out automatically.

Note: I presume the code is a minimal working example, but the colour specification is actually redundant information in this example, as it provides nothing more than the x axis already displays.

Michael Harper
  • 14,721
  • 2
  • 60
  • 84
  • Thanks for writing this up Mikey! I share your concern as this is not the most elegant solution but it should work. I don't think a proper ggplot2-way of doing this is out there, as you'd need to access the column name from within the `scale` function which is difficult. Interestingly, I just found that a conceptually similar problem received a similar answer: https://stackoverflow.com/questions/35279570/assign-point-color-depending-on-data-frame-column-value-r – Felix Mar 05 '18 at 16:03
  • Also, could you add @Quant18's concern about the proper use of color in that case in your answer? I think that's a relevant note of caution. – Felix Mar 05 '18 at 16:05
  • 1
    Shame there appears to be nothing cleaner :( added the information, my guess is that it is a MWE though. – Michael Harper Mar 05 '18 at 16:11
  • 1
    @Felix, think I have found a ggplot friendly solution by using functions directly from `scale`. Check my other answer – Michael Harper Mar 06 '18 at 14:46
1

You could do this inside ggplot2 by using scale_fill_identity and a left_join to get the column of fill colors mapped to the levels of group. This solution seems hacky and inelegant to me, but it is a way to create a color palette within ggplot2 that has the right number of colors.

ggplot(df, aes(x = group, y = value, 
               fill=left_join(data.frame(group), 
                              data.frame(group=unique(group),
                                         fill=palette_Dark2(length(unique(group)))))$fill)) + 
  geom_bar(stat = "identity") +
  scale_fill_identity()
eipi10
  • 91,525
  • 24
  • 209
  • 285
  • It works. Thanks @eipi10.. The only problem I found is that legend cannot be added using this method. – Phil Wu Mar 05 '18 at 16:53
  • 1
    You could do `scale_fill_identity(guide="legend")`, but then the legend labels are just the color codes, which isn't useful. I don't know of a way to get a properly labeled legend without reaching outside the ggplot call to the original data frame, but the whole point of this exercise (AFAICT) was to avoid that. It looks like @MikeyHarper's second answer is the way to go. – eipi10 Mar 06 '18 at 16:33
1

Another possibility is to simply put the entire creation of the palette in scale_fill_manual

ggplot(df, aes(x = group, y = value, fill = group)) + 
  geom_bar(stat = "identity") +
  scale_fill_manual(values = colorRampPalette(brewer.pal(8, "Dark2"))(length(unique(df$group))))

If you do this repeatedly and want to avoid typing of 'df' and 'group' twice, just wrap it in a function, and use aes_string:

f <- function(df, x, y, group){
  ggplot(df, aes_string(x = x, y = y, fill = group)) + 
    geom_bar(stat = "identity") +
    scale_fill_manual(values = colorRampPalette(brewer.pal(8, "Dark2"))(length(unique(df[[group]]))))
}

f(df, "x", "y", "group")
Henrik
  • 65,555
  • 14
  • 143
  • 159