12

My question is very similar to this and this and also this question. I have a scatterplot (using geom_point) coloured by a factor, using a particular colour palette. I'm using stat_smooth to draw certain smoothing lines through the points, grouped by another factor. I'd like these lines to use a different colour palette.

Here is a Dropbox link to some example data. Just do a

currDT <- read.table("SO_data", sep = "|", header = TRUE, strip.white = TRUE)

I usually have my data in a data.table, so you might find it helpful change that as well. Oh and here is the colour scheme I'm using at the moment, you can use scale_colour_brewer to generate your own, I'm just including this for completeness.

my_col_scheme <- c("#e41a1c", "#377eb8", "#4daf4a", "#984ea3", "#ff7f00", "#7B8A7B",
    "#0B29D6", "#f781bf", "#999999", "black")

Hopefully that is clear enough. Here is some example code:

icorr_elec <- ggplot(currDT,
                aes(x = EFP, y = SAPT), na.rm = TRUE) +
    geom_point(aes(colour = Anion, shape = Cation),  size = 3, alpha = 0.4) +
    scale_colour_manual(values = my_col_scheme) +
    stat_smooth(method = "lm", formula = y ~ x + 0, aes(linetype = Halide, colour = Halide), 
            alpha = 0.8, size = 0.5, level = 0) +
    scale_linetype_manual(name = "", values = c("dotdash", "F1"),
                      breaks = c("hal", "non-hal"), labels = c("Halides", "Non-Halides"))

How can this be done in ggplot2? From the other questions I gathered I could specify each line manually, but I'd like to avoid that.

Community
  • 1
  • 1
Samuel Tan
  • 1,700
  • 5
  • 22
  • 35
  • Would you mind adding a [reproducible example](http://stackoverflow.com/questions/5963269/how-to-make-a-great-r-reproducible-example)? Your question looks interesting, but I don't want to create a dummy dataset to take a look at it. – Richard Erickson Apr 30 '15 at 01:56
  • 2
    @RichardErickson thanks for your interest, if you need more information I'd be glad to provide it, just let me know. – Samuel Tan Apr 30 '15 at 02:18
  • Thanks for the example. As an FYI, I had to delete the space from `sep = "|"`. I don't know if it's my computer or some other error (I'm on a Mac right now). Also, out of curiosity, where are you getting this data from? Is it coming of an instrument? I've never seen a `|` used as a separator. – Richard Erickson Apr 30 '15 at 02:25
  • 2
    @RichardErickson No, I just wrote the data.table to a text file using `write.table`. I know that commas and whitespace are more common separators, but these sometimes appear in data, especially character/string types, whereas this is a much rarer character, hence I usually use it as a field separator. – Samuel Tan Apr 30 '15 at 02:32
  • I gave it my best shot below, but couldn't figure it out. Good luck. (BTW, I like `data.table` as well!). – Richard Erickson Apr 30 '15 at 03:24

3 Answers3

15

You can get separate color mappings for the lines and the points by using a filled point marker for the points and mapping that to the fill aesthetic, while keeping the lines mapped to the colour aesthetic. Filled point markers are those numbered 21 through 25 (see ?pch). Here's an example, adapting @RichardErickson's code:

ggplot(currDT, aes(x = EFP, y = SAPT), na.rm = TRUE) +
  stat_smooth(method = "lm", formula = y ~ x + 0, 
              aes(linetype = Halide, colour = Halide), 
              alpha = 0.8, size = 0.5, level = 0) +
  scale_linetype_manual(name = "", values = c("dotdash", "F1"),
                        breaks = c("hal", "non-hal"), labels = c("Halides", "Non-Halides")) +
  geom_point(aes(fill = Anion, shape = Cation),  size = 3, alpha = 0.4, colour="transparent") +
  scale_colour_manual(values = c("blue", "red")) +
  scale_fill_manual(values = my_col_scheme) +
  scale_shape_manual(values=c(21,24)) +
  guides(fill = guide_legend(override.aes = list(colour=my_col_scheme[1:8],
                                                 shape=15, size=3)),
         shape = guide_legend(override.aes = list(shape=c(21,24), fill="black", size=3)),
         colour = guide_legend(override.aes = list(linetype=c("dotdash", "F1"))),
         linetype = FALSE)

Here's an explanation of what I've done:

  1. In geom_point, change colour aesthestic to fill. Also, put colour="transparent" outside of aes. That will get rid of the border around the points. If you want a border, set it to whatever border color you prefer.
  2. In scale_colour_manual, I've set the colors to blue and red, but you can, of course, set them to whatever you prefer.
  3. Add scale_fill_manual to set the colors of the points using the fill aesthetic.
  4. Add scale_shape_manual and set the values to 21 and 24 (filled circles and triangles, respectively).
  5. All the stuff inside guides() is to modify the legend. I'm not sure why, but without these overrides, the legends for fill and shape are blank. Note that I've set fill="black" for the shape legend, but it's showing up as gray. I don't know why, but without fill="somecolor" the shape legend is blank. Finally, I overrode the colour legend in order to include the linetype in the colour legend, which allowed me to get rid of the redundant linetype legend. I'm not totally happy with the legend, but it was the best I could come up with without resorting to special-purpose grobs.

enter image description here

NOTE: I changed color=NA to color="transparent", as color=NA (in version 2 of ggplot2) causes the points to disappear completely, even if you use a point with separate border and fill colors. Thanks to @aosmith for pointing this out.

Community
  • 1
  • 1
eipi10
  • 91,525
  • 24
  • 209
  • 285
  • 1
    Good job! I'm impressed at the amount of hacking you had to do to make `ggplot` give you the plot you got. I tried using fill, but didn't realized I also had to change `color` to `NA`. – Richard Erickson Apr 30 '15 at 12:27
  • 1
    Thanks. Most of the work was in wrestling with the legend. You only need to include `colour=NA` if you don't want a border around the points. If you don't mind a border you can omit `colour=NA` and get a black border, or you can set the color to whatever you prefer. – eipi10 May 01 '15 at 01:09
  • 1
    Cheers! That was pretty smart, using `fill` instead of `colour`. I wouldn't have had played around with `guides` long enough to get the legend I want. This does the job. For now at least, because I'm always open to other methods. – Samuel Tan May 01 '15 at 05:39
  • 1
    Interestingly, using `fill` instead of `colour` for `geom_point` only works if you *also* specify `shape` either inside or outside `aes`. – p00ya Dec 15 '17 at 01:37
  • 1
    Yes, you have to use a point marker that has separate border and fill. Shape 21 through 25 have that characteristic. See the first paragraph and item 4 in my answer. – eipi10 Dec 15 '17 at 01:48
4

I don't think ggplot2 will let you change color twice and update the legend. I seem to recall reading that you cannot change scale_color_manual twice in a plot. I couldn't find that webpage, but this post touches on the topic. I also tried using the suggestions in this post, but that didn't work. Probably because we're trying to mix geoms (but that's just a guess).

I can get either the regression lines colored:

part1 <- 
    ggplot(currDT,
           aes(x = EFP, y = SAPT), na.rm = TRUE) +
    stat_smooth(method = "lm", formula = y ~ x + 0, 
                aes(linetype = Halide, colour = Halide), 
                alpha = 0.8, size = 0.5, level = 0) +
    scale_linetype_manual(name = "", values = c("dotdash", "F1"),
                          breaks = c("hal", "non-hal"), 
                          labels = c("Halides", "Non-Halides")) +
    scale_color_manual(name = "", values = c("red", 'blue'),
                       labels = c("Halides", "Non-Halides"))
    ggsave('part1.jpeg', part1)

enter image description here Or the data points to plot:

part2 <-           
    ggplot(currDT,
           aes(x = EFP, y = SAPT), na.rm = TRUE) +     
    geom_point(aes(color = Anion, shape = Cation),  size = 3, alpha = 0.4) +
    scale_linetype_manual(name = "", values = c("dotdash", "F1"),
                          breaks = c("hal", "non-hal"), 
                          labels =c("Halides", "Non-Halides")) +
    scale_colour_manual(values = my_col_scheme)   
    ggsave('part2.jpeg', part2)                      

enter image description here But not both:

both <- 
    ggplot(currDT,
           aes(x = EFP, y = SAPT), na.rm = TRUE) +
    stat_smooth(method = "lm", formula = y ~ x + 0, 
                aes(linetype = Halide, colour = Halide), 
                alpha = 0.8, size = 0.5, level = 0) +
    scale_linetype_manual(name = "", values = c("dotdash", "F1"),
                      breaks = c("hal", "non-hal"), labels = c("Halides", "Non-Halides")) +
    geom_point(aes(color = Anion, shape = Cation),  size = 3, alpha = 0.4) +
    scale_colour_manual(values = my_col_scheme)                    
ggsave('both.jpeg', both)   

enter image description here

I think you're going to need to add each line manual. Hopefully somebody else knows how to answer this, but I don't think @hadley allows both colors to changed. Luckily, you're data.table so it should be easy to do :-)

Comment to anybody else trying to solve this Hopefully my partial answer helps you to answer this question. Also, my third figure shows how ggplot2 isn't getting the legend color correct as the OP wants it. As another tip, you might try playing around with the legend options.

Community
  • 1
  • 1
Richard Erickson
  • 2,568
  • 8
  • 26
  • 39
  • print `part 1` with the halide/non-halide legend of the correct colours in the right place, then print `both` with that legend disabled, a transparent background, and the cation and halide legends positioned so that they don't overlap the first legend? – Scransom Apr 30 '15 at 03:32
  • wow thanks for your effort! yeah, that was basically where I ended up before I asked this question; by the way, which colours are ggplot2 using for the lines? it doesn't seem to be using `my_col_scheme` @geryan are you suggesting creating two plots then layering them one on top of the other? Can this be done in R itself or will we be looking at an outside image manipulation program? – Samuel Tan Apr 30 '15 at 03:38
  • @SamuelTan, yes, that is what I was suggesting, but I tried it with some of my own code and can't make it work, sorry. – Scransom Apr 30 '15 at 03:56
  • Yeah. I forgot to include you color code. I was changing it and forgot to add it back in. – Richard Erickson Apr 30 '15 at 12:24
4

For completeness sake - adding two scales for the same aesthetic is now easily possible with the ggnewscale package

tjebo
  • 21,977
  • 7
  • 58
  • 94
  • Wonderful, for me this is the most appropriate answer :) For future reference, it is as easy as loading `library(ggnewscale)` and inserting, e.g., `new_scale_colour()` in between the calls to _ggplot_ geoms. – Edward Mar 11 '22 at 12:19