Neater representation of a list - r

Suppose I have function that returns a list
fun <- function()
{
return(list(c(1,2,3), c(4,5,6), c(7,8,9)))
}
Now writing fun() gives me
[[1]]
[1] 1 2 3
[[2]]
[1] 4 5 6
[[3]]
[1] 7 8 9
But is there a way to get something like this?
list(c(1,2,3),
c(4,5,6),
c(7,8,9))
EDIT: how can I preserve both a nice output and a possibility of making operations on the list that is returned?

I think you're probably wanting a prettified version of dput:
fun <- function(x) {
text_con <- textConnection("output", "w")
dput(x, text_con)
close(text_con)
formatR::tidy_source(text = output, args.newline = TRUE, width = 40)
}
So for example:
mylist <- list(c(1,2,3), c(4,5,6), c(7,8,9))
fun(mylist)
#> list(
#> c(1, 2, 3),
#> c(4, 5, 6),
#> c(7, 8, 9)
#> )
EDIT
If you want a list to be printed in a particular way, but otherwise maintain all of the functionality of a list, it's probably best to create an S3 class with its own printing method:
special_list <- function(...) structure(list(...), class = "special_list")
as.special_list <- function(x) `class<-`(as.list(x), "special_list")
print.special_list <- function(x) {
text_con <- textConnection("output", "w")
dput(unclass(x), text_con)
close(text_con)
formatR::tidy_source(text = output, args.newline = TRUE, width = 40)
invisible(x)
}
Now you can create a special_list like this:
my_list <- special_list(a = c(1, 2, 3), b = letters[1:3])
my_list
#> list(
#> a = c(1, 2, 3),
#> b = c("a", "b", "c")
#> )
and it retains the functionality of any other list
my_list$a <- 2 * my_list$a
my_list
#> list(
#> a = c(2, 4, 6),
#> b = c("a", "b", "c")
#> )
It just means that you need to make a special_list instead of a normal list if you want to print a list this way. If you want to create a special_list from an existing list, you can do:
newlist <- list(a = 1:3)
newlist
#> $a
#> [1] 1 2 3
newlist <- as.special_list(newlist)
newlist
#> list(a = 1:3)

this function returns exactly that - but I may have misunderstood
print_list <- function(x){
cat("list(", paste(x, collapse="\n "), ")", sep="")
}
print_list(fun())
Adding that to your existing function:
fun <- function(){
cat("list(", paste(list(c(1,2,3), c(4,5,6), c(7,8,9)), collapse="\n "), ")", sep="")
}
As noted in the comments, you could (if not bothered about the line breaks) use dput(fun())

Related

Putting a value in a sublist

I have data as follows:
alist <- list()
vec <- c(1, 2, 3)
I want to put a value (in this case an object), into a sublist. But when I do:
for (i in 1:length(vec)) {
alist[[i]][1] <- vec
}
this is for some reason not allowed: Error in *tmp* [[i]] : subscript out of bounds.
Do I have to intialise every sublist? If so, what is the syntax for doing that?
Desired outcome:
desired_out <- list( list(alist = c(1, 2, 3) ), list(alist = c(1, 2, 3) ), list(alist = c(1, 2, 3) ))
EDIT:
An attempt to create a reproducible example for the entire loop (my actual data is a loop within a loop):
alist <- list()
vec <- c(1, 2, 3)
for (j in 1:2) {
for (i in 1:length(vec)) {
alist[[i]][j] <- vec
}
}
Create an empty list with length then fill it in:
x <- vector(mode = "list", length = 3)
for (j in 1:2) {
for (i in 1:length(vec)) {
# notice double square brackets
x[[ i ]][[ j ]] <- vec
}
}
This does what you ask for, but I think your example is still to minimal for your original nested loop problem:
alist <- list()
vec <- c(1, 2, 3)
for (i in 1:length(vec)) {
alist[[i]] <- list(alist = vec)
}
desired_out <- list(
list(alist = c(1, 2, 3)),
list(alist = c(1, 2, 3)),
list(alist = c(1, 2, 3))
)
identical(alist, desired_out)
#> [1] TRUE
Created on 2021-11-05 by the reprex package (v2.0.1)

Providing the correct match for a list in a list of lists in R

I have a list of lists in R:
a <- list(x=0, y=c(1,2,3), z=4)
b <- list(x=1, y=c(1,2,3), z=44)
c <- list(x=2, y=c(1,2,3), z=444)
L <- list(a,b,c)
For a given list, say
l <- list(x=0, y=c(1,2,3), z=4)
I know want to find the correct index of L where we find the corresponding list that equals l.
Of course, I can use a loop, but since Lis very large, I need a faster solution.
(And is a list even the right choice here?)
We can use stack with identical from base R
which(sapply(L, function(x) identical(stack(l), stack(x))))
#[1] 1
Or more compactly
which(sapply(L, identical, l))
#[1] 1
Using mapply to compare each element one by one with l. If you unlist it, all should be TRUE. Using which around an sapply finally gives the number of the matching element.
f <- function(x) all(unlist(mapply(`==`, x, l)))
which(sapply(L, f))
# [1] 1
Data:
L <- list(list(x = 0, y = c(1, 2, 3), z = 4), list(x = 1, y = c(1,
2, 3), z = 44), list(x = 2, y = c(1, 2, 3), z = 444))
l <- list(x = 0, y = c(1, 2, 3), z = 4)
Perhaps you can try mapply like below
> which(mapply(identical, L, list(l)))
[1] 1

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"

Resources