10

I have the following data set (sample):

train <- data.frame(ps_ind_06_bin = c(FALSE, FALSE, FALSE, TRUE, TRUE, FALSE),
                        ps_ind_07_bin = c(FALSE, TRUE, TRUE, FALSE, TRUE, TRUE),
                        ps_ind_08_bin = c(TRUE, TRUE, TRUE, FALSE, TRUE, FALSE),
                        ps_ind_09_log = c(1, 3, 4, 2, 3, 2))

I have the following function that shows a ggplot for a group_by() operation:

get_charts1 <- function(mygroup){
  quo_var <- enquo(mygroup)
  train %>% 
    group_by(!!quo_var) %>% 
    count() %>%
    ungroup() %>%
  ggplot(aes_q(x = quo_var, y = quote(n), fill = quo_var)) + 
    geom_col() +
    theme(legend.position = "none")
    }

It works fine when I manually imput a column name, for example:

get_charts1(ps_ind_07_bin)

However, I want to use the function on several columns, which I put on a vector:

binarias <- train %>% 
             select(ends_with("bin")) %>% 
             colnames()

Using map and taking some suggestions, I tried to use:

listaplots <- map(quo(!!! syms(binarias)), get_charts1)

But this gives me the following error:

"Error: Can't splice at top-level"

Does anyone know what I need to do to get this to work?

Claus Wilke
  • 16,992
  • 7
  • 53
  • 104
Ramiro Bentes
  • 338
  • 1
  • 9
  • Looks like `map(quos(ps_ind_06_bin , ps_ind_07_bin ), get_charts1)` doesn't work so the problem isn't with expansion really. Seem like `map()` is just forcing evaluation of the parameters. – MrFlick Dec 18 '17 at 15:22
  • Capturing forced objects (including forced quosures or symbols) will work out of the box in the next rlang version. This will be equivalent to unquoting. – Lionel Henry Dec 18 '17 at 15:26
  • 1
    [What should I do when someone answers my question?](https://stackoverflow.com/help/someone-answers) – zx8754 Jan 05 '18 at 22:16

3 Answers3

16

I’m going to start by creating a reprex (you were very close, but forgot to load the needed packages), and re-style to a consistent format using styler:

library(tidyverse)
library(rlang)

train <- data.frame(
  ps_ind_06_bin = c(FALSE, FALSE, FALSE, TRUE, TRUE, FALSE),
  ps_ind_07_bin = c(FALSE, TRUE, TRUE, FALSE, TRUE, TRUE),
  ps_ind_08_bin = c(TRUE, TRUE, TRUE, FALSE, TRUE, FALSE),
  ps_ind_09_log = c(1, 3, 4, 2, 3, 2)
)

get_charts <- function(mygroup) {
  quo_var <- enquo(mygroup)
  train %>%
    group_by(!! quo_var) %>%
    count() %>%
    ungroup() %>%
    ggplot(aes_q(x = quo_var, y = quote(n), fill = quo_var)) +
    geom_col() +
    theme(legend.position = "none")
}

You want to automate the generation of code like this:

get_charts(ps_ind_06_bin)
get_charts(ps_ind_07_bin)
get_charts(ps_ind_08_bin)

That will require either a for loop or an apply/map function. A map() works well here since we want to return the ggplot2 objects, and doing that with a for loop requires some more infrastructure. It’s straightforward once you remember that you need to use symbols here, not raw strings

vars <- train %>% select(ends_with("bin")) %>% colnames()

vars %>%
  syms() %>%
  map(function(var) get_charts(!!var))

## [[1]]

## 
## [[2]]

## 
## [[3]]

hadley
  • 102,019
  • 32
  • 183
  • 245
2

Rather than map, I think you want invoke_map here. This seems to give what you want

listaplots  <- invoke_map(get_charts1, rlang::syms(binarias))

The map() seems to force evaluation of the parameters while invoke_map does not.

MrFlick
  • 195,160
  • 17
  • 277
  • 295
1

Change enquo() to sym() and your code works fine like this:

get_charts1 <- function(mygroup){
    quo_var <- sym(mygroup)  # <- HERE

    train %>% 
      group_by(!!quo_var) %>% 
      count() %>%
      ungroup() %>%
      ggplot(aes_q(x = quo_var, y = quote(n), fill = quo_var)) + 
      geom_col() +
      theme(legend.position = "none")
}

binarias <- train %>% select(ends_with("bin")) %>% colnames()

binarias %>% map(get_charts1)
Peter H.
  • 1,995
  • 8
  • 26