Display names of column of recursive list as tree - r

Is there a (built'in/easy) way to recursively display the names of a interlinked list as a tree? (with possibly an output similar to the tree shell command. )
For instance with list X, with two column A and B, A consiting in two subcolumn a1 and a2
nametree(x)
X
├── A
│   ├── a1
│   └── a2
└── B
names(X) would just display [1] "A" "B"

Here is a recursive solution:
nametree <- function(X, prefix = "")
if( is.list(X) )
for( i in seq_along(X) ) {
cat( prefix, names(X)[i], "\n", sep="" )
nametree(X[[i]], paste0(prefix, " "))
}
X <- list(X = list( A = list( a1=1:10, a2=1:10 ), B = 1:10 ))
nametree(X)
# X
# A
# a1
# a2
# B
Displaying the tree structure with branches rather than spaces is slightly trickier:
nametree <- function(X, prefix1 = "", prefix2 = "", prefix3 = "", prefix4 = "")
if( is.list(X) )
for( i in seq_along(X) ) {
cat( if(i<length(X)) prefix1 else prefix3, names(X)[i], "\n", sep="" )
prefix <- if( i<length(X) ) prefix2 else prefix4
nametree(
X[[i]],
paste0(prefix, "├──"),
paste0(prefix, "│ "),
paste0(prefix, "└──"),
paste0(prefix, " ")
)
}
nametree(X)
# X
# +--A
# ¦ +--a1
# ¦ +--a2
# +--B
# +--C
# +--a
# +--b

A simple example:
> mylist <- list(A=data.frame(A1=1:3,A2=4:6),B=7:9)
> out <- lapply(mylist,names)
$A
[1] "A1" "A2"
$B
NULL
This assumes you only have dataframes one level below the list...so it's not recursive per se, but it sounds like this is similar to your data structure.
DrMike and Henrik's suggestion to use str(mylist) will be recursive and is, in fact, able to control both how deep into the structure and the display of the output.
SimonO101's example of recursion:
> df <- data.frame( A = runif(3) , B = runif(3) )
> ll <- list( A = df , B = list( C = df , D = df ) , E = 1 )
> str(ll)
List of 3
$ A:'data.frame': 3 obs. of 2 variables:
..$ A: num [1:3] 0.948 0.356 0.467
..$ B: num [1:3] 0.2319 0.7574 0.0312
$ B:List of 2
..$ C:'data.frame': 3 obs. of 2 variables:
.. ..$ A: num [1:3] 0.948 0.356 0.467
.. ..$ B: num [1:3] 0.2319 0.7574 0.0312
..$ D:'data.frame': 3 obs. of 2 variables:
.. ..$ A: num [1:3] 0.948 0.356 0.467
.. ..$ B: num [1:3] 0.2319 0.7574 0.0312
$ E: num 1
Some examples of output:
> str(mylist)
List of 2
$ A:'data.frame': 3 obs. of 2 variables:
..$ A1: int [1:3] 1 2 3
..$ A2: int [1:3] 4 5 6
$ B: int [1:3] 7 8 9
> str(mylist, give.attr=FALSE, give.length=FALSE, give.head=FALSE, vec.len=0,
indent.str="|", comp.str="----")
List of 2
|----A:'data.frame': 3 obs. of 2 variables:
| ..$ A1:NULL ...
| ..$ A2:NULL ...
|----B:NULL ...

You can use the data.tree package. For example:
x <- list( A = list( a1 = list(data = 1:10), b1 = list(data = 1:100 )), B = list(data = c(1, 3, 5) ))
library(data.tree)
xtree <- FromListSimple(x, nodeName = "X")
xtree
This prints out:
levelName
1 X
2 ¦--A
3 ¦ ¦--a1
4 ¦ °--b1
5 °--B
Or you can convert the data into a printable format:
print(xtree, maxData = function(node) if (is.null(node$data)) 0 else max(node$data))
Which shows:
levelName maxData
1 X 0
2 ¦--A 0
3 ¦ ¦--a1 10
4 ¦ °--b1 100
5 °--B 5
Finally, to show the names of a node:
names(xtree$children)
This prints:
[1] "A" "B"

Here's what I came up with, see function definition at the bottom.
sample data:
# a short list
l1 <- list(a = factor("1"), b = c(u = 3, v = 4), d= list(x=5, y =6), e= 8, f = 9)
# a longer list
l2 <- replicate(100, l1, simplify = F)
default way of printing a short list:
print_list(l1)
#> $a
#> [1] 1
#> Levels: 1
#> $b
#> u v
#> 3 4
#> $d
#> $x
#> [1] 5
#> $y
#> [1] 6
#> $e
#> [1] 8
#> $f
#> [1] 9
restrict to first 3 items when named :
print_list(l1,n_named = 3)
#> $a
#> [1] 1
#> Levels: 1
#> $b
#> u v
#> 3 4
#> $d
#> $x
#> [1] 5
#> $y
#> [1] 6
#> # + 2 named items
pass parameters to print()
print_list(l1, quote = TRUE)
#> $a
#> [1] "1"
#> Levels: "1"
#> $b
#> u v
#> 3 4
#> $d
#> $x
#> [1] 5
#> $y
#> [1] 6
#> $e
#> [1] 8
#> $f
#> [1] 9
use str() rather than print() on non list items:
print_list(l1, fun = str)
#> $a
#> Factor w/ 1 level "1": 1
#> $b
#> Named num [1:2] 3 4
#> - attr(*, "names")= chr [1:2] "u" "v"
#> $d
#> $x
#> num 5
#> $y
#> num 6
#> $e
#> num 8
#> $f
#> num 9
use invisible rather than print to display only names:
print_list(l1, fun = invisible)
#> $a
#>
#> $b
#>
#> $d
#> $x
#>
#> $y
#>
#> $e
#>
#> $f
#>
print long list with restrictions:
print_list(l2,n_named = 3, n_unnamed = 2)
#> [[1]]
#> $a
#> [1] 1
#> Levels: 1
#> $b
#> u v
#> 3 4
#> $d
#> $x
#> [1] 5
#> $y
#> [1] 6
#> # + 2 named items
#> [[2]]
#> $a
#> [1] 1
#> Levels: 1
#> $b
#> u v
#> 3 4
#> $d
#> $x
#> [1] 5
#> $y
#> [1] 6
#> # + 2 named items
#> # + 98 items
function code
#' print list nicely
#'
#' #param l list to print
#' #param n_named max number of named items to display if list/sublist contains only named items
#' #param n_unnamed max number of items to display if list/sublist contains unnamed items
#' #param fun function to use to print non list items
#' #param ... additional arguments passed to fun
#'
#' #return unchanged input
#' #export
print_list <- function(l,
n_named = 20,
n_unnamed = 6,
fun = print,
...){
dots <- list(...)
fun0 <- function(l) do.call(fun, c(list(l),dots))
print_list0(l, nm = NULL, i = NULL, indent = -2,
n_named = n_named, n_unnamed = n_unnamed , fun = fun0)
}
print_list0 <- function(l, nm = NULL, i = NULL, indent=-2,
n_named = 20,
n_unnamed = 6,
fun){
if(!is.null(nm)){
if(nm!=""){
cat(strrep(" ", indent), "$", nm,"\n",sep="")
} else {
cat(strrep(" ", indent), "[[", i,"]]\n",sep="")
}
}
if(is.data.frame(l) || !is.list(l)){
output <- capture.output(fun(l))
output <- paste(strrep(" ", indent), output, collapse="\n")
cat(output,"\n")
} else {
nm = allNames(l)
named <- all(nm != "")
if(named && length(l) > n_named){
n_unshowed <- length(l) - n_named
l <- l[seq_len(n_named)]
nm <- nm[seq_len(n_named)]
Map(print_list0, l, nm, i = seq_along(l), indent=indent+2,
n_named = n_named, n_unnamed = n_unnamed,
fun = replicate(length(l), fun))
cat(strrep(" ", indent+2), "# + ", n_unshowed, " named items\n",sep="")
} else if(length(l) > n_unnamed){
n_unshowed <- length(l) - n_unnamed
l <- l[seq_len(n_unnamed)]
nm <- nm[seq_len(n_unnamed)]
Map(print_list0, l, nm, i = seq_along(l), indent=indent+2,
n_named = n_named, n_unnamed = n_unnamed,
fun = replicate(length(l), fun))
cat(strrep(" ", indent+2), "# + ", n_unshowed, " items\n",sep="")
} else {
Map(print_list0, l, nm, i = seq_along(l), indent=indent+2,
n_named = n_named, n_unnamed = n_unnamed,
fun = replicate(length(l), fun))
}
}
invisible(l)
}

Related

Recasting nested list of any depth

Assume this simplified example:
L <- list()
L$Foo <- list()
L$Foo$Bar <- list()
L$Foo$Bar$VAR <- TRUE
L$Lorem <- list()
L$Lorem$Ipsum <- list()
L$Lorem$Ipsum$Dolor <- list()
L$Lorem$Ipsum$Dolor$VAR <- TRUE
I will then melt this list with reshape2::melt(L). That will output the following:
value L3 L2 L4 L1
1 TRUE VAR Bar <NA> Foo
2 TRUE Dolor Ipsum VAR Lorem
After some operations on certain cells in the value column, I'm then looking to recast this melted list into the exact same nested list structure as L—the only difference being that I updated a few of the value instances.
Any ideas how to achieve this? Please keep in mind that the nested lists can have any, and varying, depth.
An option is relist, after we unlisted L
tmp <- unlist(L)
# make small changes
tmp[] <- FALSE
relist(tmp, L)
Result
$Foo
$Foo$Bar
$Foo$Bar$VAR
[1] FALSE
$Lorem
$Lorem$Ipsum
$Lorem$Ipsum$Dolor
$Lorem$Ipsum$Dolor$VAR
[1] FALSE
L looks like
$Foo
$Foo$Bar
$Foo$Bar$VAR
[1] TRUE
$Lorem
$Lorem$Ipsum
$Lorem$Ipsum$Dolor
$Lorem$Ipsum$Dolor$VAR
[1] TRUE
An alternative could be to use rrapply() in the rrapply-package which has options how = "melt" and how = "unmelt" to transform between nested lists and melted data.frames:
library(rrapply)
L <- list(Foo = list(Bar = list(VAR = TRUE)), Lorem = list(Ipsum = list(Dolor = list(VAR = TRUE))))
## melt to data.frame
(L1 <- rrapply(L, how = "melt"))
#> L1 L2 L3 L4 value
#> 1 Foo Bar VAR <NA> TRUE
#> 2 Lorem Ipsum Dolor VAR TRUE
## cast back to nested list
L2 <- rrapply(L1, how = "unmelt")
str(L2)
#> List of 2
#> $ Foo :List of 1
#> ..$ Bar:List of 1
#> .. ..$ VAR: logi TRUE
#> $ Lorem:List of 1
#> ..$ Ipsum:List of 1
#> .. ..$ Dolor:List of 1
#> .. .. ..$ VAR: logi TRUE
identical(L2, L)
#> [1] TRUE
An important advantage with respect to relist() is that no list skeleton object is needed (see ?relist), so we are not constrained by the list format defined in the skeleton object when modifying the melted data.frame, e.g.:
L_unlist <- unlist(as.relistable(L))
## this change has no effect when relisting
## as the original list is used as skeleton
names(L_unlist)[1] <- "Foo.Bar.Test"
relist(L_unlist)
#> $Foo
#> $Foo$Bar
#> $Foo$Bar$VAR
#> [1] TRUE
#>
#> $Lorem
#> $Lorem$Ipsum
#> $Lorem$Ipsum$Dolor
#> $Lorem$Ipsum$Dolor$VAR
#> [1] TRUE
#>
#> attr(,"class")
#> [1] "relistable" "list"
## here it does behave as expected
L_melt <- rrapply(L, how = "melt")
L_melt[1, "L3"] <- "Test"
rrapply(L_melt, how = "unmelt")
#> $Foo
#> $Foo$Bar
#> $Foo$Bar$Test
#> [1] TRUE
#>
#> $Lorem
#> $Lorem$Ipsum
#> $Lorem$Ipsum$Dolor
#> $Lorem$Ipsum$Dolor$VAR
#> [1] TRUE

Combine vector and single row of data frame into a list

I am trying to assemble a list out of a vector and a row of a data
frame. The list will be passed to do.call() as the arguments to a
function. If the vector is length 1, no problem.
tbl <- tibble::tibble(a = 1:4,
b = letters[1:4])
vec <- 1
works <- c(avec = vec, as.list(tbl[1,]))
testit <- function(avec, a, b){
length(avec) + length(a) + length(b)
}
do.call(testit, works)
#> [1] 3
But it also needs to work with longer vectors
vec <- 1:2
broken <- c(avec = vec, as.list(tbl[2,]))# breaks apart avec
do.call(testit, broken)
#> Error in (function (avec, a, b) : unused arguments (avec1 = 1, avec2 = 2)
toomany <- list(avec = vec, as.list(tbl[2,]))#too many layers
do.call(testit, toomany)
#> Error in (function (avec, a, b) : argument "b" is missing, with no default
#what I want:
whatIwant <- list(avec = 1:2, a = 2, b = "b")
do.call(testit, whatIwant)
#> [1] 4
It doesn’t matter if a data frame, and I want solution to work with both
tibbles and dataframes anyhow.
adf <- data.frame(a = 1:4,
b = letters[1:4], stringsAsFactors = FALSE)
list(avec = vec, as.list(adf[1,]))
#> $avec
#> [1] 1 2
#>
#> [[2]]
#> [[2]]$a
#> [1] 1
#>
#> [[2]]$b
#> [1] "a"
Other things I’ve tried.
purrr::flatten(toomany) # breaks up avec again
#> [[1]]
#> [1] 1
#>
#> [[2]]
#> [1] 2
#>
#> $a
#> [1] 2
#>
#> $b
#> [1] "b"
c(avec = vec, as.list(adf[1,]), recursive = TRUE)
#> avec1 avec2 a b
#> "1" "2" "1" "a"
list(avec = vec, as.vector(adf[1,]))
#> $avec
#> [1] 1 2
#>
#> [[2]]
#> a b
#> 1 1 a
list(vec, unlist(adf[1,]))
#> [[1]]
#> [1] 1 2
#>
#> [[2]]
#> a b
#> "1" "a"
I didn’t think this would be so hard! Do I have to assemble the list in
text and parse it? I’m missing something. Created on 2019-03-01 by the
reprex package (v0.2.0).

Equalizing the lengths of all the lists within a list?

I have a list of lists and I want the sub-lists to all have the same length
i.e. to pad them with NAs if needed so they all reach the length of the longest list.
Mock example
list1 <- list(1, 2, 3)
list2 <- list(1, 2, 3, 4, 5)
list3 <- list(1, 2, 3, 4, 5, 6)
list_lists <- list(list1, list2, list3)
My best attempt yet
max_length <- max(unlist(lapply (list_lists, FUN = length)))
# returns the length of the longest list
list_lists <- lapply (list_lists, function (x) length (x) <- max_length)
Problem, it is replacing all my sub-lists into an integer = max_length...
list_lists [[1]]
> [1] 6
Can someone help?
Try this (where ls is your list):
lapply(lapply(sapply(ls, unlist), "length<-", max(lengths(ls))), as.list)
In lists, NULL would seem more appropriate than NA, and could be added with vector:
list_lists <- list(list(1, 2, 3),
list(1, 2, 3, 4, 5),
list(1, 2, 3, 4, 5, 6))
list_lists2 <- Map(function(x, y){c(x, vector('list', length = y))},
list_lists,
max(lengths(list_lists)) - lengths(list_lists))
str(list_lists2)
#> List of 3
#> $ :List of 6
#> ..$ : num 1
#> ..$ : num 2
#> ..$ : num 3
#> ..$ : NULL
#> ..$ : NULL
#> ..$ : NULL
#> $ :List of 6
#> ..$ : num 1
#> ..$ : num 2
#> ..$ : num 3
#> ..$ : num 4
#> ..$ : num 5
#> ..$ : NULL
#> $ :List of 6
#> ..$ : num 1
#> ..$ : num 2
#> ..$ : num 3
#> ..$ : num 4
#> ..$ : num 5
#> ..$ : num 6
If you really want NAs, just change vector to rep:
list_lists3 <- Map(function(x, y){c(x, rep(NA, y))},
list_lists,
max(lengths(list_lists)) - lengths(list_lists))
str(list_lists3)
#> List of 3
#> $ :List of 6
#> ..$ : num 1
#> ..$ : num 2
#> ..$ : num 3
#> ..$ : logi NA
#> ..$ : logi NA
#> ..$ : logi NA
#> $ :List of 6
#> ..$ : num 1
#> ..$ : num 2
#> ..$ : num 3
#> ..$ : num 4
#> ..$ : num 5
#> ..$ : logi NA
#> $ :List of 6
#> ..$ : num 1
#> ..$ : num 2
#> ..$ : num 3
#> ..$ : num 4
#> ..$ : num 5
#> ..$ : num 6
Note the types in the latter won't match up unless you specify NA_real_ or coerce NA to match the type of x.
Here is your code fixed.
The function should return x, not length(x).
Also, I used vectors, not lists for clarity.
list1 <- c(1, 2, 3)
list2 <- c(1, 2, 3, 4, 5)
list3 <- c(1, 2, 3, 4, 5, 6)
list_lists <- list(list1, list2, list3)
max_length <- max(unlist(lapply (list_lists, FUN = length)))
list_lists <- lapply (list_lists, function (x) {length (x) <- max_length;x})
# [[1]]
# [1] 1 2 3 NA NA NA
#
# [[2]]
# [1] 1 2 3 4 5 NA
#
# [[3]]
# [1] 1 2 3 4 5 6
For original lists the result is:
# [[1]]
# [[1]][[1]]
# [1] 1
#
# [[1]][[2]]
# [1] 2
#
# [[1]][[3]]
# [1] 3
#
# [[1]][[4]]
# NULL
#
# [[1]][[5]]
# NULL
#
# [[1]][[6]]
# NULL
#
#
# [[2]]
# [[2]][[1]]
# [1] 1
#
# [[2]][[2]]
# [1] 2
#
# [[2]][[3]]
# [1] 3
#
# [[2]][[4]]
# [1] 4
#
# [[2]][[5]]
# [1] 5
#
# [[2]][[6]]
# NULL
#
#
# [[3]]
# [[3]][[1]]
# [1] 1
#
# [[3]][[2]]
# [1] 2
#
# [[3]][[3]]
# [1] 3
#
# [[3]][[4]]
# [1] 4
#
# [[3]][[5]]
# [1] 5
#
# [[3]][[6]]
# [1] 6
Try this:
funJoeOld <- function(ls) {
list_length <- sapply(ls, length)
max_length <- max(list_length)
lapply(seq_along(ls), function(x) {
if (list_length[x] < max_length) {
c(ls[[x]], lapply(1:(max_length - list_length[x]), function(y) NA))
} else {
ls[[x]]
}
})
}
funJoeOld(list_lists)[[1]]
[[1]]
[1] 1
[[2]]
[1] 2
[[3]]
[1] 3
[[4]]
[1] NA
[[5]]
[1] NA
[[6]]
[1] NA
Edit
Just wanted to illuminate how using the right tools in R makes a huge difference. Although my solution gives correct results, it is very inefficient. By replacing sapply(ls, length) with lengths as well as lapply(1:z, function(y) NA) with as.list(rep(NA, z)), we obtain almost a 15x speed up. Observe:
funJoeNew <- function(ls) {
list_length <- lengths(ls)
max_length <- max(list_length)
lapply(seq_along(ls), function(x) {
if (list_length[x] < max_length) {
c(ls[[x]], as.list(rep(NA, max_length - list_length[x])))
} else {
ls[[x]]
}
})
}
funAlistaire <- function(ls) {
Map(function(x, y){c(x, rep(NA, y))},
ls,
max(lengths(ls)) - lengths(ls))
}
fun989 <- function(ls) {
lapply(lapply(sapply(ls, unlist), "length<-", max(lengths(ls))), as.list)
}
Compare equality
set.seed(123)
samp_list <- lapply(sample(1000, replace = TRUE), function(x) {lapply(1:x, identity)})
## have to unlist as the NAs in 989 are of the integer
## variety and the NAs in Joe/Alistaire are logical
identical(sapply(fun989(samp_list), unlist), sapply(funJoeNew(samp_list), unlist))
[1] TRUE
identical(funJoeNew(samp_list), funAlistaire(samp_list))
[1] TRUE
Benchmarks
microbenchmark(funJoeOld(samp_list), funJoeNew(samp_list), fun989(samp_list),
funAlistaire(samp_list), times = 30, unit = "relative")
Unit: relative
expr min lq mean median uq max neval cld
funJoeOld(samp_list) 21.825878 23.269846 17.434447 20.803035 18.851403 4.8056784 30 c
funJoeNew(samp_list) 1.827741 1.841071 2.253294 1.667047 1.780324 2.4659653 30 ab
fun989(samp_list) 3.108230 3.563780 3.170320 3.790048 3.888632 0.9890681 30 b
funAli(samp_list) 1.000000 1.000000 1.000000 1.000000 1.000000 1.0000000 30 a
There are two take aways here:
Having a good understanding of the apply family of functions makes for
concise and efficient code (as can be seen in #alistaire's and #989's solution).
Understanding the nuances of base R in general can have considerable consequences
Not sure if you are you looking for this and you may use lengths function for lists:
list_lists <- list(unlist(list1), unlist(list2), unlist(list3))
list_lists1 <- lapply(list_lists, `length<-`, max(lengths(list_lists)))
list_lists1
> list_lists1
[[1]]
[1] 1 2 3 NA NA NA
[[2]]
[1] 1 2 3 4 5 NA
[[3]]
[1] 1 2 3 4 5 6
OR for lists of the lists, you can go one step further:
list_lists2 <- lapply(list_lists1,as.list)
> list_lists2
[[1]]
[[1]][[1]]
[1] 1
[[1]][[2]]
[1] 2
[[1]][[3]]
[1] 3
[[1]][[4]]
[1] NA
[[1]][[5]]
[1] NA
[[1]][[6]]
[1] NA
[[2]]
[[2]][[1]]
[1] 1
[[2]][[2]]
[1] 2
[[2]][[3]]
[1] 3
[[2]][[4]]
[1] 4
[[2]][[5]]
[1] 5
[[2]][[6]]
[1] NA
[[3]]
[[3]][[1]]
[1] 1
[[3]][[2]]
[1] 2
[[3]][[3]]
[1] 3
[[3]][[4]]
[1] 4
[[3]][[5]]
[1] 5
[[3]][[6]]
[1] 6
>

How to manipulate NULL elements in a nested list?

I have a nested list containing NULL elements, and I'd like to replace those with something else. For example:
l <- list(
NULL,
1,
list(
2,
NULL,
list(
3,
NULL
)
)
)
I want to replace the NULL elements with NA. The natural way to do this is to recursively loop over the list using rapply. I tried:
rapply(l, function(x) NA, classes = "NULL", how = "replace")
rapply(l, function(x) if(is.null(x)) NA else x, how = "replace")
Unfortunately, neither of these methods work, since rapply apparently ignores NULL elements.
How can I manipulate the NULL elements in a nested list?
I'm going to go with "use a version of rapply doesn't doesn't have weird behaviour with NULL". This is the simplest implementation I can think of:
simple_rapply <- function(x, fn)
{
if(is.list(x))
{
lapply(x, simple_rapply, fn)
} else
{
fn(x)
}
}
(rawr::rapply2, as mentioned in the comments by #rawr is a more sophisticated attempt.)
Now I can do the replacement using
simple_rapply(l, function(x) if(is.null(x)) NA else x)
This is what William Dunlap suggested in 2010 when this question was asked on Rhelp:
replaceInList <- function (x, FUN, ...)
{
if (is.list(x)) {
for (i in seq_along(x)) {
x[i] <- list(replaceInList(x[[i]], FUN, ...))
}
x
}
else FUN(x, ...)
}
replaceInList(l, function(x)if(is.null(x))NA else x)
This is a hack, but as far as hacks go, I think I'm somewhat happy with it.
lna <- eval(parse(text = gsub("NULL", "NA", deparse(l))))
str(lna)
#> List of 3
#> $ : logi NA
#> $ : num 1
#> $ :List of 3
#> ..$ : num 2
#> ..$ : logi NA
#> ..$ :List of 2
#> .. ..$ : num 3
#> .. ..$ : logi NA
Update:
If for some reason you needed "NULL" as a character entry in the list (corner case, much?) you can still use the above hack since it replaces the contents of the string, not the quotes, thus it just requires another step
l2 <- list(
NULL,
1,
list(
2,
"NULL",
list(
3,
NULL
)
)
)
lna2 <- eval(parse(text = gsub("NULL", "NA", deparse(l2))))
lna2_2 <- eval(parse(text = gsub('\\"NA\\"', '\"NULL\"', deparse(lna2))))
str(lna2_2)
#> List of 3
#> $ : logi NA
#> $ : num 1
#> $ :List of 3
#> ..$ : num 2
#> ..$ : chr "NULL"
#> ..$ :List of 2
#> .. ..$ : num 3
#> .. ..$ : logi NA
I wrapped the replacement inside the sapply, which makes it more readable/understandable to me, albeit less general.
replace_null <- function(x) {
lapply(x, function(x) {
if (is.list(x)){
replace_null(x)
} else{
if(is.null(x)) NA else(x)
}
})
}
replace_null(l)
This can also be done with rrapply() in the rrapply-package. Below are a few different ways we could replace the NULL elements in a nested list by NA values:
library(rrapply)
l <- list(
NULL,
1,
list(
2,
NULL,
list(
3,
NULL
)
)
)
## replace NULL by NA using only f
rrapply(l, f = function(x) if(is.null(x)) NA else x, how = "replace")
#> [[1]]
#> [1] NA
#>
#> [[2]]
#> [1] 1
#>
#> [[3]]
#> [[3]][[1]]
#> [1] 2
#>
#> [[3]][[2]]
#> [1] NA
#>
#> [[3]][[3]]
#> [[3]][[3]][[1]]
#> [1] 3
#>
#> [[3]][[3]][[2]]
#> [1] NA
## replace NULL by NA using condition argument
rrapply(l, condition = is.null, f = function(x) NA, how = "replace")
#> [[1]]
#> [1] NA
#>
#> [[2]]
#> [1] 1
#>
#> [[3]]
#> [[3]][[1]]
#> [1] 2
#>
#> [[3]][[2]]
#> [1] NA
#>
#> [[3]][[3]]
#> [[3]][[3]][[1]]
#> [1] 3
#>
#> [[3]][[3]][[2]]
#> [1] NA
## replace NULL by NA using condition and deflt arguments
rrapply(l, condition = Negate(is.null), deflt = NA, how = "list")
#> [[1]]
#> [1] NA
#>
#> [[2]]
#> [1] 1
#>
#> [[3]]
#> [[3]][[1]]
#> [1] 2
#>
#> [[3]][[2]]
#> [1] NA
#>
#> [[3]][[3]]
#> [[3]][[3]][[1]]
#> [1] 3
#>
#> [[3]][[3]][[2]]
#> [1] NA
We can also prune the NULL elements from the list altogether by setting how = "prune":
## keep only non-NULL elements
rrapply(l, condition = Negate(is.null), how = "prune")
#> [[1]]
#> [1] 1
#>
#> [[2]]
#> [[2]][[1]]
#> [1] 2
#>
#> [[2]][[2]]
#> [[2]][[2]][[1]]
#> [1] 3

R the same element to sublists

I have a list m containing sublists. One sublist is like:
> m[[1]]
$input
$input$a
[1] 1
$input$b
[1] 2
$input$c
[1] 3
$output
$output$y
[1] "big dog"
The entire list is:
[[1]]
[[1]]$input
[[1]]$input$a
[1] 1
[[1]]$input$b
[1] 2
[[1]]$input$c
[1] 3
[[1]]$output
[[1]]$output$y
[1] "big dog"
[[2]]
[[2]]$input
[[2]]$input$a
[1] 12
[[2]]$input$b
[1] 89
[[2]]$input$c
[1] 67
[[2]]$output
[[2]]$output$y
[1] "fat cat"
[[3]]
[[3]]$input
[[3]]$input$a
[1] 7
[[3]]$input$b
[1] 4
[[3]]$input$c
[1] 97
[[3]]$output
[[3]]$output$y
[1] "fat cat"
And the code is:
m<-list(
list(
input=list(a=1,b=2,c=3),
output=list(y="big dog")
),
list(
input=list(a=12,b=89,c=67),
output=list(y="fat cat")
),
list(
input=list(a=7,b=4,c=97),
output=list(y="fat cat")
)
)
Now I want to add to each subsublist named 'input' the same variable named type and containing value "pet" to have:
m
[[1]]
[[1]]$input
[[1]]$input$a
[1] 1
[[1]]$input$b
[1] 2
[[1]]$input$c
[1] 3
[[1]]$input$type
[1] "pet"
[[1]]$output
[[1]]$output$y
[1] "big dog"
[[2]]
[[2]]$input
[[2]]$input$a
[1] 12
[[2]]$input$b
[1] 89
[[2]]$input$c
[1] 67
[[2]]$input$type
[1] "pet"
[[2]]$output
[[2]]$output$y
[1] "fat cat"
[[3]]
[[3]]$input
[[3]]$input$a
[1] 7
[[3]]$input$b
[1] 4
[[3]]$input$c
[1] 97
[[3]]$input$type
[1] "pet"
[[3]]$output
[[3]]$output$y
[1] "fat cat"
I tried:
Map(function(u) c(u$input, type="pet"))
But it does not work as it filter the list :(
Do yo have any idea?
This should do the trick:
m2 <- lapply(m, modifyList, list(input=list(type="pet")))
## Check that it worked
str(m2[3])
# List of 1
# $ :List of 2
# ..$ input :List of 4
# .. ..$ a : num 7
# .. ..$ b : num 4
# .. ..$ c : num 97
# .. ..$ type: chr "pet"
# ..$ output:List of 1
# .. ..$ y: chr "fat cat"
If the formulation above seems a bit opaque, try running the following, which will likely help you to see how modifyList() works in this case:
m[[1]]
list(input=list(type="pet"))
modifyList(m[[1]], list(input=list(type="pet")))
If I understand you correctly, I think you just want
m<-lapply(m, function(x) {x$input$type<-"pet";x})
That will add $type="pet" to every input in your list. Resulting in
list(
list(
input = list(a = 1, b = 2, c = 3, type = "pet"),
output = list(y = "big dog")
),
list(
input = list(a = 12, b = 89, c = 67, type = "pet"),
output = list(y = "fat cat")
),
list(
input = list(a = 7, b = 4, c = 97, type = "pet"),
output = list(y = "fat cat")
)
)

Resources