具有命名向量的list参数的override.aes

时间:2019-05-07 14:32:35

标签: r ggplot2

据我所知,使用override.aes时,需要根据图例的顺序对list参数中的值进行“硬编码”。 可以改用命名向量吗?

示例:

library(ggplot2)
set.seed(1)
df1 <- data.frame(x = 1:25, y_line = rnorm(25, 1, 0.01))

p <- ggplot(df1, aes(x, y_line)) +
      geom_line(aes(colour = 'line')) +
      geom_point(aes(y = 1, colour = 'point')) + 
      geom_line(aes(y = 1, color = 'point'), linetype = 'dotted') 

# This specifies values by order:
p + guides(colour = guide_legend(override.aes = list(linetype = c('dotted', 'solid'), shape = c(NA, 16))))

# Attempt to use named vectors does not change the output 
p + guides(colour = guide_legend(override.aes = list(linetype = c(point = 'dotted', line = 'solid'), shape = c(NA, 16))))

两个结果都相同:

使用带引号的名称时相同。

reprex package(v0.2.1)于2019-05-07创建

2 个答案:

答案 0 :(得分:1)

很抱歉,但是我想我可能确实错过了您要使用此功能的目的,因此,如果我的解决方法不能直接满足您的需要,我深表歉意。

我不确定是否可以在guides函数中完成此操作,但是您可以做的是将guide_legend()scale_colour_*结合使用来预先设置顺序breaks。我认为这会模仿您的预期行为:

p <- ggplot(df1, aes(x, y_line)) +
  geom_line(aes(colour = 'line')) +
  geom_point(aes(y = 1, colour = 'point')) +
  geom_line(aes(y = 1, colour = 'point'), linetype = 'dotted') +
  scale_colour_discrete(
    breaks = c("point", "line"),
    guide = guide_legend(override.aes = list(linetype = c("dotted", "solid"),
                                             shape = c(16, NA)))
    )

enter image description here

答案 1 :(得分:1)

注意:我尚未对它进行彻底的测试,并且当hack与ggplot2中的其他未观察到的功能进行交互时,可能会出现无法预料的问题。程序包的内部工作原理可能很难理解,但是希望这可以提供一个起点...

图例构建部分位于ggplot2:::build_guides(未导出的函数)内。如您所见,override.aes中命名向量的名称在此过程中将被忽略。一种可能的解决方法是在函数中插入一些代码,以正确的顺序(基于图例标签)获得命名的矢量。我还添加了默认美学参数的检查,以便在某些情况下我们可能只希望覆盖一个或两个标签的美学,而其余参数保留默认值。

这是要插入的代码。我只尝试过线型,形状和大小。随便说一下,线型是我想到的唯一带有数字和分类值的情况,因此这是下面default.aes涵盖的特定情况。

# define a function that completes each element in the override.aes list if
# it's a named vector, by arranging it in the order used by the legend labels,
# & replacing any unsupplied value with the latest (based on most recent layer) 
# default aesthetic value for that specific element
complete.override.aes <- function(gdef, default.aes){
  override.aes <- gdef$override.aes
  if(!any(sapply(override.aes, function(x) !is.null(names(x))))){
    return(gdef)
  }
  key.label <- gdef$key$.label
  for(i in seq_along(override.aes)){
    if(!is.null(names(override.aes[[i]]))){
      x <- override.aes[[i]][key.label]
      default.x <- default.aes[[names(override.aes)[[i]]]]
      if(!is.na(default.x)){
        x <- dplyr::coalesce(x,
                             rep(default.x,
                                 times = length(key.label)))
      }
      names(x) <- NULL
      override.aes[[i]] <- x
    }
  }
  gdef$override.aes <- override.aes
  gdef
}

# extract default aes associated with each layer in ggplot object,
# combine, & remove duplicates (keep latest where applicable)
default.aes <- sapply(layers, function(x) x$geom$default_aes)
default.aes <- purrr::flatten(default.aes)
default.aes <- default.aes[!duplicated(default.aes, fromLast = TRUE)]
# for linetype (if applicable), map from numeric to string
if(!is.null(default.aes[["linetype"]]) &
   is.numeric(default.aes[["linetype"]])){
  if(default.aes[["linetype"]] == 0) default.aes[["linetype"]] <- 7
  default.aes[["linetype"]] <- c("solid", "dashed", "dotted",
                                 "dotdash", "longdash", "twodash",
                                 "blank")[default.aes[["linetype"]]]
}

gdefs <- lapply(gdefs, complete.override.aes, default.aes)

要使用此代码,请运行trace(ggplot2:::build_guides, edit = TRUE)并将上面的代码插入第32行之后(即return(zeroGrob())之后和gdefs <- guides_merge(gdefs)之前)。

(或者,我们可以将代码插入上述函数的自己的版本中,并将其命名为build_guides2,定义ggplot2:::ggplot_gtable.ggplot_built的修改版本,而不是ggplot2:::build_guides调用该代码,然后是ggplot2:::print.ggplot的修改版本,它调用的是那个而不是ggplot_gtable。但是,这种方法很快变得笨拙,占用了大量空间,并且与该主题相关在手边,所以我在这里不做详细介绍。)

结果:

# correct mapping for linetype
p + guides(colour = guide_legend(
             override.aes = list(linetype = c(point = 'dotted', line = 'solid'), 
                                 shape = c(NA, 16))))

plot

# both linetype & shape use named vectors, & specify one value each
# (otherwise linetype defaults to "solid" & shape to 19)
p + guides(colour = guide_legend(
             override.aes = list(linetype = c(point = 'dotted'), 
                                 shape = c(line = 8))))

plot 2