Subset list, keep names - r

I have list out like this:
u <- list(a = list(b = 1, c = 2),
x = list(k = list(ka = 1, kb = 3),
l = list(la = 1, la = 4)))
v <- list(a = list(b = 1, c = 2),
x = list(m = list(ma = 5, mb = 8),
n = list(na = 5, nb = 8)))
w <- list(a = list(b = 1, c = 2),
x = list(o = list(oa = 4, ob = 1),
p = list(pa = 8, pb = 0)))
out <- list(u, v, w)
I would like to create another list where there are elements k, l, m, n, o, p and names of the list elements are preserved. I found a solution, but looks sub-optimal:
x <- lapply(out, function(y) y[['x']])
o <- list()
for (a in x) {
o <- c(o, a)
}
> str(o, max.level = 1)
List of 6
$ k:List of 2
$ l:List of 2
$ m:List of 2
$ n:List of 2
$ o:List of 2
$ p:List of 2
Is there a better way?

The loop could be replaced with unlist:
res <- unlist( lapply(out,"[[","x"), recursive=FALSE)
identical(res,o)
# [1] TRUE
My lapply is the same as in the OP; it's just a shortcut.
As #akrun suggested, you could more closely mirror the OP's loop with
do.call("c", lapply(out, '[[', 'x'))

Related

Unlist LAST level of a list in R

I have a list of list like ll:
ll <- list(a = list(data.frame(c = 1, d = 2), data.frame(h = 3, j = 4)), b = list(data.frame(c = 5, d = 6), data.frame(h = 7, j = 9)))
I want to unnest/unlist the last level of the structure (the interior list). Note that every list contains the same structure. I want to obtain lj:
lj <- list(a = (data.frame(c = 1, d = 2, h = 3, j = 4)), b = data.frame(c = 5, d = 6, h = 7, j = 9))
I have tried the following code without any success:
lj_not_success <- unlist(ll, recursive = F)
However, this code unlists the FIRST level, not the LAST one.
Any clue?
We may need to cbind the inner list elements instead of unlisting as the expected output is a also a list of data.frames
ll_new <- lapply(ll, function(x) do.call(cbind, x))
-checking
> identical(lj, ll_new)
[1] TRUE

How to append elements from second list to existent elements from first list in R

I have two lists x and y:
x <- list(id1 = list(a = list(t = 5)), id2 = list(a = list(t = 1), b = list(t = 3)), id3 = list(a = list(t = 1), b = list(t = 2)))
y <- list(b = list(k = 7))
I need to modify x list and add corresponding "b" elements from y list to obtain z list:
z <- list(id1 = list(a = list(t = 5)), id2 = list(a = list(t = 1), b = list(t = 3, k = 7)),
id3 = list(a = list(t = 1), b = list(t = 2, k = 7)))
I try to use list_modify(x, y) and list_merge(x, !!!y) from purrr package but obtain wrong result.
How to do this in R?
In this case you could do:
result <- lapply(x, function(i) {
if("b" %in% names(i)) i$b <- append(i$b, y$b); i;
})
Such that
identical(result, z)
#> [1] TRUE
It's not particularly elegant, but you could use a loop to do this. The loop goes over the elements in x and within that, the elements in y. If there is an element in x with the same name as an element in y, the two are concatenated together in z, otherwise, z is just x. Depending on the scale of the real problem, this may be too slow, and perhaps someone can provide a tidy alternative.
z <- vector(mode="list", length = length(x))
for(i in 1:length(x)){
for(j in 1:length(y)){
z[[i]] <- x[[i]]
if(names(y)[j] %in% names(x[[i]])){
z[[i]][[names(y)[j]]] <- c(x[[i]][[names(y)[j]]], y[[j]])
}else{
}
names(z) <- names(x)
}
}
An alternative solution using map_if: apply list_merge to every element in the original list that has at least one subname in common with y:
map_if(x, ~any(names(.) %in% names(y)), list_merge, !!!y)
purrr::list_merge works as expected, but when the lists are named it uses the names to find the positions for merging, and your y isn't named properly with id2/id3 for it to merge.
To use list_merge:
y <- list(id2 = list(b = list(k = 7)), id3 = list(b = list(k = 7)))
z2 <- list_merge(x, !!!y)
identical(z, z2)
[1] TRUE

Unpack a list by duplicating elements longer than 1

I have the following list that I wish to unpack (aka expand) using only base R.
For example, I want to turn this:
b <- list(a = c(1, 2), b = 1, d = c(5, 7))
into the equivalent of:
list(a = 1, a = 2, b = 1, d = 5, d = 7)
I have this function that works if only one named element has length > 1 but not if there are multiple elements:
expand_list <- function(listx){
long_elements <- as.numeric(which(lapply(listx, length) > 1))
short_elements <- as.numeric(which(lapply(listx, length) == 1))
res <- lapply(long_elements, function(x){
as.list(setNames(listx[[x]], rep(names(listx)[x], length(listx[[x]]))))
})
expanded_elements <- res[[1]]
c(listx[short_elements], expanded_elements)
}
expand_list(b)
You can use stack followed by setNames to achieve that
y <- list(a = c(1, 2), b = 1, c = 2, d = c(5, 7))
x <- stack(y)
as.list(setNames(x$values, x$ind))

Subset a list without knowing its structure in r

I have a list in R:
my_list <- list(a = 1, b = 2, c = list(d = 4, e = 5))
Suppose I don't know the structure of the list, but I know that somewhere in this list, there is an element named d, nested or not. I would like to:
Subset that list element, without knowing the structure of the master list that contains it
Know the name of its parent list (i.e. element c)
Is there an easy method / package that can solve this seemingly simple problem?
I'm implementing the suggestion by #r2evans. I'm sure this can be improved:
getParentChild <- function(lst, myN) {
myFun <- function(lst, myN) {
test <- which(names(lst) == myN)
if (length(test) > 0)
return(lst[test])
lapply(lst, function(x) {
if (is.list(x))
myFun(x, myN)
})
}
temp <- myFun(lst, myN)
temp[!sapply(temp, function(x) is.null(unlist(x)))]
}
getParentChild(my_list, "d")
$c
$c$d
[1] 4
Here is a more complicate example that illustrates how getParentChild shows lineage when there are multiple children/grandchildren.
exotic_list <- list(a = 1, b = 2, c = list(d = 4, e = 5), f = list(g = 6, h = list(k = 7, j = 8)), l = list(m = 6, n = list(o = 7, p = 8)), q = list(r = 5, s = 11), t = 12)
getParentChild(exotic_list, "n")
$l
$l$n
$l$n$o
[1] 7
$l$n$p
[1] 8
Here's another recursive approach, very similar to #JosephWood's answer, that generalizes the solution such that you can search for multiple elements at the same time, and find all the matching elements, if there are multiple:
find_all <- function(x, elements) {
lists <- vapply(x, is.list, logical(1)) # find sublists
# find all elements in sublists
out <- lapply(x[lists], find_all, elements)
out <- out[!vapply(out, is.null, logical(1))]
# output any found elements
if (any(elements %in% names(x)))
out <- c(out, x[names(x) %in% elements])
if (length(out) == 0) NULL else out
}
The example problem:
my_list <- list(a = 1, b = 2, c = list(d = 4, e = 5))
str(find_all(my_list, "e"))
#> List of 1
#> $ c:List of 1
#> ..$ e: num 5
And #JosephWood's exotic example complicted further:
exotic_list <-
list(
a = 1,
b = 2,
c = list(d = 4, e = 5),
f = list(g = 6, h = list(k = 7, j = 8)),
l = list(m = 6, n = list(o = 7, p = 8)),
q = list(r = 5, s = 11),
t = 12,
n = 13
)
str(find_all(exotic_list, c("n", "q")))
#> List of 3
#> $ l:List of 1
#> ..$ n:List of 2
#> .. ..$ o: num 7
#> .. ..$ p: num 8
#> $ q:List of 2
#> ..$ r: num 5
#> ..$ s: num 11
#> $ n: num 13
Using the purrr package we could also get rid of the vapplys, making the
function a little bit more succinct, and perhaps a bit more readable, too:
library(purrr)
find_all2 <- function(x, elements) {
# find all elements in sublists
out <- map(keep(x, is.list), find_all, elements)
out <- compact(out) # drop nulls
# output any found elements
if (any(elements %in% names(x)))
out <- c(out, x[names(x) %in% elements])
if (length(out) == 0) NULL else out
}
identical(
find_all(exotic_list, c("n", "q")),
find_all2(exotic_list, c("n", "q"))
)
#> [1] TRUE
Created on 2018-03-15 by the reprex package (v0.2.0).
Maybe something like the following will do what you want.
wanted <- "d"
inx <- grep(wanted, names(unlist(my_list)), value = TRUE)
unlist(my_list)[inx]
#c.d
# 4
sub(paste0("(\\w)\\.", wanted), "\\1", inx)
#[1] "c"

Create a list from matrix in R

I have two lists v and w and I would like to create again a list z from matrix M . How can I do this in R?
v = list(a = c(1, 5), b = 2, c= 3)
w = list( a= c(2, 10), b = 4, c = 6)
M = as.matrix(unlist( v) * unlist(w))
> M
[,1]
a1 2
a2 50
b 8
c 18
z = list(a = c(2, 50), b = 8, c = 18)
Do it like this:
mapply(`*`, v, w)
Maybe you want z <- lapply(1:length(v), function(i) v[[i]]*w[[i]])? Add names(z) <- names(v) to keep the names.

Resources