Mark Zurbrügg
Mark Zurbrügg

Reputation: 125

Making a combined legend for color and shape legend in ggplot

I have a question that I have been stuck on for a long time now. I want to make a ggplot that combines color and shape aesthetics.

for example let's consider the plot:

ggplot(mtcars, aes(x = mpg, y = hp, color = factor(carb),
   shape = factor(gear))) +
  geom_point(size = 10) +
  custom_theme_wrap

There are 3 different shapes and 6 different colors. When looking at

length(unique(paste(mtcars[[carb]], mtcars[[gear]], sep = "-"))) we get 11 different unique values.

Now in the plot we get a legend that is split into shape and color. However what I would want is a legend in which we get a unique shape AND color for each point. Giving me 11 legend items in total. So for example I would have a red triangle, red circle, red square and blue circle as my figure legend rather than 2 legends for shape and colour separately.

As far as I am aware such functionality does not exist in ggplot as it will not combine two different aesthetics to create a combined aesthetic.

I have managed to generate a unique set of colors and shapes for each data point as follows.

color_column <- "carb"
shape_column <- "gear"

available_shapes <- c(15, 17, 18, 19, 21, 22, 23, 24, 25)
unique_color <- unique(mtcars[[color_column]])
unique_shape <- unique(mtcars[[shape_column]])

# Generate evenly distributed colors
n_colors <- length(unique_color)
color_palette <- (hcl.colors(n_colors))
n_shapes <- length(unique_shape)
shape_palette <- available_shapes[1:n_shapes]

mtcars$combined_name <- paste(mtcars[[color_column]], mtcars[[shape_column]], sep = "-")


length(unique(paste(mtcars[[color_column]], mtcars[[shape_column]], sep = "-")))

name_colors <- setNames(color_palette[1:n_colors], unique_color)
name_shapes <- setNames(shape_palette[1:n_shapes], unique_shape)

mtcars<-
mtcars  %>% 
  mutate(color_id = ifelse(carb %in% names(name_colors),
   name_colors[as.character(carb)], "default_color"),
   shape_id = ifelse(gear %in% names(name_shapes),
   name_shapes[as.character(gear)], "default_shape"))

I can then use the color_id and shape_id to create a plot in base R. However, it again becomes very difficult to create a legend.

Is anyone aware of a way to merge shape and color aesthetics to create a ggplot?

Combine legends for color and shape into a single legend

Upvotes: 0

Views: 379

Answers (1)

Limey
Limey

Reputation: 12585

The accepted answer in the question you link to works only when the columns defining the two aesthetics you wish to define have the same values. That's not the case here. That's why the legends don't combine.

One way of combining two columns in a single aesthetic is to use the interaction function:

mtcars %>% 
  ggplot(
    aes(
      x = mpg, 
      y = hp,
      color = interaction(as.factor(carb), as.factor(gear)),
      shape = interaction(as.factor(carb), as.factor(gear))
    )
  )+
  geom_point()

which gives

enter image description here

together with two warnings:

Warning messages:
1: The shape palette can deal with a maximum of 6 discrete values because more than 6 becomes difficult to discriminate; you
have 11. Consider specifying shapes manually if you must have them. 
2: Removed 9 rows containing missing values (`geom_point()`). 

And the legend itself is very unsatisfactory. So you are going to have to do a bit of work to get what you want.

Start by obtaining the unique combinations of carb and gear that exist in mtcars:

legend <- mtcars %>% distinct(carb, gear)
legend
                  carb gear
Mazda RX4            4    4
Datsun 710           1    4
Hornet 4 Drive       1    3
Hornet Sportabout    2    3
Duster 360           4    3
Merc 240D            2    4
Merc 450SE           3    3
Porsche 914-2        2    5
Ford Pantera L       4    5
Ferrari Dino         6    5
Maserati Bora        8    5

Now determine the shape, colour and label that you want to represent each:

legend <- mtcars %>% distinct(carb, gear) %>% 
  mutate(
    shape = case_when(
      gear == 3 ~ 15,
      gear == 4 ~ 17,
      gear == 5 ~ 18
    ),
    colour = case_when(
      carb == 1 ~ "red",
      carb == 2 ~ "green",
      carb == 3 ~ "blue",
      carb == 4 ~ "orange",
      carb == 6 ~ "black",
      carb == 8 ~ "pink"
    ),
    label = paste0(carb, " carbs, ", gear, " gears")
  )
legend
                  carb gear shape colour            label
Mazda RX4            4    4    17 orange 4 carbs, 4 gears
Datsun 710           1    4    17    red 1 carbs, 4 gears
Hornet 4 Drive       1    3    15    red 1 carbs, 3 gears
Hornet Sportabout    2    3    15  green 2 carbs, 3 gears
Duster 360           4    3    15 orange 4 carbs, 3 gears
Merc 240D            2    4    17  green 2 carbs, 4 gears
Merc 450SE           3    3    15   blue 3 carbs, 3 gears
Porsche 914-2        2    5    18  green 2 carbs, 5 gears
Ford Pantera L       4    5    18 orange 4 carbs, 5 gears
Ferrari Dino         6    5    18  black 6 carbs, 5 gears
Maserati Bora        8    5    18   pink 8 carbs, 5 gears

Now you have all the information you need to construct your plot:

mtcars %>% 
  ggplot(
    aes(
      x = mpg, 
      y = hp,
      color = interaction(as.factor(carb), as.factor(gear)),
      shape = interaction(as.factor(carb), as.factor(gear))
    )
  ) +
  scale_colour_discrete(
    name = "Combined", 
    labels = legend %>% distinct(gear, label) %>% pull(label),
    type = legend %>% distinct(gear, colour) %>% pull(colour)
  ) +
  scale_shape_manual(
    name = "Combined", 
    labels = legend %>% distinct(carb, label) %>% pull(label),
    values = legend %>% distinct(carb, shape) %>% pull(shape)
  ) +
  geom_point()

giving

enter image description here

and no warnings.

[Personally, I'd map carb to colour and gear to shape because there are more distinct values of carb than gear and, as the warning message says, lots of shapes are hard to distinguish...]

Upvotes: 2

Related Questions