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

回眸只為那壹抹淺笑 提交于 2019-11-30 06:49:14

I feel like the main problem is ggplot is greedy when it tries to evaluate !!gath and does !(!gath), throwing an error as not(gath) has no meaning. I've has this issue crop up a lot when I've tried to use !! so I'm kinda weary about using it in its sugar form.

If someone more precise could correctly identify the problem it would definitely be helpful.

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)
    )
}

There seems to be a few mistakes in the function call you wrote in the question. properly spacing your code will help avoid that.

You also don't have you use the rlang call, I just don't have the newest dplyr version installed.

EDIT Some thoughts using a simpler mtcars example:

Tbh I'm quite unsure of what's going on here, but I imagine it's to do with the fact the ggplot2 is relatively old now and has a slightly different design? Stepping into aes with debug, we find a structure similar to

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

(This won't run through the interpreter but is roughly what the structure looks like). I think this shows why the eval call is necessary here, o/w ggplot is trying to map rlang::UQE(var) to the y aesthetic and reports it doesn't know what to do with something of class name. eval evaluates the name to, say, cyl, then the aesthetic can be mapped as normal.

I imagine dplyr verbs don't have this extra mapping step where the arguments are manipulated into some intermediate structure in the same way, so we don't have this issue.

Also, when I said you don't have to use the rlang call, it was because I assumed this function was re-exported into the new dplyr version. Because of the whole !!(...) or !(!(...)) thing I mentioned earlier, I prefer to use rlang::"!!", or rlang::UQE (which are exactly equivalent I believe).

Most of this is speculation though and if someone could correct me on anything I've got wrong it would be appreciated.

Mike

In order to make this work I had to use dplyr::quo_name to change the quosure into a string. I also had to use ggplot2::aes_string, which also requires all the inputs to be strings, and therefore quoted with "".

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))
}

It's now possible to use tidy evaluation inside aes in ggplot2 v3.0.0. Thus aes_string is no longer needed.

# 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)

I answered this question elsewhere recently (Use dplyr SE with ggplot2). Not sure how to mark duplicates so I will repeat here.

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

This bit of code should work in your example. Notice that all the hard-coded variables (value, perc, key) are quoted with a tilda while the quosure (gath) is used directly.

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))
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!