lapply'd geoms lose factor-ordering

Viewed 116

In ggplot2 – issue with overlay of lines and errorbars, I suggested an answer that used lapply(.) to produce groups of geoms in a particular order, so that points/lines/errorbars for each level would be layered together. However, it appears that the factor order of levels is lost in the process.

This first code block generates the original plot: the order of elements in the legend is correct (but the geom layering is not, this is before my answer).

d2 <- structure(list(time_serie = c(1.3, 2.3, 3.3, 1.2, 2.2, 3.2, 1.1, 2.1, 3.1), treatment = structure(c(3L, 3L, 3L, 2L, 2L, 2L, 1L, 1L, 1L), .Label = c("HIGH", "MEDIUM", "LOW"), class = "factor"), mean_value = c(2.93173234758433, 5.60600521659944, 7.85452806402463, 5.25617444992531, 3.6695183776319, 4.57195128872991, 3.24979097663891, 4.59766399173532, 4.39298335579224), SE_value = c(0.232090045905285, 0.585377662916667, 0.679289569404838, 1.3130008364543, 0.849157470954342, 1.22194305280708, 1.21458843275054, 1.0620028602709, 0.949469468240659 )), row.names = c(NA, -9L), class = "data.frame")

library(ggplot2)
ggplot(aes(x = time_serie, y = mean_value, color = treatment, group = treatment), data = d2) +
  geom_errorbar(aes(ymin = mean_value - SE_value, ymax = mean_value + SE_value),
                width = 0.2, size = 2) +
  geom_point(aes(), size = 3) + 
  geom_line(aes(color = treatment), size = 2)

ggplot2 with correct order of 'treatment'

The answer I suggested uses lapply to control the order of the layers. However, in doing so, the order of treatment levels is lost:

ggplot(aes(x = time_serie, y = mean_value, color = treatment, group = treatment), data = d2) +
  lapply(rev(levels(d2$treatment)), function(trtmnt) {
    list(
      geom_errorbar(data = ~ subset(., treatment == trtmnt),
                    aes(ymin = mean_value - SE_value, ymax = mean_value + SE_value),
                    width = 0.2, size = 2),
        geom_point(data = ~ subset(., treatment == trtmnt), aes(), size = 3),
        geom_line(data = ~ subset(., treatment == trtmnt), size = 2)
    )
  })

ggplot2, order of levels is lost

Note that the legend has the levels in the incorrect order.

levels(d2$treatment)
# [1] "HIGH"   "MEDIUM" "LOW"   

The answer to "how to change legend element order" is almost always "use factor(., levels=.)", but that's been done and it is ignored.

Why is the order lost, and more importantly how can we retain the order in the legend (when layers are added as lists-of-geoms)?

(PS: if you have comments on how to better control layering, please discuss it in the previous question. This question is based on the requirement of using lapply for some reason to generate geoms, and therefore how to prevent that process from dropping factor levels. Thanks!)

1 Answers

Not sure about the details. So I would say this only a partial answer as it lacks a detailed explanation. But at least it offers a possible solution. My guess is that it has to do with argument drop=TRUE being the default for discrete scales. Hence, one option to keep the order in the legend would be to do ggplot(...) + scale_color_discrete(drop = FALSE) + lapply(...):

library(ggplot2)

ggplot(aes(x = time_serie, y = mean_value, color = treatment, group = treatment), data = d2) +
  scale_color_discrete(drop = FALSE) +
  lapply(rev(levels(d2$treatment)), function(trtmnt) {
    list(
      geom_errorbar(data = ~ subset(., treatment == trtmnt),
                    aes(ymin = mean_value - SE_value, ymax = mean_value + SE_value),
                    width = 0.2, size = 2),
      geom_point(data = ~ subset(., treatment == trtmnt), aes(), size = 3),
      geom_line(data = ~ subset(., treatment == trtmnt), size = 2)
    )
  })

Related