【问题标题】:Functions inside aesaes里面的函数
【发布时间】:2015-07-03 17:46:19
【问题描述】:

问题:为什么我不能在aes() 中调用sapply

下图的目标:创建显示死亡/活着的比例的直方图,以便组/类型的每个组合的比例总和为 1(示例受之前的 post 启发)。

我知道您可以通过在 ggplot 之外进行总结来得出这个数字,但问题实际上是为什么该函数在 aes 内部不起作用。

## Data
set.seed(999)
dat <- data.frame(group=factor(rep(1:2, 25)),
                  type=factor(sample(1:2, 50, rep=T)),
                  died=factor(sample(0:1, 50, rep=T)))

## Setup the figure
p <- ggplot(dat, aes(x=died, group=interaction(group, type), fill=group, alpha=type)) +
  theme_bw() +
  scale_alpha_discrete(range=c(0.5, 1)) +
  ylab("Proportion")

## Proportions, all groups/types together sum to 1 (not wanted)
p + geom_histogram(aes(y=..count../sum(..count..)), position=position_dodge())

## Look at groups
stuff <- ggplot_build(p)
stuff$data[[1]]

## The long way works: proportions by group/type
p + geom_histogram(
    aes(y=c(..count..[..group..==1] / sum(..count..[..group..==1]),
            ..count..[..group..==2] / sum(..count..[..group..==2]),
            ..count..[..group..==3] / sum(..count..[..group..==3]),
            ..count..[..group..==4] / sum(..count..[..group..==4]))),
        position='dodge'
)

## Why can't I call sapply there?
p + geom_histogram(
    aes(y=sapply(unique(..group..), function(g)
        ..count..[..group..==g] / sum(..count..[..group..==g]))),
        position='dodge'
)

get(as.character(FUN), mode = "function", envir = envir) 中的错误: 找不到模式'function'的对象'expr'

【问题讨论】:

  • sapply 通常在 aes 中工作,如以下(愚蠢的)示例所示:xy &lt;- data.frame(x=1:10,y=1:10) ggplot(xy, aes(x = x, y=sapply(y,mean))) + geom_line()。我的猜测是这与 ..count.. 和 ..group.. 通过 sapply 的作用域有关,或者 ..count.. 和 ..group.. 在 sapply 函数中被 aes 解析的方式有关,但我想不通。

标签: r ggplot2


【解决方案1】:

因此,问题的出现是因为递归调用 ggplot2:::strip_dots 以获取包括“计算美学”在内的任何美学。在this SO question and answer 中有一些关于计算美学的讨论。 layer.r中的相关代码在这里:

new <- strip_dots(aesthetics[is_calculated_aes(aesthetics)])

strip_dots 仅在使用正则表达式 "\\.\\.([a-zA-z._]+)\\.\\." 定义的计算美学时才被调用。

strip_dots in 采用递归方法,向下遍历嵌套调用并去除点。代码是这样的:

function (expr) 
{
    if (is.atomic(expr)) {
        expr
    }
    else if (is.name(expr)) {
        as.name(gsub(match_calculated_aes, "\\1", as.character(expr)))
    }
    else if (is.call(expr)) {
        expr[-1] <- lapply(expr[-1], strip_dots)
        expr
    }
    else if (is.pairlist(expr)) {
        as.pairlist(lapply(expr, expr))
    }
    else if (is.list(expr)) {
        lapply(expr, strip_dots)
    }
    else {
        stop("Unknown input:", class(expr)[1])
    }
}

如果我们提供一个匿名函数,代码如下:

anon <- as.call(quote(function(g) mean(g)))
ggplot2:::strip_dots(anon)

我们重现错误:

#Error in get(as.character(FUN), mode = "function", envir = envir) : 
#  object 'expr' of mode 'function' was not found

通过这个,我们可以看到 anon 是call。对于calls,strip_dots 将使用lapplycall 的第二个和第三个元素上调用strip_dots。对于这样的匿名函数,第二个元素是函数的formals。如果我们使用dput(formals(eval(anon)))dput(anon[[2]]) 查看anonformals,我们会看到:

#pairlist(g = )

对于pairlists,strip_dots 尝试将lapply 分配给它自己。我不确定为什么会有这段代码,但在这种情况下肯定会导致错误:

expr <- anon[[2]]
lapply(expr, expr)

# Error in get(as.character(FUN), mode = "function", envir = envir) : 
#  object 'expr' of mode 'function' was not found

TL; DR 在这个阶段,ggplot2 不支持在 aes 中使用匿名函数,其中使用了计算美学(例如 ..count..)。

不管怎样,使用dplyr可以达到想要的最终结果;一般来说,我认为它可以使代码更易读,从而将数据摘要与绘图分开:

newDat <- dat %>%
  group_by(died, type, group) %>%
  summarise(count = n()) %>%
  group_by(type, group) %>%
  mutate(Proportion = count / sum(count))

p <- ggplot(newDat, aes(x = died, y = Proportion, group = interaction(group, type), fill=group, alpha=type)) +
  theme_bw() +
  scale_alpha_discrete(range=c(0.5, 1)) +
  geom_bar(stat = "identity", position = "dodge")

ggplot2 修复

我已经分叉了 ggplot2 并对 aes_calculated.r 进行了两项更改,从而解决了这个问题。第一个是将pairlists 的处理更正为lapply strip_dots 而不是expr,我认为这一定是预期的行为。第二个是对于没有默认值的形式(如此处提供的示例),as.character(as.name(expr)) 引发错误,因为expr 是一个空名称,虽然这是一个有效的构造,但不可能从空字符串。

https://github.com/NikNakk/ggplot2 和拉取请求just made 的ggplot2 的分叉版本。

最后,毕竟,sapply 给出的示例不起作用,因为它返回一个 2 行乘 4 列的矩阵,而不是一个 8 长度的向量。修正后的版本是这样的:

p + geom_histogram(
    aes(y=unlist(lapply(unique(..group..), function(g)
        ..count..[..group..==g] / sum(..count..[..group..==g])))),
    position='dodge'
)

这给出了与上述dplyr 解决方案相同的输出。

另外需要注意的是,这个lapply 代码假定该阶段的数据是按组排序的。我认为情况总是如此,但如果由于某种原因不是这样,你最终会得到 y 数据乱序。保留计算数据中行顺序的替代方法是:

p + geom_histogram(
  aes(y={grp_total <- tapply(..count.., ..group.., sum);
  ..count.. / grp_total[as.character(..group..)]
  }),
  position='dodge'
)

还值得一提的是,这些表达式是在 baseenv()(基本包的命名空间)中计算的。这意味着来自其他包的任何函数,即使是像statsutils 这样的标准函数,都需要与:: 运算符一起使用(例如stats::rnorm)。

【讨论】:

【解决方案2】:

玩了一会儿,问题似乎是在 aes 中使用带有 ..group.. 或 ..count.. 的匿名函数:

xy <- data.frame(x=1:10,y=1:10) #data

ggplot(xy, aes(x = x, y = sapply(y, mean))) + geom_line() #sapply is fine

ggplot(xy, aes(x = x, group = y)) + 
       geom_bar(aes(y = sapply(..group.., mean))) #sapply with ..group.. is fine

ggplot(xy, aes(x = x, group = y)) + 
       geom_bar(aes(y = sapply(..group.., function(g) {mean(g)})))
#broken, with same error

ggplot(xy, aes(x = x, group = y)) + 
    geom_bar(aes(y = sapply(y, function(g) {mean(g)})), stat = "identity")
#sapply with anonymous functions works fine!

这似乎是一个非常奇怪的错误,除非我错过了一些愚蠢的东西。

【讨论】:

  • 我已经对此进行了进一步的研究,并将其归结为ggplot2 如何使用其strip_plots 函数处理计算美学(如..group..)。我曾一度认为这会对您的回答做出很好的评论,但正如您从我的回答中看到的那样,它的结束时间太长了!不过,您最初的调查工作确实有助于缩小问题范围。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-13
  • 1970-01-01
相关资源
最近更新 更多