get names of quoted list without evaluation - r

I've got a quoted list
quote(list(orders = .N,
total_quantity = sum(quantity)))
(that I eventually eval in the j part of a data.table)
What I would like is to extract the names of that list without having to evaluate the expression because outside of the correct environment evaluating the expression will produce an error.

The list doesn't have any names at that point. It's not even a list. It's a call to the list() function. But that said you can parse that function call and extract name parameter. For example
x <- quote(list(orders = .N,
total_quantity = sum(quantity)))
names(as.list(x))[-1]
# [1] "orders" "total_quantity"
That as.list() on the expression turns the function call into a (named) list without evaluation.

Related

Why are functions only sometimes first class variables in R? Using them from built-in data structures causes "Error: attempt to apply non-function"

I am trying to understand how first class functions work in R. I had understood that functions were first class in R, but was sorely disappointed when applying that understanding. When a function is saved to a list, whether that be as an ordinary list, a vector or a dictionary style list or vector, it is no longer callable, leading to the following error:
Error: attempt to apply non-function
e.g.
print_func <- function() {
print('hi')
}
print_func()
[1] "hi"
my_list = list(print_func)
my_list[0]()
Error: attempt to apply non-function
my_vector = c(print_func)
my_vector[0]()
Error: attempt to apply non-function
my_map <- c("a" = print_func)
my_map["a"]()
Error: attempt to apply non-function
So why is this? Does R not actually treat functions as first class members in all cases, or is there another reason why this occurs?
I see that R vectors also do unexpected things (for me - perhaps not for experienced R users) to nested arrays:
nested_vector <- c("a" = c("b" = 1))
nested_vector["a"]
<NA>
NA
nested_vector["a.b"]
a.b
1
Here it makes sense to me that "a.b" might reference the sub-member of the key "b" under the key "a". But apparently that logic goes out the window when trying to call the upper level key "a".
R is 1-based; so, you refer to the first element of a vector using index 1 (and not 0 like in python).
There are two approaches to accessing list elements:
accessing list elements while keeping a list (return a list containing the desired elements)
pulling an element out of a list
In the first case, the subsetting is done using a single pair of brackets ([]) and you will always get a list back. Note that this is different from python where you get a list only if you select more than one element (lst = [fun1, fun2]; lst[0] return fun1 and not a one-element list like R while lst[0:2] returns a list).
In the second approach, the subsetting is done using a double pair of brackets ([[]]). you basically pull an element completely out of a list; more like subsetting one element out of a list in python.
print_func <- function() {
print('hi')
}
print_func()
my_list = list(print_func)
mode(my_list[1]) # return a list (not a function); so, it's not callable
[1] "list"
mode(my_list[[1]]) # return a function; so, it's callable
[1] "function"
my_list[1]() # error
my_list[[1]]() # works
[1] "hi"
#
my_vector = c(print_func)
mode(my_vector) # a list, so, not callable
[1] "list"
my_vector[1]() # error because it returns a list and not a function
my_vector[[1]]() # works
[1] "hi"
When subsetting with names, the same logic of single and double pair of brackets applies
my_map <- c("a" = print_func)
mode(my_map) # list, so, not callable
[1] "list"
my_map["a"]() # error
my_map[["a"]]() # works
[1] "hi"
Limey pointed out my 2 issues in the comments. I was using a 0-index and I was using single brackets. If I use a 1-index and double brackets it works, and functions are treated as first class variables.
My issue is resolved, and hopefully I won't make that same mistake again.

How to pass a list whose names is not NULL to the arguments of do.call in r

When I try to pass a list whose names is not NULL, I get the following error from evaluating do.call: Error: argument "x" is missing, with no default. Is there another way to bypass the names of the list and access instead the actual elements within the list without setting the names to NULL?
# with NULL names, do.call runs
num_list <- list(1:10)
do.call(mean,num_list)
# without names being NULL, do.call fails
names(num_list) <- 'a'
do.call(mean,num_list)
Specifically, I'd like to pass the list to a function's ellipsis such as for raster::merge, https://www.rdocumentation.org/packages/raster/versions/3.3-7/topics/merge.
library(rgdal)
library(sf)
library(raster)
cities <- sf::st_read(system.file("vectors/cities.shp", package = "rgdal"))
birds <- sf::st_read(system.file("vectors/trin_inca_pl03.shp", package = "rgdal"))
sf_shapes <- list(cities, birds)
# without names works
sf_shape_extents = lapply(sf_shapes, raster::extent)
sf_max <- do.call(what = raster::merge, args = sf_shape_extents)
# with names does not
names(sf_shapes) <- c('cities', 'birds')
sf_shape_extents_names = lapply(sf_shapes, raster::extent)
sf_max_names <- do.call(what = raster::merge, args = sf_shape_extents)
You either ensure that the names of the list being passed in corresponds to the parameters of the function, or that the list is unnamed and the position of the list elements corresponds to the position of the parameter in question.
names(num_list) <- 'x'
do.call(mean,num_list)
[1] 5.5
names(num_list) <- 'a'
do.call(mean,unname(num_list))
[1] 5.5
EDIT:
I do not see any structural change in your edited version. The error is because of the names since they do not correspond to the named parameters of the function. You are passing in named arguments and that will throw an error.
The question you need to ask yourself is what are the parameter names of the function you intend to use?
If the ellipsis of a function takes in unnamed parameters, then whether the passed in arguments are named or not, it does not matter. eg, the paste function in R:
a <- list(a="a",b=3,c="d");
do.call(paste,a)
[1] "a 3 d"

Extract names from a list using map() function

I already have a list contains all functions in dplyr by using this code
content <- mget(ls("package:dplyr"), inherits = TRUE)
dplyr_functions <- Filter(is.function, content)
The result I wanna get is just like the result of
names(dplyr_functions)
It will be a chr vector containing all function names in dplyr package.
But when I use map() function, my code is like:
dplyr_name <- map_chr(dplyr_functions, names)
There is an error said,
"Result 1 must be a single string, not NULL of length 0"
So I just want to know what the error mean? How can I use map_chr to get a vector containing all names in dplyr_functions?
map loop through the list element's content "value" e.g. dplyr_functions[[1]] and so on, not through the element as in dplyr_functions[1], try both to see the difference. Hence names(dplyr_functions[[1]]) returns NULL and map_chr fails, while names(dplyr_functions[1]) returns %>% and map_chr could work.
So we can loop through the list index and subset using the 2nd method or use imap which designed to loop through the list names.
library(purrr)
map_chr(seq_along(dplyr_functions), ~names(dplyr_functions[.x]))
#or
imap_chr(dplyr_functions, ~.y) %>% unname()

How to write a function for a variable name?

I have an object that is st
str(st)
List of 34
$ cell_ele : num [1:2000, 1:1000] 999 999 ...
Now I want to write a function
myfun <- function(var){
rt= st$var
rt=raster(rt)
out <- writeRaster(rt, filename = "C:\\var_data")
}
var will be used twice , to be read and then to be part of the output file name
myfun (cell_ele)
Error in raster(matrix(data = rt)) :
error in evaluating the argument 'x' in selecting a method for function
'data' must be of a vector type, was 'NULL'
I tried it without the function and it worked fine. The problem is in the function
Take a look at this command written inside the function's body:
rt= st$var
This will look for a column named var of the variable st. It will not substitute var with the contents of the variable given as an argument.
Instead, you should have written:
rt = st[var]
So please alter your function like so:
myfun <- function(var){
rt= st[var]
rt=raster(rt)
out <- writeRaster(rt, filename = paste("C:\\", var, "_data", sep=""))
}
which will do the substitution and look for a column whose name is defined by the argument you are passing to the function. We are also using the function paste, since we want to have a variable name:
paste converts its arguments (via as.character) to character strings, and concatenates them (separating them by the string given by sep). If the arguments are vectors, they are concatenated term-by-term to give a character vector result.
Also, you should pass a string argument:
myfun ("cell_ele")
The [[ form allows only a single element to be selected using integer or character indices, whereas [ allows indexing by vectors. Note though that for a list or other recursive object, the index can be a vector and each element of the vector is applied in turn to the list, the selected component, the selected component of that component, and so on. The result is still a single element.
The form using $ applies to recursive objects such as lists and pairlists. It allows only a literal character string or a symbol as the index. That is, the index is not computable: for cases where you need to evaluate an expression to find the index, use x[[expr]]. When $ is applied to a non-recursive object the result used to be always NULL: as from R 2.6.0 this is an error.

Convert character vector to numeric vector in R for value assignment?

I have:
z = data.frame(x1=a, x2=b, x3=c, etc)
I am trying to do:
for (i in 1:10)
{
paste(c('N'),i,sep="") -> paste(c('z$x'),i,sep="")
}
Problems:
paste(c('z$x'),i,sep="") yields "z$x1", "z$x1" instead of calling the actual values. I need the expression to be evaluated. I tried as.numeric, eval. Neither seemed to work.
paste(c('N'),i,sep="") yields "N1", "N2". I need the expression to be merely used as name. If I try to assign it a value such as paste(c('N'),5,sep="") -> 5, ie "N5" -> 5 instead of N5 -> 5, I get target of assignment expands to non-language object.
This task is pretty trivial since I can simply do:
N1 = x1...
N2 = x2...
etc, but I want to learn something new
I'd suggest using something like for( i in 1:10 ) z[,i] <- N[,i]...
BUT, since you said you want to learn something new, you can play around with parse and substitute.
NOTE: these little tools are funny, but experienced users (not me) avoid them.
This is called "computing on the language". It's very interesting, and it helps understanding the way R works. Let me try to give an intro:
The basic language construct is a constant, like a numeric or character vector. It is trivial because it is not different from its "unevaluated" version, but it is one of the building blocks for more complicated expressions.
The (officially) basic language object is the symbol, also known as a name. It's nothing but a pointer to another object, i.e., a token that identifies another object which may or may not exist. For instance, if you run x <- 10, then x is a symbol that refers to the value 10. In other words, evaluating the symbol x yields the numeric vector 10. Evaluating a non-existant symbol yields an error.
A symbol looks like a character string, but it is not. You can turn a string into a symbol with as.symbol("x").
The next language object is the call. This is a recursive object, implemented as a list whose elements are either constants, symbols, or another calls. The first element must not be a constant, because it must evaluate to the real function that will be called. The other elements are the arguments to this function.
If the first argument does not evaluate to an existing function, R will throw either Error: attempt to apply non-function or Error: could not find function "x" (if the first argument is a symbol that is undefined or points to something other than a function).
Example: the code line f(x, y+z, 2) will be parsed as a list of 4 elements, the first being f (as a symbol), the second being x (another symbol), the third another call, and the fourth a numeric constant. The third element y+z, is just a function with two arguments, so it parses as a list of three names: '+', y and z.
Finally, there is also the expression object, that is a list of calls/symbols/constants, that are meant to be evaluated one by one.
You'll find lots of information here:
https://github.com/hadley/devtools/wiki/Computing-on-the-language
OK, now let's get back to your question :-)
What you have tried does not work because the output of paste is a character string, and the assignment function expects as its first argument something that evaluates to a symbol, to be either created or modified. Alternativelly, the first argument can also evaluate to a call associated with a replacement function. These are a little trickier, but they are handled by the assignment function itself, not by the parser.
The error message you see, target of assignment expands to non-language object, is triggered by the assignment function, precisely because your target evaluates to a string.
We can fix that building up a call that has the symbols you want in the right places. The most "brute force" method is to put everything inside a string and use parse:
parse(text=paste('N',i," -> ",'z$x',i,sep=""))
Another way to get there is to use substitute:
substitute(x -> y, list(x=as.symbol(paste("N",i,sep="")), y=substitute(z$w, list(w=paste("x",i,sep="")))))
the inner substitute creates the calls z$x1, z$x2 etc. The outer substitute puts this call as the taget of the assignment, and the symbols N1, N2 etc as the values.
parse results in an expression, and substitute in a call. Both can be passed to eval to get the same result.
Just one final note: I repeat that all this is intended as a didactic example, to help understanding the inner workings of the language, but it is far from good programming practice to use parse and substitute, except when there is really no alternative.
A data.frame is a named list. It usually good practice, and idiomatically R-ish not to have lots of objects in the global environment, but to have related (or similar) objects in lists and to use lapply etc.
You could use list2env to multiassign the named elements of your list (the columns in your data.frame) to the global environment
DD <- data.frame(x = 1:3, y = letters[1:3], z = 3:1)
list2env(DD, envir = parent.frame())
## <environment: R_GlobalEnv>
## ta da, x, y and z now exist within the global environment
x
## [1] 1 2 3
y
## [1] a b c
## Levels: a b c
z
## [1] 3 2 1
I am not exactly sure what you are trying to accomplish. But here is a guess:
### Create a data.frame using the alphabet
data <- data.frame(x = 'a', y = 'b', z = 'c')
### Create a numerical index corresponding to the letter position in the alphabet
index <- which(tolower(letters[1:26]) == data[1, ])
### Use an 'lapply' to apply a function to every element in 'index'; creates a list
val <- lapply(index, function(x) {
paste('N', x, sep = '')
})
### Assign names to our list
names(val) <- names(data)
### Observe the result
val$x

Resources