如何在带有 tidyr 和 ggplot2 的函数中使用 dplyr 的 enquo 和 quo_name

How to use dplyr's enquo and quo_name in a function with tidyr and ggplot2

library(dplyr) #Devel version, soon-to-be-released 0.6.0
library(tidyr)
library(ggplot2)
library(forcats) #for gss_cat data

我正在尝试编写一个函数,将即将发布的 dplyr 开发版本的 quosures 与 tidyr::gatherggplot2 结合起来。到目前为止,它似乎适用于 tidyr,但我在绘图时遇到了问题。

以下函数似乎适用于 tidyr's gather

GatherFun<-function(gath){
  gath<-enquo(gath)

  gss_cat%>%select(relig,marital,race,partyid)%>%
    gather(key,value,-!!gath)%>%
    count(!!gath,key,value)%>%
    mutate(perc=n/sum(n))
}

但我不知道如何让情节发挥作用。我尝试将 !!gathggplot2 一起使用,但没有用。

GatherFun<-function(gath){
  gath<-enquo(gath)

  gss_cat%>%select(relig,marital,race,partyid)%>%
    gather(key,value,-!!gath)%>%
    count(!!gath,key,value)%>%
    mutate(perc=n/sum(n))%>%
    ggplot(aes(x=value,y=perc,fill=!!gath))+
       geom_col()+
       facet_wrap(~key, scales = "free") +
       geom_text(aes(x = "value", y = "perc", 
                     label = "perc", group = !!gath),
                 position = position_stack(vjust = .05))
}

为了完成这项工作,我不得不使用 dplyr::quo_name 将 quosure 更改为字符串。我还必须使用 ggplot2::aes_string,这也要求所有输入都是字符串,因此用 "" 引用。

GatherFun <- function(gath){
  gath <- enquo(gath)
  gathN <- quo_name(gath)

  gss_cat %>% 
    select(relig, marital, race, partyid) %>%
    gather(key, value, -!!gath) %>%
    count(!!gath, key, value) %>%
    mutate(perc = round(n/sum(n), 2)) %>%
    ggplot() +
    geom_col(aes_string(x = "value", y = "perc", fill = gathN)) +
    facet_wrap(~key, scales = "free") +
    geom_text(aes_string(x = "value", y = "perc", label = "perc", group = gathN), 
              position = position_stack(vjust = .05))
}

我觉得主要的问题是 ggplot 在尝试计算 !!gath!(!gath) 时贪婪,因为 not(gath) 没有意义而抛出错误。当我尝试使用 !! 时,这个问题经常出现,所以我有点厌倦了以糖形式使用它。

如果更精确的人能够正确识别问题,那肯定会有所帮助。

gather_func = function(gath) {

  gath = enquo(gath)

  gss_cat %>%
    select(relig, marital, race, partyid) %>%
    gather(key, value, -!!gath) %>%
    count(!!gath, key, value) %>%
    mutate(perc = round(n/sum(n), 2)) %>%
    ggplot(aes(x = value, y = perc, fill = eval(rlang::`!!`(gath)))) +
    geom_col() + 
    facet_wrap(~key, scales = "free") +
    geom_text(
      aes(
        x = value, 
        y = perc, 
        label = perc, 
        group = eval(rlang::`!!`(gath))
      ),
      position = position_stack(vjust = .05)
    )
}

你在问题中写的函数调用似乎有一些错误。适当地间隔您的代码将有助于避免这种情况。

您也没有使用 rlang 调用,我只是没有安装最新的 dplyr 版本。

编辑 使用更简单的 mtcars 示例的一些想法:

Tbh 我不太确定这里发生了什么,但我想这与 ggplot2 现在相对较旧并且设计略有不同的事实有关?用debug步入aes,我们发现类似于

的结构
structure(list(x = mpg, y = eval(rlang::UQE(var))), .Names = c("x", 
"y"), class = "uneval")

(这不会 运行 通过解释器,但大致是结构的样子)。我认为这说明了为什么 eval 调用在这里是必要的,o/w ggplot 试图将 rlang::UQE(var) 映射到 y 美学并报告它不知道如何处理class name 的东西。 eval 将名称评估为 cyl,然后可以正常映射审美。

我想 dplyr 动词没有这个额外的映射步骤,其中参数以相同的方式被操纵到一些中间结构中,所以我们没有这个问题。

此外,当我说您不必使用 rlang 调用时,那是因为我假设此函数已重新导出到新的 dplyr 版本中。由于我之前提到的整个 !!(...)!(!(...)),我更喜欢使用 rlang::"!!"rlang::UQE(我相信它们完全等同)。

不过大部分都是猜测,如果有人可以纠正我的任何错误,我们将不胜感激。

我最近在别处回答了这个问题 (Use dplyr SE with ggplot2)。不确定如何标记重复项,所以我会在这里重复。

If you are already handling quosures the syntax is cleaner if you use aes_ rather than aes_string.

这段代码应该适用于您的示例。请注意,所有硬编码变量(value、perc、key)都用 tilda 引用,而直接使用 quosure(gath)。

ggplot(aes_(x = ~value, y = ~perc, fill = gath) +
  geom_col() +
  facet_wrap(~key, scales = "free") +
  geom_text(aes_(x = ~value, y = ~perc, label = ~perc, group = gath),
            position = position_stack(vjust = .05))

现在可以在 ggplot2 v3.0.0 中的 aes 中使用 tidy 求值。因此不再需要aes_string

# install.packages("ggplot2", dependencies = TRUE)

library(tidyverse) 

GatherFun2 <- function(gath) {

  gath <- enquo(gath)

  gss_cat %>% 
    select(relig, marital, race, partyid) %>%
    gather(key, value, -!! gath) %>%
    count(!!gath, key, value) %>%
    mutate(perc = round(n/sum(n), 2)) %>%
    ggplot() +
      geom_col(aes(x = value, y = perc, fill = !! gath)) +
      facet_wrap(~ key, scales = "free") +
      xlab(NULL) +
      geom_text(aes(x = value, y = perc, 
                    label = ifelse(perc == 0, "", perc), 
                    group = !! gath), 
                position = position_stack(vjust = .2)) +
      theme(legend.position = "bottom",
            axis.text.x = element_text(angle = 90, hjust = 1.0)) 
}

GatherFun2(marital)