reassign values in a list without looping - r

test <- list(a = list("first"= 1, "second" = 2),
b = list("first" = 3, "second" = 4))
In the list above, I would like to reassign the "first" elements to equal, let's say, five. This for loop works:
for(temp in c("a", "b")) {
test[[temp]]$first <- 5
}
Is there a way to do the same using a vectorized operation (lapply, etc)? The following extracts the values, but I can't get them reassigned:
lapply(test, "[[", "first")

Here is a vectorised one-liner using unlist and relist:
relist((function(x) ifelse(grepl("first",names(x)),5,x))(unlist(test)),test)
$a
$a$first
[1] 5
$a$second
[1] 2
$b
$b$first
[1] 5
$b$second
[1] 4

You can do it like this:
test <- lapply(test, function(x) {x$first <- 5; x})

Related

concatenation sublists of two different lists [duplicate]

I have two lists
first = list(a = 1, b = 2, c = 3)
second = list(a = 2, b = 3, c = 4)
I want to merge these two lists so the final product is
$a
[1] 1 2
$b
[1] 2 3
$c
[1] 3 4
Is there a simple function to do this?
If lists always have the same structure, as in the example, then a simpler solution is
mapply(c, first, second, SIMPLIFY=FALSE)
This is a very simple adaptation of the modifyList function by Sarkar. Because it is recursive, it will handle more complex situations than mapply would, and it will handle mismatched name situations by ignoring the items in 'second' that are not in 'first'.
appendList <- function (x, val)
{
stopifnot(is.list(x), is.list(val))
xnames <- names(x)
for (v in names(val)) {
x[[v]] <- if (v %in% xnames && is.list(x[[v]]) && is.list(val[[v]]))
appendList(x[[v]], val[[v]])
else c(x[[v]], val[[v]])
}
x
}
> appendList(first,second)
$a
[1] 1 2
$b
[1] 2 3
$c
[1] 3 4
Here are two options, the first:
both <- list(first, second)
n <- unique(unlist(lapply(both, names)))
names(n) <- n
lapply(n, function(ni) unlist(lapply(both, `[[`, ni)))
and the second, which works only if they have the same structure:
apply(cbind(first, second),1,function(x) unname(unlist(x)))
Both give the desired result.
Here's some code that I ended up writing, based upon #Andrei's answer but without the elegancy/simplicity. The advantage is that it allows a more complex recursive merge and also differs between elements that should be connected with rbind and those that are just connected with c:
# Decided to move this outside the mapply, not sure this is
# that important for speed but I imagine redefining the function
# might be somewhat time-consuming
mergeLists_internal <- function(o_element, n_element){
if (is.list(n_element)){
# Fill in non-existant element with NA elements
if (length(n_element) != length(o_element)){
n_unique <- names(n_element)[! names(n_element) %in% names(o_element)]
if (length(n_unique) > 0){
for (n in n_unique){
if (is.matrix(n_element[[n]])){
o_element[[n]] <- matrix(NA,
nrow=nrow(n_element[[n]]),
ncol=ncol(n_element[[n]]))
}else{
o_element[[n]] <- rep(NA,
times=length(n_element[[n]]))
}
}
}
o_unique <- names(o_element)[! names(o_element) %in% names(n_element)]
if (length(o_unique) > 0){
for (n in o_unique){
if (is.matrix(n_element[[n]])){
n_element[[n]] <- matrix(NA,
nrow=nrow(o_element[[n]]),
ncol=ncol(o_element[[n]]))
}else{
n_element[[n]] <- rep(NA,
times=length(o_element[[n]]))
}
}
}
}
# Now merge the two lists
return(mergeLists(o_element,
n_element))
}
if(length(n_element)>1){
new_cols <- ifelse(is.matrix(n_element), ncol(n_element), length(n_element))
old_cols <- ifelse(is.matrix(o_element), ncol(o_element), length(o_element))
if (new_cols != old_cols)
stop("Your length doesn't match on the elements,",
" new element (", new_cols , ") !=",
" old element (", old_cols , ")")
}
return(rbind(o_element,
n_element,
deparse.level=0))
return(c(o_element,
n_element))
}
mergeLists <- function(old, new){
if (is.null(old))
return (new)
m <- mapply(mergeLists_internal, old, new, SIMPLIFY=FALSE)
return(m)
}
Here's my example:
v1 <- list("a"=c(1,2), b="test 1", sublist=list(one=20:21, two=21:22))
v2 <- list("a"=c(3,4), b="test 2", sublist=list(one=10:11, two=11:12, three=1:2))
mergeLists(v1, v2)
This results in:
$a
[,1] [,2]
[1,] 1 2
[2,] 3 4
$b
[1] "test 1" "test 2"
$sublist
$sublist$one
[,1] [,2]
[1,] 20 21
[2,] 10 11
$sublist$two
[,1] [,2]
[1,] 21 22
[2,] 11 12
$sublist$three
[,1] [,2]
[1,] NA NA
[2,] 1 2
Yeah, I know - perhaps not the most logical merge but I have a complex parallel loop that I had to generate a more customized .combine function for, and therefore I wrote this monster :-)
merged = map(names(first), ~c(first[[.x]], second[[.x]])
merged = set_names(merged, names(first))
Using purrr. Also solves the problem of your lists not being in order.
In general one could,
merge_list <- function(...) by(v<-unlist(c(...)),names(v),base::c)
Note that the by() solution returns an attributed list, so it will print differently, but will still be a list. But you can get rid of the attributes with attr(x,"_attribute.name_")<-NULL. You can probably also use aggregate().
We can do a lapply with c(), and use setNames to assign the original name to the output.
setNames(lapply(1:length(first), function(x) c(first[[x]], second[[x]])), names(first))
$a
[1] 1 2
$b
[1] 2 3
$c
[1] 3 4
Following #Aaron left Stack Overflow and #Theo answer, the merged list's elements are in form of vector c.
But if you want to bind rows and columns use rbind and cbind.
merged = map(names(first), ~rbind(first[[.x]], second[[.x]])
merged = set_names(merged, names(first))
Using dplyr, I found that this line works for named lists using the same names:
as.list(bind_rows(first, second))

For loop: paste index into string

This may strike you as odd, but I want to exactly achieve the following: I want to get the index of a list pasted into a string containing a string reference to a subset of this list.
For illustration:
l1 <- list(a = 1, b = 2)
l2 <- list(a = 3, b = 4)
l <- list(l1,l2)
X_l <- vector("list", length = length(l))
for (i in 1:length(l)) {
X_l[[i]] = "l[[ #insert index number as character# ]]$l_1*a"
}
In the end, I want something like this:
X_l_wanted <- list("l[[1]]$l_1*a","l[2]]$l_1*a")
You can use sprintf/paste0 directly :
sprintf('l[[%d]]$l_1*a', seq_along(l))
#[1] "l[[1]]$l_1*a" "l[[2]]$l_1*a"
If you want final output as list :
as.list(sprintf('l[[%d]$l_1*a', seq_along(l)))
#[[1]]
#[1] "l[[1]]$l_1*a"
#[[2]]
#[1] "l[[2]]$l_1*a"
Using paste0 :
paste0('l[[', seq_along(l), ']]$l_1*a')
Try paste0() inside your loop. That is the way to concatenate chains. Here the solution with slight changes to your code:
#Data
l1 <- list(a = 1, b = 2)
l2 <- list(a = 3, b = 4)
l <- list(l1,l2)
#List
X_l <- vector("list", length = length(l))
#Loop
for (i in 1:length(l)) {
#Code
X_l[[i]] = paste0('l[[',i,']]$l_1*a')
}
Output:
X_l
[[1]]
[1] "l[[1]]$l_1*a"
[[2]]
[1] "l[[2]]$l_1*a"
Or you could do it with lapply()
library(glue)
X_l <- lapply(1:length(l), function(i)glue("l[[{i}]]$l_l*a"))
X_l
# [[1]]
# l[[1]]$l_l*a
# [[2]]
# l[[2]]$l_l*a

Use paste0 to create multiple object names with a for loop

I would like to create multiple object names with a for loop. I have tried the following which fails horribly:
somevar_1 = c(1,2,3)
somevar_2 = c(4,5,6)
somevar_3 = c(7,8,9)
for (n in length(1:3)) {
x <- as.name(paste0("somevar_",[i]))
x[2]
}
The desired result is x being somevar_1, somevar_2, somevar_3 for the respective iterations, and x[2] being 2, 5 and 8 respectively.
How should I do this?
somevar_1 = c(1,2,3)
somevar_2 = c(4,5,6)
somevar_3 = c(7,8,9)
for (n in 1:3) {
x <- get(paste0("somevar_", n))
print(x[2])
}
Result
[1] 2
[1] 5
[1] 8
We can use mget to get all the required objects in a list and use sapply to subset 2nd element from each of them.
sapply(mget(paste0("somevar_", 1:3)), `[`, 2)
#somevar_1 somevar_2 somevar_3
# 2 5 8

access plyr id variables within functions

I regulary have the problem that I need to access the actual id variable when using d*ply or l*ply. A simple (yet nonsense) example would be:
df1 <- data.frame( p = c("a", "a", "b", "b"), q = 1:4 )
df2 <- data.frame( m = c("a", "b" ), n = 1:2 )
d_ply( df1, "p", function(x){
actualId <- unique( x$p )
print( mean(x$q)^df2[ df2$m == actualId, "n" ] )
})
So in case of d*ply functions I can help myself with unique( x$p ). But when it comes to l*ply, I have no idea how to access the name of the according list element.
l_ply( list(a = 1, b = 2, c = 3), function(x){
print( <missing code> )
})
# desired output
[1] "a"
[1] "b"
[1] "c"
Any suggestions? Anything I am ignoring?
One way I've gotten around this is to loop over the index (names) and do the subsetting within the function.
l <- list(a = 1, b = 2, c = 3)
l_ply(names(l), function(x){
print(x)
myl <- l[[x]]
print(myl)
})
myl will then be the same as
l_ply(l, function(myl) {
print(myl)
})
Here's one idea.
l_ply( list(a = 1, b = 2, c = 3), function(x){
print(eval(substitute(names(.data)[i], parent.frame())))
})
# [1] "a"
# [1] "b"
# [1] "c"
(Have a look at the final code block of l_ply to see where I got the names .data and i.)
I'm not sure there's a way to do that, because the only argument to your anonymous function is the list element value, without its name :
l_ply( list(a = 1, b = 2, c = 3), function(x){
print(class(x))
})
[1] "numeric"
[1] "numeric"
[1] "numeric"
But if you get back the results of your command as a list or a data frame, the names are preserved for you to use later :
llply( list(a = 1, b = 2, c = 3), function(x){
x
})
$a
[1] 1
$b
[1] 2
$c
[1] 3
Aside from Josh solution, you can also pass both names and values of your list elements to a function with mapply or m*ply :
d <- list(a = 1, b = 2, c = 3)
myfunc <- function(value, name) {
print(as.character(name))
print(value)
}
mapply(myfunc, d, names(d))
m_ply(data.frame(value=unlist(d), name=names(d)), myfunc)

elementwise combination of two lists in R

Say I have two lists:
list.a <- as.list(c("a", "b", "c"))
list.b <- as.list(c("d", "e", "f"))
I would like to combine these lists recursively, such that the result would be a list of combined elements as a vector like the following:
[[1]]
[1] a d
[[2]]
[1] a e
[[3]]
[1] a f
[[4]]
[1] b d
and so on. I feel like I'm missing something relatively simple here. Any help?
Cheers.
expand.grid(list.a, list.b) gives you the desired result in a data.frame. This tends to be the most useful format for working with data in R. However, you could get the exact structure you ask for (save the ordering) with a call to apply and lapply:
result.df <- expand.grid(list.a, list.b)
result.list <- lapply(apply(result.df, 1, identity), unlist)
If you want this list ordered by the first element:
result.list <- result.list[order(sapply(result.list, head, 1))]
You want mapply (if by "recursively" you mean "in parallel"):
mapply(c, list.a, list.b, SIMPLIFY=FALSE)
Or maybe this is more what you want:
unlist(lapply(list.a, function(a) lapply(list.b, function (b) c(a, b))), recursive=FALSE)
Surprised nobody has mentioned this simple one liner:
as.list(outer(list.a,list.b, paste))
[[1]]
[1] "a d"
[[2]]
[1] "b d"
[[3]]
[1] "c d"
[[4]]
[1] "a e"
This gets you what you are looking for:
unlist(lapply(list.a, function(X) {
lapply(list.b, function(Y) {
c(X, Y)
})
}), recursive=FALSE)
Here is a function you can pass lists to to expand
expand.list <- function(...){
lapply(as.data.frame(t((expand.grid(...)))),c, recursive = TRUE, use.names = FALSE)}
expand.list(list.a, list.b)
Here is a somewhat brute force approach that will, given they are the same dimensions, append list.b to list.a recursively using the append function.
# CREATE LIST OBJECTS
list.a <- as.list(c("a", "b", "c"))
list.b <- as.list(c("d", "e", "f"))
# CREATE AN EMPTY LIST TO POPULATE
list.ab <- list()
# DOUBLE LOOP TO CREATE RECURSIVE COMBINATIONS USING append
ct=0
for( i in 1:length(list.a) ) {
for (j in 1:length(list.b) ) {
ct=ct+1
list.ab[[ct]] <- append(list.a[[i]], list.b[[j]])
}
}
# PRINT RESULTS
list.ab

Resources