I know the deparse+substitute trick to get the name from an object passed as argument to a function, but the same trick inside a loop does no work.
My code (just for testing):
mylist <- list(first = c("lawyer","janitor"), second = c("engineer","housewife"))
for (element in names(mylist)){
print(deparse(substitute(mylist[[element]])))
}
[1] "mylist[[element]]"
[1] "mylist[[element]]"
is there any way of getting the result?:
first
second
using lapply
lapply(mylist, function(x) { print(names(x))} )
# NULL
# NULL
# $first
# NULL
#
# $second
# NULL
using for loop as per your question
for (element in names(mylist)){
print(element)
}
# [1] "first"
# [1] "second"
Use "names"
for (element in names(mylist)){
print(as.name(element))
}
Related
This question already has an answer here:
How is dot (.) alias for list constructor implemented in data.table package?
(1 answer)
Closed 4 years ago.
from ?data.table::data.table :
The expression '.()' is a shorthand alias to list(); they both mean
the same
However this function is nowhere to be found :
data.table:::.
Error in get(name, envir = asNamespace(pkg), inherits = FALSE) :
object '.' not found
So I suppose the input is parsed somehow, how is it done ? I'd like to use the same feature in my own package.
The following works not too bad :
test <- function(x) {
eval(substitute(
eval.parent(substitute(x, list(.=list)))
))
}
foo <- "bar"
test(.(foo))
# [[1]]
# [1] "bar"
identical(test(.(foo)), list(foo))
# [1] TRUE
However there will be some dot variables used inside this dot function, and this fails :
. <- "baz"
test(.(foo,.))
# [[1]]
# [1] "bar"
#
# [[2]]
# function (...) .Primitive("list")
Expected :
# [[1]]
# [1] "bar"
#
# [[2]]
# [1] "baz"
The data.table package accomplishes it with this bit of code
replace_dot_alias <- function(e) {
# we don't just simply alias .=list because i) list is a primitive (faster to iterate) and ii) we test for use
# of "list" in several places so it saves having to remember to write "." || "list" in those places
if (is.call(e)) {
# . alias also used within bquote, #1912
if (e[[1L]] == 'bquote') return(e)
if (e[[1L]] == ".") e[[1L]] = quote(list)
for (i in seq_along(e)[-1L]) if (!is.null(e[[i]])) e[[i]] = replace_dot_alias(e[[i]])
}
e
}
found in R/data.table.R (currently at line 173). That's why you don't find data.table:::. anywhere, and how they accomplish the parsing you mention in your post.
Then in [.data.table" <- function (x, i, j,... they can do this sort of thing
if (!missing(j)) {
jsub = replace_dot_alias(substitute(j))
root = if (is.call(jsub)) as.character(jsub[[1L]])[1L] else ""
....
I have a loop with a lot of data frames.
When I come to certain data frames my logic needs to adjust. I'm looking for a while to make a TRUE/FALSE statement out of this, but I'm not sure exactly how to pull this off.
I want to convert the actual name of the variable into the text of the variable.
my_list_of_dfs = list(iris,mtcars)
for (i in my_list_of_dfs){ if i =='iris'{ print('this works') } }
Try a named list instead
# Created a named list instead...
my_list_of_dfs = list('iris'=iris, 'mtcars'=mtcars)
# check the names
names(my_list_of_dfs)
# [1] "iris" "mtcars"
for (i in names(my_list_of_dfs) ) {
if (i =='iris') {
print('this works')
}
print (my_list_of_dfs[i]) # You can access data-frame like this...
}
BTW, you also forgot the brackets around the condition in your if statement
An addition to answer by #Ismail (so please upvote that answer if you agree with the idea of using a named list), is to create a function that generates a named list for you. That is, instead of explicitly typing something like list('iris'=iris, 'mtcars'=mtcars), the following function will take R objects, combine them in a list, and name the list with the objects:
named_list <- function(...) {
.l <- list(...)
.names <- deparse(substitute(list(...)))
.names <- strsplit(gsub("list\\(|\\)| ", "", .names), ",")[[1]]
names(.l) <- .names
.l
}
x <- 3
y <- data.frame(a=1,b=2)
named_list(x, y)
#> $x
#> [1] 3
#>
#> $y
#> a b
#> 1 1 2
my_list_of_dfs <- named_list(iris, mtcars)
names(my_list_of_dfs )
#> [1] "iris" "mtcars"
Can follow #Ismail answer from here with something like:
for (i in names(my_list_of_dfs )) {
if (i == "iris")
print(names(my_list_of_dfs[[i]]))
else
print ("This is NOT iris")
}
#> [1] "Sepal.Length" "Sepal.Width" "Petal.Length" "Petal.Width" "Species"
#> [1] "This is NOT iris"
In the source code for data.frame, the last three lines of code set the attributes and return the result.
...
attr(value, "row.names") <- row.names
attr(value, "class") <- "data.frame"
value
}
In a function I wrote, the result is a named list created by lapply. Before I set any attributes in the function body, result is as follows.
> x <- data.frame(a = 1:5, b = letters[1:5])
> (g <- grep.dataframe("a|c", x))
# ...
# $b
# value row
# 1 a 1
# 2 c 3
> attributes(g) # I want "list" in here...
# $names
# [1] "a" "b"
I'd like "class" to be included in the attributes list, so I add attr(res, "class") <- "list" (res is the final result) just before res. "class" now shows up in the attributes list. However,it also prints out with the result of the function, which I don't want. I tried wrapping it with invisible, but that didn't work.
Why do the manually assigned attributes print with the function result, but are suppressed in a new data frame I create?
> (h <- grep.dataframe("a|c", x))
# ...
# $b
# value row
# 1 a 1
# 2 c 3
# attr(,"class") # ...This prints with the result. I don't want that.
# [1] "list"
> attributes(h) # ...But I want these attributes
# $names
# [1] "a" "b"
# $class
# [1] "list"
The ?class documentation offers some pointers:
Many R objects have a class attribute, a character vector giving the names of the classes from which the object inherits. If the object does not have a class attribute, it has an implicit class, "matrix", "array" or the result of mode(x) (except that integer vectors have implicit class "integer"). (Functions oldClass and oldClass<- get and set the attribute, which can also be done directly.)
When a generic function fun is applied to an object with class attribute c("first", "second"), the system searches for a function called fun.first and, if it finds it, applies it to the object. If no such function is found, a function called fun.second is tried. If no class name produces a suitable function, the function fun.default is used (if it exists). If there is no class attribute, the implicit class is tried, then the default method.
From that and running a few simple tests, I gather that:
a list is one of these implicit classes: see attributes(list(1)), typeof(list(1))
when print is called on a list, it is using print.default
print.default prints the attributes of an object
So you could define a print.list that will handle your special case:
print.list <- function(x, ...) {
if (is.list(x)) attr(x, "class") <- NULL
print.default(x, ...)
}
res <- list(1)
attr(res, "class") <- "list"
res
# [[1]]
# [1] 1
attributes(res)
# $class
# [1] "list"
All the concatenation functions in R can be rewritten as recursive functions. For instance, I could use c as a binary operator and I could define a new concat function as
concat <- function(...) {
Reduce(c, ...)
}
and concat would function as c actually functions in R base.
R syntactically uses many such functions, for instance c for vectors and lists, cbind for arrays, data.frames and matrices. When defining new object classes, it makes sense to create a method for combining them using a function which takes ... as an argument.
I know R can match methods to objects when they are the first object in the argument list, but what if I define a method like
concat <- function(...) {
UseMethod('concat')
}
concat.numeric <- function(...) {
c(...)
}
concat.character <- function(...) {
c(...)
}
myCon <- function(charPart, numPart) {
out <- list(charPart=charPart, numPart=numPart)
class(out) <- "myClass"
out
}
concat.myClass <- function(...) {
myCon(sapply(..., `[[`, 'charPart'), sapply(..., `[[`, 'numPart'))
}
> concat(4, 6, 'a')
"4" "6" "a"
> myObj1 <- myCon('a', 1)
> myObj2 <- myCon('b', 2)
> concat(myObj1, myObj2)
Error in get(as.character(FUN), mode = "function", envir = envir) :
object 'p' of mode 'function' was not found
At what point does R identify the types of arguments supplied to concat? How can I convince R to attempt to cast arguments to concat up to my specific object class?
You're not quite passing what you think to sapply you need to put it in list(...) so sapply can iterate through the elements rather than parsing them as extra arguments in the wrong place.
concat.myClass <- function(...)
{
myCon(sapply(list(...), `[[`, 'charPart'), sapply(list(...), `[[`, 'numPart'))
}
> myObj1
$charPart
[1] "a"
$numPart
[1] 1
attr(,"class")
[1] "myClass"
> myObj2
$charPart
[1] "b"
$numPart
[1] 2
attr(,"class")
[1] "myClass"
this then gives:
> concat(myObj1, myObj2)
$charPart
[1] "a" "b"
$numPart
[1] 1 2
attr(,"class")
[1] "myClass"
.. which I presume is what you want???
In my program, I am recursively going over a nested list and adding elements to an overall list that I will return. There are a few details to be taken care of, so I can't just use unlist.
formulaPart is taken to be a formula object.
My code is:
parseVariables <- function(formulaPart, myList){
for(currentVar in as.list(formulaPart))
if(typeof(currentVar == 'language'
parseVariables(currentVar, myList)
else
if(! toString(currentVar) %in% c(\\various characters)
list <- c(list, currentVar)
}
I have checked that the function correctly adds elements to the list when it should. The problem is that the list loses elements due to recursion. The elements added during one inner recursive call are not saved for another recursive call.
If this was in C++, I could just use a pointer; the same for Java. However, I do not understand how to handle this error in R.
R does something like pass-by-value, so you can't modify (most) existing objects just by passing them into a function. If you want to add on to something recursively, one trick would be to use an environment instead, which get passed by reference. This can easily be coerced to list when you're done.
parseVariables <- function(formulaPart, myList){
for(currentVar in as.list(formulaPart)) {
if(typeof(currentVar) == 'language') {
parseVariables(currentVar, myList)
}
else {
if(! toString(currentVar) %in% c(':', '+', '~'))
assign(toString(currentVar), currentVar, myList)
}
}
}
f1 <- z ~ a:b + x
f2 <- z ~ x + y
myList <- new.env()
parseVariables(f1, myList)
parseVariables(f2, mylist)
ls(myList)
# [1] "a" "b" "x" "z"
as.list(myList)
# $x
# x
#
# $z
# z
#
# $a
# a
#
# $b
# b