canIchangethis
canIchangethis

Reputation: 197

How to set the shape used for different groups in an R ggplot?

I like to have different shapes depending on the values of some variables in R. Actually, a combination of variables. I like to give an exact shape number ( stored as s) to the combination.

I already tried to generate a new variable that sums the shape I want.

#gen some example code
c <- c('a', 'a', 'b', 'b')
d <- c('firstsecond', 'firstfirst', 'lowerupper', 'lowerlower')
e <- c(0.2, 0.3, 0.4, 0.5)
f <- c('w', 'v','w', 'v')
df <- cbind(c,d,e,f)
df<- as.data.frame(df)
df$e <- as.numeric(df$e)
orderd <- rev(c( 'firstfirst', 'firstsecond', 'lowerupper', 'lowerlower' ))
df<- within(df, d <- factor(d, levels=orderd))

My current solution approach is that I try to set shape in a variable s:

library(data.table)
    df <- setDT(df)
    df$c <- as.character(df$c)
    df$f <- as.character(df$f)
    df[c %chin% c('a') & f %chin% c('w') , s := 16  ]
    df[c %chin% c('a') & f %chin% c('v') , s := 1  ]
    df[c %chin% c('b') & f %chin% c('w') , s := 17  ]
    df[c %chin% c('b') & f %chin% c('v'), s := 2  ]

However all shapes are the same :( they do not differ by group, as I would like them to do.

#plotting it:
library(ggplot2)
    p<- ggplot(df, aes(x = d, y = e, color = f)) +
      geom_pointrange(aes(min = e - 1.95 * sqrt(e), max = e + 1.95 * sqrt(e)), shape = s) +
      theme_bw() + 
      facet_wrap(c ~ ., scales = "free", nrow = 5, strip.position = "left") +
      coord_flip() +
      scale_colour_viridis_d(begin = 0.75 , end = 0) +
      geom_text(aes(label = f), colour = "black", size = 2.5, hjust=1.05, vjust=1.2)
    p

Upvotes: 2

Views: 9670

Answers (1)

eipi10
eipi10

Reputation: 93861

The code below uses the pre-existing c and f columns to generate the shape mapping, without creating a separate s column. library(ggplot2)

#gen some example code
c <- c('a', 'a', 'b', 'b')
d <- c('firstsecond', 'firstfirst', 'lowerupper', 'lowerlower')
e <- c(0.2, 0.3, 0.4, 0.5)
f <- c('w', 'v','w', 'v')
df <- data.frame(c,d,e,f)

orderd <- rev(c( 'firstfirst', 'firstsecond', 'lowerupper', 'lowerlower' ))
df$d <- factor(df$d, levels=orderd)

ggplot(df, aes(x = d, y = e, color = f, shape=interaction(c,f))) +
  geom_linerange(aes(min = e - 1.95 * sqrt(e), max = e + 1.95 * sqrt(e)), size=1) +
  geom_point(aes(size=interaction(c,f))) +
  geom_text(aes(label = f), colour="white", size = 5, vjust=0.4) +
  facet_wrap(c ~ ., scales = "free", nrow = 5, strip.position = "left") +
  coord_flip() +
  scale_colour_viridis_d(begin = 0.75 , end = 0) +
  scale_shape_manual(values=15:18) +
  scale_size_manual(values=c(5,6,5,7)) +
  theme_bw() +
  labs(shape="Shape Title", colour="Colour Title") +
  guides(shape=guide_legend(override.aes=list(size=3)),
         colour=guide_legend(override.aes=list(size=3, linetype=0)),
         size=FALSE)

enter image description here

Note: In the code above, the size aesthetic is solely to make each shape marker roughly the same physical size. At any given size specification, the square and triangle markers look bigger than the circle and diamond markers. I used the size aesthetic to specify custom sizes (in scale_size_manual) for each shape. If you don't want to do this, or want to use different shapes, then you can remove the size aesthetic from the code.

If you decide you want the labels placed below, rather than within, the point markers, I suggest using the nudge arguments, rather than messing with vjust and hjust, which can give unpredictable results. For example:

geom_text(aes(label = f), colour = "black", size = 3, nudge_x = -0.2) +

UPDATE: Regarding your comment, to have a separate color scale for the text labels, we can use the ggnewscale package. Note in the code below that we now place the color aesthetics in the geoms to which they apply, rather than into the main call to ggplot:

library(ggnewscale)

ggplot(df, aes(x = d, y = e, shape=interaction(c,f))) +
  geom_linerange(aes(min = e - 1.95 * sqrt(e), max = e + 1.95 * sqrt(e), color = f), 
                 size=1) +
  geom_point(aes(size=interaction(c,f), color = f)) +
  facet_wrap(c ~ ., scales = "free", nrow = 5, strip.position = "left") +
  coord_flip() +
  scale_colour_viridis_d(name="Colour Title", begin = 0.75 , end = 0) +
  scale_shape_manual(values=c(0, 1, 17, 18)) +
  scale_size_manual(values=c(5,6,5,7)) +
  new_scale_colour() +
  geom_text(aes(label = f, colour=f), size = 5, vjust=0.4, show.legend=FALSE) +
  scale_colour_manual(values=c("black", "white")) +
  theme_bw() +
  labs(shape="Shape Title") +
  guides(shape=guide_legend(override.aes=list(size=3)),
         size=FALSE)

enter image description here

Another option for having more than one color scale is the relayer package. Note that ggplot is not inherently designed to have more than one scale for a given aesthetic and both of these packages are experimental.

Upvotes: 3

Related Questions