Reputation: 121
I am trying to write a function that makes use of an object's name (as in unevaluated symbol) for downstream application. Here is an example that captures the sense:
return_obj_name <- function(obj){
inp <- enquo(obj)
inp_name <- rlang::as_name(inp) # Use the name for something
inp_data <- rlang::eval_tidy(inp) # This line just for completeness, not important here
return(inp_name)
}
Here is a standard use case of this function:
test_obj <- 42
return_obj_name(test_obj)
[1] "test_obj"
So far, so good. However, I plan to use my function as an anonymous function in a map (or map2) statement, and this is where things go wrong.
test_obj2 <- 44
test_vec <- c(test_obj, test_obj2)
map(test_vec, ~ .x %>% return_obj_name())
[[1]]
[1] "."
[[2]]
[1] "."
The intended output would have been:
[[1]]
[1] "test_obj"
[[2]]
[1] "test_obj2"
I think I do understand what is happening. The function receives the piped reference to the initial object, which would be ".". It quotes this with enquo and continues as by design.
I am wondering if there is a way to with evaluate the reference in the environment in which map is called, as opposed to within the map call, as is happening now.
Upvotes: 2
Views: 279
Reputation: 206253
After you run
test_obj2 <- 44
test_vec <- c(test_obj, test_obj2)
The value test_vec
has no knowledge of the names of the variables that were used to create it. All it knows is that it's a numeric vector that contains 42 and 44. Tracking the source for every variable would create a lot of overhead.
It's important to remember that values do not have names in R; it's the names that have values. And it's not always unique either. Multiple names can point to the same value.
In addition, the pipe operator does not preserve variable names. Observe
test_obj %>% return_obj_name()
# [1] "."
If you want to keep track of labels for value sources, you should either use a named list (but remember, it's the collection that tracks the names, the elements in the collection are unaware if they are named) or have a separate vector of names. The answer given by @Ronak offers some good alternatives using this strategy.
Another alternative would be to store your values as a collection of quosures. For example
test_vec <- quos(test_obj, test_obj2)
map(test_vec, ~return_obj_name(!!.x))
But here test_vec
is storing those variable names, and not their necessarily their values. You would need to evaluate it to get the values 42 and 44.
Upvotes: 3
Reputation: 389012
The stand-alone example that you have shared does not match with the map
intended output. In the stand-alone example you run return_obj_name(test_obj)
and get output as "test_obj"
. Note that here the value of "test_obj"
is 42. But in the map
example your intended output is to return "42"
and "44"
instead of test_obj
and test_obj2
? One of this needs to change for the question to be consistent.
Anyway, as far as answer is concerned I think you should name your vector/list explicitly and pass that as separate object.
return_obj_name <- function(obj, name){
#Do something
#Do something
return(name)
}
For example, using tibble::lst
which makes it easy to name objects.
test_vec <- tibble::lst(test_obj, test_obj2)
You can then use imap
:
purrr::imap(test_vec, return_obj_name)
#$test_obj
#[1] "test_obj"
#$test_obj2
#[1] "test_obj2"
Or Map
in base R :
Map(return_obj_name, test_vec, names(test_vec))
If you want to return "42"
and "44"
i.e the values here that would be obj
value in return_obj_name
function.
Upvotes: 2