How can I apply a mapping (stored in a list) to a vector?
Consider I have a mapping defined in this way:
m <- list(foo='bar', a='b', 1=2)
I can get a single list element by just writing something like m[['foo']]. What I want to do is to get a list values for multiple keys at once. An obvious solution would be just iterating through the vector:
a <- c('foo', 'a')
b <- c()
for (it in a) {b <- c(b, m[[it]])}
But looks like this is not an R-style. Can I do it with a one-liner? I also tried using lapply() and mapply() with get() function, but didn't succeed in that.
Please note: I'm new to R, so I may use some terms improperly.
m <- list(foo='bar', a='b', c = 'baz')
a <- c("foo", "a")
unlist(m[a])
## foo a
## "bar" "b"
Related
Is there a way to use mapply on two vectors to construct a named list? The first vector would be of type character and contain the names used for the list while the second contains the values.
So far, the only solution I have is:
> dummyList = list()
> addToList <- function(name, value) {
+ dummyList[[name]] <- value
+ }
> mapply(addToList, c("foo", "bar"), as.list(c(1, 2))
$foo
`1`
$bar
`2`
This seems like a rather contrived solution, but I can't figure out how to do it otherwise. The problems I have with it are:
It requires the creation of dummyList even though dummyList is never changed and is an empty list after the call to mapply.
If the numeric vector, c(1, 2), is not converted to a list, then the result of the call to mapply is a named vector of doubles.
To get around problem 2, I can always just call mapply on two vectors and then call as.list on the result, but it seems like there should be a way to directly create a list with the values being in a vector.
You can use setNames()
setNames(as.list(c(1, 2)), c("foo", "bar"))
(for a list) or
setNames(c(1, 2), c("foo", "bar"))
(for a vector)
What I propose is made in 2 steps, and it's quite straightforward, so maybe it's easier to understand:
test_list <- list(1, 2)
names(test_list) <- c("foo", "bar")
What #ben-bolker proposes works, but just wanted to share an alternative, in case you prefer it.
Happy coding!
I share Ben's puzzlement about why you might want to do this, and his recommendation.
Just for curiosity's sake, there is a sort of "hidden" feature in mapply that will allow this:
x <- letters[1:2]
y <- 1:2
mapply(function(x,y) { y }, x, y, SIMPLIFY = FALSE,USE.NAMES = TRUE)
$a
[1] 1
$b
[1] 2
Noting that the documentation for USE.NAMES says:
USE.NAMES logical; use names if the first ... argument has names, or
if it is a character vector, use that character vector as the names.
I used to work in C++ and I think I am misunderstanding how for-loops (or iterations) work in R. I want to change list items in a for loop, but the for loop seems to make a temporary copy and only change that? How can I prevent this? This seems like a trivial beginners question, but I was unable to find a tutorial / question on stackoverflow about why this happens.
Code:
myList <- list(a=1, b=1, c=1, d=1)
for(item in myList){item <- 3}
myList
# Expected output: 3,3,3,3 - Real output: 1,1,1,1
# Additionally, I now have a variable "item" with value 3.
for(item in myList) creates a new object called item
If you want to refer to the items from the list, it would be better to do it by calling either their position with myList[1], or their name with myList[["a"]].
You can for-loop through the list by using the index (as one of the comments suggested).
myList <- list(a=1, b=2, c=4, d=5)
for(i in 1:length(myList)){
myList[i] <- 3
}
myList
But I would recomment a vector approach. Check this out:
myList <- list(a=1, b=2, c=1, d=5)
myList=='1'
myList[myList=='1']=3
myList
myList[names(myList)=='a']=9
myList
Now you do not have any redundant variables.
This is actually the recommended approach in R. For-loops are too computationally expensive.
As stated by #nicola, lapply should be a good option. Here is an example based on your question.
myList <- list(a = 1, b = 1, c = 1, d = 1) # output: 1,1,1,1
lapply(myList, function(x) 3) # output: 3,3,3,3
# lapply iterates over every list item
The apply functions in R are a nice way to simplify for loops to get to an output. Is there an equivalent function that helps one avoid for loops when replacing the values of a vector? This is better understood by example...
# Take this list for example
x = list( list(a=1,b=2), list(a=3,b=4), list(a=5,b=6) )
# To get all of the "a" elements from each list, I can do
vapply(x,"[[",1,"a")
[1] 1 3 5
# If I want to change all of the "a" elements, I cannot do
vapply(x,"[[",1,"a") = 10:12
Error in vapply(x, "[[", 1, "a") = 10:12 :
could not find function "vapply<-"
# (this error was expected)
# Instead I must do something like this...
new.a = 10:12
for(i in seq_along(x)) x[[i]]$a = new.a[i]
Is there a simpler or faster alternative to using a loop?
One option would be to first unlist the list x, then replace the values named "a", and then relist the new list u based on the list structure of x.
u <- unlist(x)
u[names(u) == "a"] <- 10:12
relist(u, x)
vapply is a special case of sapply where you need to pre-specify the return type.
If you a multivariate version of sapply, the function you are looking for is mapply (or Map which is a wrapper with SIMPLIFY=FALSE`)
In general, functions with side-effects are frowned upon in R. The standard approach would be to create a new object when modifying.
You could use modlifyList to perform the modifications
xnew <- Map(modifyList, x, val = lapply(10:12,function(x) list(a = x)))
Is there a way to use mapply on two vectors to construct a named list? The first vector would be of type character and contain the names used for the list while the second contains the values.
So far, the only solution I have is:
> dummyList = list()
> addToList <- function(name, value) {
+ dummyList[[name]] <- value
+ }
> mapply(addToList, c("foo", "bar"), as.list(c(1, 2))
$foo
`1`
$bar
`2`
This seems like a rather contrived solution, but I can't figure out how to do it otherwise. The problems I have with it are:
It requires the creation of dummyList even though dummyList is never changed and is an empty list after the call to mapply.
If the numeric vector, c(1, 2), is not converted to a list, then the result of the call to mapply is a named vector of doubles.
To get around problem 2, I can always just call mapply on two vectors and then call as.list on the result, but it seems like there should be a way to directly create a list with the values being in a vector.
You can use setNames()
setNames(as.list(c(1, 2)), c("foo", "bar"))
(for a list) or
setNames(c(1, 2), c("foo", "bar"))
(for a vector)
What I propose is made in 2 steps, and it's quite straightforward, so maybe it's easier to understand:
test_list <- list(1, 2)
names(test_list) <- c("foo", "bar")
What #ben-bolker proposes works, but just wanted to share an alternative, in case you prefer it.
Happy coding!
I share Ben's puzzlement about why you might want to do this, and his recommendation.
Just for curiosity's sake, there is a sort of "hidden" feature in mapply that will allow this:
x <- letters[1:2]
y <- 1:2
mapply(function(x,y) { y }, x, y, SIMPLIFY = FALSE,USE.NAMES = TRUE)
$a
[1] 1
$b
[1] 2
Noting that the documentation for USE.NAMES says:
USE.NAMES logical; use names if the first ... argument has names, or
if it is a character vector, use that character vector as the names.
Frequently I encounter situations where I need to create a lot of similar models for different variables. Usually I dump them into the list. Here is the example of dummy code:
modlist <- lapply(1:10,function(l) {
data <- data.frame(Y=rnorm(10),X=rnorm(10))
lm(Y~.,data=data)
})
Now getting the fit for example is very easy:
lapply(modlist,predict)
What I want to do sometimes is to extract one element from the list. The obvious way is
sapply(modlist,function(l)l$rank)
This does what I want, but I wonder if there is a shorter way to get the same result?
probably these are a little bit simple:
> z <- list(list(a=1, b=2), list(a=3, b=4))
> sapply(z, `[[`, "b")
[1] 2 4
> sapply(z, get, x="b")
[1] 2 4
and you can define a function like:
> `%c%` <- function(x, n)sapply(x, `[[`, n)
> z %c% "b"
[1] 2 4
and also this looks like an extension of $:
> `%$%` <- function(x, n) sapply(x, `[[`, as.character(as.list(match.call())$n))
> z%$%b
[1] 2 4
I usually use kohske way, but here is another trick:
sapply(modlist, with, rank)
It is more useful when you need more elements, e.g.:
sapply(modlist, with, c(rank, df.residual))
As I remember I stole it from hadley (from plyr documentation I think).
Main difference between [[ and with solutions is in case missing elements. [[ returns NULL when element is missing. with throw an error unless there exist an object in global workspace having same name as searched element. So e.g.:
dah <- 1
lapply(modlist, with, dah)
returns list of ones when modlist don't have any dah element.
With Hadley's new lowliner package you can supply map() with a numeric index or an element name to elegantly pluck components out of a list. map() is the equivalent of lapply() with some extra tricks.
library("lowliner")
l <- list(
list(a = 1, b = 2),
list(a = 3, b = 4)
)
map(l, "b")
map(l, 2)
There is also a version that simplifies the result to a vector
map_v(l, "a")
map_v(l, 1)