49

I would like to plot lines with different shapes with more than six sets of data, using discrete colors. The problems are 1) a different legend is generated for line color and shape, but should be only one legend with the line color and shape, 2) when correcting the title for the line color legend, the color disappear.

t=seq(0,360,20)
for (ip in seq(0,10)) {
  if (ip==0) {
    df<-data.frame(t=t,y=sin(t*pi/180)+ip/2,sn=ip+100)
  } else {
    tdf<-data.frame(t=t,y=sin(t*pi/180)+ip/2,sn=ip+100)
    df<-rbind(df,tdf)

  }
}
head(df)

# No plot
# Error: A continuous variable can not be mapped to shape
gp <- ggplot(df,aes(x=t,y=y,group=sn,color=sn,shape=sn))
gp <- gp + labs(title = "Demo more than 6 shapes", x="Theat (deg)", y="Magnitude")
gp <- gp + geom_line() + geom_point()
print(gp)

# No plot
# Error: A continuous variable can not be mapped to shape (doesn't like integers)
gp <- ggplot(df,aes(x=t,y=y,group=sn,color=sn,shape=as.integer(sn)))
gp <- gp + labs(title = "Demo more than 6 shapes", x="Theat (deg)", y="Magnitude")
gp <- gp + geom_line() + geom_point()
print(gp)

# Gives warning about 6 shapes, and only shows 6 shapes, continous sn colors
gp <- ggplot(df,aes(x=t,y=y,group=sn,color=sn,shape=as.factor(sn)))
gp <- gp + labs(title = "Only shows six shapes, and two legends, need discrete colors", 
                x="Theat (deg)", y="Magnitude")
gp <- gp + geom_line() + geom_point()
print(gp)

# This is close to what is desired, but correct legend title and combine legends
gp <- ggplot(df,aes(x=t,y=y,group=sn,color=as.factor(sn),shape=as.factor(sn %% 6)))
gp <- gp + labs(title = "Need to combine legends and correct legend title", x="Theat (deg)", y="Magnitude")
gp <- gp + geom_line() + geom_point()
print(gp)

# Correct legend title, but now the line color disappears
gp <- ggplot(df,aes(x=t,y=y,group=sn,color=as.factor(sn),shape=as.factor(sn %% 6)))
gp <- gp + labs(title = "Color disappeard, but legend title changed", x="Theat (deg)", y="Magnitude")
gp <- gp + geom_line() + geom_point()
gp <- gp + scale_color_manual("SN",values=as.factor(df$sn)) 
print(gp)

# Add color and shape in geom_line / geom_point commands, 
gp <- ggplot(df,aes(x=t,y=y,group=sn))
gp <- gp + labs(title = "This is close, but legend symbols are wrong", x="Theat (deg)", y="Magnitude")
gp <- gp + geom_line(aes(color=as.factor(df$sn))) 
gp <- gp + geom_point(color=as.factor(df$sn),shape=as.factor(df$sn %% 6))
gp <- gp + scale_color_manual("SN",values=as.factor(df$sn)) 
print(gp)
  • possible duplicate of [Changing shapes used for scale\_shape() in ggplot2](http://stackoverflow.com/questions/1478532/changing-shapes-used-for-scale-shape-in-ggplot2) – Henrik Oct 06 '14 at 20:32
  • 1
    I don't think this question is a duplicate of that question, he just talks about changing the shape over the range of shapes. My problem is that the number of shapes required exceeds the number of shapes available. This question is a duplicate of http://stackoverflow.com/questions/26223857/more-than-six-shapes-in-ggplot, but there is no answer for that question. –  Oct 06 '14 at 21:57

3 Answers3

76

First, it would be easier to convert sn to a factor.

df$sn <- factor(df$sn)

Then, you need to use scale_shape_manual to specify your shapes to use.

gp <- ggplot(df,aes(x=t, y=y, group=sn,color=sn, shape=sn)) +
             scale_shape_manual(values=1:nlevels(df$sn)) +
             labs(title = "Demo more than 6 shapes", x="Theat (deg)", y="Magnitude") +
             geom_line() + 
             geom_point(size=3)
gp

This should give you what you want. You need to use scale_shape_manual because, even with sn as a factor, ggplot will only add up to 6 different symbols automatically. After that you have to specify them manually. You can change your symbols in a number of ways. Have a look at these pages for more information on how: http://sape.inf.usi.ch/quick-reference/ggplot2/shape
http://www.cookbook-r.com/Graphs/Shapes_and_line_types/

enter image description here

Cotton.Rockwood
  • 1,601
  • 12
  • 29
  • 2
    This is closer, but it doesn't quite work on the real data. I still need the mod function, the real data has too many levels. Seems that scale_shape_manual(values=(1:nlevels(df$sn))%%Nmax) works. –  Oct 06 '14 at 21:49
  • Where Nmax is the number of shapes to display. –  Oct 06 '14 at 22:02
  • 1
    In the last example of the first link I included, it tells you a bit more about the available shapes. Note that shapes 21-25 are hollow and take a `fill` argument, so that would be another way to add distinct symbols. Good job sorting out the solution. – Cotton.Rockwood Oct 06 '14 at 22:29
  • if you wanna keep it a string (ggplot automatically makes it a factor anyway), then use 1:length(unique(df$sn)) – Fitzroy Hogsflesh Apr 29 '21 at 08:45
17

For me, the key to the error message about the 6 shapes is the part that says Consider specifying shapes manually..

If you add in the values in scale_shape_manual, I believe you'll get what you want. I made sn a factor in the dataset first.

df$sn = factor(df$sn)

ggplot(df, aes(x = t, y = y, group = sn, color = sn, shape = sn)) +
    geom_point() +
    geom_line() +
    scale_shape_manual(values = 0:10)

I go to the Cookbook for R site when I need to remember which numbers correspond to which shapes.

Edit The example above shows adding 11 symbols, the same number of symbols in your example dataset. Your comments indicate that you have many more unique values for the sn variable than in your example. Be careful with using a long series of numbers in values, as not all numbers are defined as symbols.

Ignoring whether it is a good idea to have so many shapes in a single graphic or not, you can use letters and numbers as well as symbols as shapes. So if you wanted, say, 73 unique shapes based on a factor with 73 levels, you could use 19 symbols, all upper and lower case letters, and the numbers 0 and 1 as your values.

scale_shape_manual(values = c(0:18, letters, LETTERS, "0", "1"))
aosmith
  • 34,856
  • 9
  • 84
  • 118
  • Thanks for the input. I think values either has to equal the number of lines (serial numbers), or the number of line factors. The actual data has more lines than there are symbols, so it went through the alphabet, then I think displayed no symbol. The values still needs a mod function to scale back to the range of symbols. –  Oct 06 '14 at 21:55
  • @user3969377 Wow, how many levels does `sn` have? If you really wanted this many different shapes, you can get 71 unique shapes if you use symbols 0:18 as well as all uppercase and lowercase letters. There are even more if you start adding numbers and special characters, but unsure how you would ever be able to to tell everything a part in a plot. – aosmith Oct 06 '14 at 22:44
  • Some of the figures have over 400 levels. I know it's not a good plot, I'm just looking for patterns... –  Oct 06 '14 at 22:49
  • Instead of letters, how could I get integers to replace the shape? I realize that letters and LETTERS are built in constants. Thank you for your useful answer - much farther than I got on my own! – gbrlrz017 May 20 '16 at 06:21
  • @gbrlrz017 You could use, e.g., `values = as.character(1:11)`. Just make sure the integers you want to use are given as characters in `scale_shape_manual`. – aosmith May 20 '16 at 14:40
  • Thanks @aosmith! For some reason it didn't work as expected in `ggplot(df, aes(x, y))+geom_point(aes(shape = z), size = 5)+scale_shape_manual(values = as.character(0:29))` where `df <- data.frame(x = runif(30),y = runif(30),z = factor(0:29))`. But I think I'll just settle with the upper and lower case letters. Thanks for your above answer, too! – gbrlrz017 May 24 '16 at 04:27
  • Is there a way mark shapes using unicode characters instead of standard ascii characters? – tmn Jan 27 '17 at 22:01
  • @tmn Possibly. See [here](http://stackoverflow.com/a/30743128/2461552) – aosmith Jan 27 '17 at 22:28
2

you can get about a hundred different shapes if you need them. good.shapes is a vector of the shape numbers that render on my screen without any fill argument.

library(ggplot2)
N = 100; M = 1000
good.shapes = c(1:25,33:127)
foo = data.frame( x = rnorm(M), y = rnorm(M), s = factor( sample(1:N, M, replace = TRUE) ) )
ggplot(aes(x,y,shape=s ), data=foo ) +
    scale_shape_manual(values=good.shapes[1:N]) +
        geom_point()
Nathan Siemers
  • 423
  • 4
  • 8