Reputation: 378
Could you help me understand how Quasiquotation works? I'm using map and count function but it doesn't seem to work properly.
First try:
map(names(starwars),~starwars %>% count(.x))
Error: Column `.x` is unknown
Second try:
map(names(starwars),~starwars %>% count(!!.x))
#not useful [[1]]
# A tibble: 1 x 2
`"name"` n
<chr> <int>
1 name 87
[[2]]
# A tibble: 1 x 2
`"height"` n
<chr> <int>
1 height 87
Third try:
map(names(starwars),~starwars %>% count(!!!.x))
# the same
Another example (working on functions):
If I want to make a function that takes a list and the changes each element of the list with regard to the previous element on that list:
my_list <- list("a" =1 , "b" = 2, "c" = 3)
# this obviously is not working (list + number)
> my_list+1
Error in my_list + 1 : non-numeric argument to binary operator
# this is a bit strange
my_list %>% map(~+1)
#this works fine
my_list %>% map(+1)
# as this
my_list %>% map(~.x+1)
# moving on to add the previous element to the next element
imap(my_list, my_list[[.y +1]] := .x %>% +1)
Error in `:=`(my_list[[.y + 1]], .x %>% +1) : could not find function ":="
# wrong eval 1?
imap(my_list, my_list[[.y +1]] <- .x %>% +1)
Error in eval(lhs, parent, parent) : object '.x' not found
# wrong eval 2?
imap(my_list, my_list[[.y +1]] <- !!.x %>% +1)
Error in eval(lhs, parent, parent) : object '.x' not found
# wrong symbol 1?
imap(my_list, my_list[[.y +1]] = .x %>% +1)
Error: unexpected '=' in "imap(my_list, my_list[[.y +1]] ="
Upvotes: 2
Views: 377
Reputation: 816
I think this question can be decomposed into a section on quasi-quotation and anoth on map
functions.
First, ~ starwars %>% count(.x))
is shorthand for and a slightly more complicated version of function(.x){starwars %>% count(.x)}
. So I'm going to work with the functions directly.
Second, names(starwars)
gives you a character vector.
So to avoid the confusion that map
brings let's start with functions and pass them the character "eye_color".
dplyr
functions treat symbols as if they are columns in the tbldplyr
functions are nice when doing interactive data analysis, because they allow us to refer to columns with symbols. I recommend reading:
https://dplyr.tidyverse.org/articles/programming.html for more info.
func <- function(.x) { starwars %>% count(.x) }
func("eye_color")
Error: Column `.x` is unknown
In your first attempt, this leads to a problem, because .x
is symbol, so R thinks .x
is column in starwars
.
count()
/ group_by()
expect symbols not character input.!!
takes .x
and replaces it with "eye_color". But "eye_color" is not symbol/name but rather a character.
func_2 <- function(.x) { starwars %>% count(!!.x) }
func_2("eye_color")
# A tibble: 1 x 2
`"eye_color"` n
<chr> <int>
1 eye_color 87
This weird output is the result of grouping by a character. For whatever reason, dplyr
groups the whole dataframe as "eye_color" and then tells you there are 87 rows. starwars %>% count("hooray")
gives similar output.
A somewhat intuitive way to code dplyr
functions is to pass symbols/names and use {{.x}}
to evaluate the promise. (Less intuitively you can do !!enquo(.x)
.)
func_3 <- function(.x) { starwars %>% count({{.x}}) }
func_3(eye_color)
# A tibble: 15 x 2
eye_color n
<chr> <int>
1 black 10
2 blue 19
3 ...
This works!
func_4 <- function(.x) { .x = as.symbol(.x)
starwars %>% count({{.x}}) }
func_4("eye_color")
# A tibble: 15 x 2
eye_color n
<chr> <int>
1 black 10
2 blue 19
3 ...
This also works!
map
Before I continue, I think nniloc's solution is better for your problem.
But you could use map as follows
starwars %>%
select_if(negate(is.list)) %>%
names() %>%
map(function(.x) {x = as.symbol(.x)
starwars %>% count( {{ x }} )
})
or
starwars %>%
select_if(negate(is.list)) %>%
names() %>%
map(as.symbol) %>%
map(function(.x) {
starwars %>% count( {{ .x }} )
})
When you use the ~
notation, .x
is now a "pronoun" that refers to the symbols directly, so we can use !!
to access the symbols directly. (I don't fully understand this).
starwars %>%
select_if(negate(is.list)) %>%
names() %>%
map(as.symbol) %>%
map(~ starwars %>% count( !! .x ))
Regarding imap()
, it looks like you want to code in python (or some other language with iteration). imap()
is short hand for map2(.x, names(.x), ...)
so is distinct from enumerate()
in python. There are R functions like seq_along
which give you position in an object, but I haven't used those with map.
Upvotes: 3