I was wondering how I could have my R function foo return the names of the vectors that are inputted to it?
In this example, I want foo to return "a" and "b".
Here is what I tried without success:
a = 1:30 ; b = 50:60 # the inputted vectors
foo <- function(...){ # the function
L <- list(...)
names(L)
}
# Example of use:
foo(a, b)
Using substitute as shown gives a pairlist of symbols and deparse applied individually to each element converts each to a character string:
foo <- function(...) sapply(substitute(...()), deparse)
foo(a, b)
## [1] "a" "b"
foo <- function(...) as.character(substitute((...)))[-1]
foo(a, b)
# [1] "a" "b"
Here is an option with match.call
foo <- function(...) sapply(as.list(match.call())[-1], as.character)
foo(a, b)
#[1] "a" "b"
Related
This question already has answers here:
In R, how to get an object's name after it is sent to a function?
(4 answers)
Closed 2 years ago.
I find I often am comparing two character vectors to see where they don't match up (typically columns in two different data frames). Because I'm doing this often, I want to write a function to make it easier. This is what I've come up with so far:
x <- c("A", "B", "C")
y <- c("B", "C", "D", "X")
check_mismatch <- function(vec1, vec2) {
vec1 <- unique(as.character(vec1))
vec2 <- unique(as.character(vec2))
missing_from_1 <- vec2[vec2 %notin% vec1]
missing_from_2 <- vec1[vec1 %notin% vec2]
print("Missing from vector 1")
print(missing_from_1)
print("Missing from vector 2")
print(missing_from_2)
}
check_mismatch(x,y)
[1] "Missing from vector 1"
[1] "D" "X"
[1] "Missing from vector 2"
[1] "A"
What I would really like is "Missing from x" instead of "Missing from vector 1". I would like the function to output the name of the actual argument that was entered. Another example of how I would like the function to work:
check_mismatch(all_polygons_df$Plot, sb_year$Plot)
[1] "Missing from all_polygons_df$Plot"
[1] "KWI-1314B"
[1] "Missing from sb_year$Plot"
character(0)
Any suggestions on how I could do this? I'm open to other ways of displaying the output too - perhaps some kind of table. But the output needs to be flexible to different lengths of output.
Up front, deparse(substitute(...)) is what you're asking for, and that is what makes your initial question a duplicate.
Some recommendations, however:
printing things to the console is a little off (IMO), since it prepends [1] to everything you print. Consider message (or cat). Since many R environments color things based on comments, etc, I have found it useful to prepend # before some text to break it out from other portions of the same text.
Your function is operating solely in side-effect, printing something to the console and then losing it forever. The function does happen to return a single object (the value of missing_from_2, accidentally), but it might be more useful if the function returned the mismatches.
With that, I offer an alternative:
check_mismatch <- function(vec1, vec2) {
nm1 <- deparse(substitute(vec1))
nm2 <- deparse(substitute(vec2))
vec1 <- unique(as.character(vec1))
vec2 <- unique(as.character(vec2))
missing_from_1 <- vec2[!vec2 %in% vec1]
missing_from_2 <- vec1[!vec1 %in% vec2]
setNames(list(missing_from_1, missing_from_2), c(nm1, nm2))
}
check_mismatch(x, y)
# $x
# [1] "D" "X"
# $y
# [1] "A"
One immediate benefit is that we can look for specific differences in one of the vectors immediately:
mis <- check_mismatch(x, y)
mis$x
# [1] "D" "X"
However, this uses the names of the variables presented to it. Realize that with non-standard evaluation comes responsibility and consequence. Consider:
mis <- check_mismatch(x, c("A", "B", "E"))
mis
# $x
# [1] "E"
# $`c("A", "B", "E")`
# [1] "C"
The name of the second element is atrocious. Fortunately, if all you care about is what the differences are for the second element, once can still use [[2]] to retrieve the character vector without issue. (This is mostly aesthetic.)
mis[[2]]
# [1] "C"
Also, one might want to repeat this for more than two vectors, so generalizing it might be useful (for "1 or more"):
check_mismatch_many <- function(...) {
dots <- list(...)
if (!length(dots)) {
out <- list()
} else {
nms <- as.character(match.call()[-1])
out <- lapply(seq_along(dots), function(i) {
b <- unique(unlist(dots[-i]))
b[!b %in% dots[[i]]]
})
out <- replace(out, sapply(out, is.null), list(dots[[1]][0]))
names(out) <- nms
}
out
}
z <- c("Y","Z")
check_mismatch_many()
# list()
check_mismatch_many(x)
# $x
# character(0)
check_mismatch_many(x, y)
# $x
# [1] "D" "X"
# $y
# [1] "A"
check_mismatch_many(x, y, z)
# $x
# [1] "D" "X" "Y" "Z"
# $y
# [1] "A" "Y" "Z"
# $z
# [1] "A" "B" "C" "D" "X"
And finally, if you want to be a little "personal" with the presentation on the console, you can go overboard and class it with an additional print.myclass S3 method.
check_mismatch_many <- function(...) {
dots <- list(...)
if (!length(dots)) {
out <- list()
} else {
nms <- as.character(match.call()[-1])
out <- lapply(seq_along(dots), function(i) {
b <- unique(unlist(dots[-i]))
b[!b %in% dots[[i]]]
})
out <- replace(out, sapply(out, is.null), list(dots[[1]][0]))
names(out) <- nms
}
class(out) <- c("mismatch", "list")
out
}
print.mismatch <- function(x, ...) {
cat("<Mismatch>\n")
cat(str(x, give.attr = FALSE, no.list = TRUE))
invisible(x)
}
mis <- check_mismatch_many(x, y)
mis
# <Mismatch>
# $ x: chr [1:2] "D" "X"
# $ y: chr "A"
(There are a lot more things you can do in the print.mismatch method, obviously. str is the major component of it, and it is the swiss-army-knife of depicting structure.)
ll<-list(list(c('A', 'B', 'C'),"Peter"),"John","Hans")
looks like:
[[1]]
[[1]][[1]]
[1] "A" "B" "C"
[[1]][[2]]
[1] "Peter"
[[2]]
[1] "John"
[[3]]
[1] "Hans"
Lets say I have the indices in a list for "Peter" and "B" respectively.
peter.ind <- list(1,2) # correlates with ll[[1]][[2]]
B.ind <- list(1,1,2) # correlates with ll[[1]][[1]][[2]]
So how can I most effectively extract a "tangled" list element by its cascaded index chain?
Here is my already working function:
extract0r <- function(x,l) {
for(ind in l) {
x <- x[[ind]]
}
return(x)
}
call function:
extract0r(ll,peter.ind) #evals [1] "Peter"
extract0r(ll,B.ind) #evals [1] "B"
Is there a neater alternative to my function?
You can use a recursive function:
ll <- list(list(c('A', 'B', 'C'),"Peter"),"John","Hans")
my.ind <- function(L, ind) {
if (length(ind)==1) return(L[[ind]])
my.ind(L[[ind[1]]], ind[-1])
}
my.ind(ll, c(1,2))
my.ind(ll, c(1,1,2))
# > my.ind(ll, c(1,2))
# [1] "Peter"
# > my.ind(ll, c(1,1,2))
# [1] "B"
The recursive function has a (relative) clear coding, but during execution it has an overhead for the deep function calls.
There are many ways of doing this.
For example, you can build the commands from character strings:
my.ind.str <- function(L, ind) {
command <- paste0(c("L",sprintf("[[%i]]", ind)),collapse="")
return(eval(parse(text=command)))
}
With your example, I had to convert the lists of indices to vectors:
my.ind.str(ll, unlist(peter.ind))
[1] "Peter"
my.ind.str(ll, unlist(B.ind))
[1] "B"
How can I pass a character vector using NSE:
fun <- function(x){
x_ <-deparse(substitute(x))
print(x_)
}
fun_ <- function(x){
do_something(x)
}
For example,
fun(x= a, b, c)
should print interpret the argument to x as a vector c(a, b, c) and pass a character vector to fun_(x):
fun_(x=c("a", "b", "c"))
There are additional parameters as well.
You can't use commas within a parameter; commas separate parameters. Calling fun(x=a,b,c) would call fun() with three parameters, the first one named and the second two unnamed. If you just want to ignore the name x=, you can turn unquoted names to strings with
fun <- function(...){
x <- sapply(substitute(...()), deparse)
fun_(x)
}
fun_ <- function(x) {
print(x)
}
fun(a,b,c)
# [1] "a" "b" "c"
fun_(c("a","b","c"))
# [1] "a" "b" "c"
Here's what I'd like to do:
as.character(quote(x))
[1] "x"
Now I would like to put it in a function.
qq <- function(a) as.character(substitute(a))
qq(x)
[1] "x"
Fine. But:
qq <- function(...) as.character(substitute(...))
qq(x,y,z)
[1] "x"
OK, how about:
qq <- function(...) sapply(..., function (x) as.character(substitute(x)))
qq(x,y,z)
Error in get(as.character(FUN), mode = "function", envir = envir) :
object 'y' of mode 'function' was not found
And:
qq <- function(...) sapply(list(...), function (x) as.character(substitute(x)))
qq(x,y,z)
Error in lapply(X = X, FUN = FUN, ...) : object 'z' not found
Is there a way to do this?
You could try match.call
foo <- function(...) {
sapply(as.list(match.call())[-1], deparse)
}
foo(x, y, z)
# [1] "x" "y" "z"
foo(a, b, c, d, e)
# [1] "a" "b" "c" "d" "e"
If there are any other arguments, you may want some variation of the above function.
foo2 <- function(x, ...) {
a <- as.list(match.call(expand.dots = FALSE))$...
sapply(a, deparse)
}
foo2(5, x, y, z)
# [1] "x" "y" "z"
Try this:
qq <- function(...) sapply(substitute({ ... })[-1], deparse)
qq(a, b, c)
## [1] "a" "b" "c"
Note: qq <- function(...) as.character(substitute(...)) does work if passed a single argumet: qq(a) so the problem is that substitute is expecting a single argument, not several. The {...} converts the multiple arguments to one.
Suppose I have a vector-like S4 class:
.MyClass <- setClass("MyClass", representation(a="numeric", b="character"))
setMethod("[", c("MyClass", "numeric", "missing"), function(x, i, j, ...) {
do.call(initialize, c(x, sapply(slotNames(x), function(y) slot(x, y)[i],
simplify=FALSE)))
})
setMethod("length", "MyClass", function(x) length(x#a))
And say I have also defined methods for as.list and as.list.default:
setGeneric("as.list")
setMethod("as.list", "MyClass",
function(x) lapply(seq_along(x), function(i) x[i]))
setGeneric("as.list.default")
setMethod("as.list.default", "MyClass",
function(x) lapply(seq_along(x), function(i) x[i]))
Now given an object of this class, myobj:
myobj <- .MyClass(a=1:4, b=letters[1:4])
When I use lapply, it complains:
> lapply(myobj, function(i) rep(i#b, i#a))
Error in as.list.default(X) :
no method for coercing this S4 class to a vector
But if I use as.list.default, the function gives the desired output:
> lapply(as.list.default(myobj), function(i) rep(i#b, i#a))
[[1]]
[1] "a"
[[2]]
[1] "b" "b"
...
Why does lapply not work even though I have defined a method for as.list.default for the class?
Obviously I can manually define a lapply method for the class and it will work fine (below), but I was wondering where the error is actually being encountered. Why is lapply attempting to coerce my object into a vector even though the function it is calling should be turning the object into a list?
setGeneric("lapply")
setMethod("lapply", c("MyClass", "function"), function(X, FUN, ...) {
lapply(as.list(X), FUN, ...)
})
lapply(myobj, function(i) rep(i#b, i#a))
From the ?Methods help page, a workable strategy seems to be
#same
.MyClass <- setClass("MyClass", representation(a="numeric", b="character"))
setMethod("[", c("MyClass", "numeric", "missing"), function(x, i, j, ...) {
do.call(initialize, c(x, sapply(slotNames(x), function(y) slot(x, y)[i],
simplify=FALSE)))
})
setMethod("length", "MyClass", function(x) length(x#a))
#different
as.list.MyClass <-function(x) {
lapply(seq_along(x), function(i) x[i])
}
setMethod("as.list", "MyClass", as.list.MyClass)
#test
myobj <- .MyClass(a=1:4, b=letters[1:4])
lapply(myobj, function(i) rep(i#b, i#a))
# [[1]]
# [1] "a"
#
# [[2]]
# [1] "b" "b"
#
# [[3]]
# [1] "c" "c" "c"
#
# [[4]]
# [1] "d" "d" "d" "d"