Skip to contents

[[, [[<-, sb2_rec, and sb2_recin, can perform recursive subset operations on a nested list.
Such recursive subset operations only operate on a single element.
Performing recursive subset operations on multiple elements is not vectorized, and requires a (potentially slow) loop.

The lst_untree() function takes a nested tree-like list, and turns it into a recursive matrix (a matrix of list-elements), allowing vectorized subset operations to be performed on the nested list.
lst_untree() can also simply flatten the list, making it a non-nested list.
See the Examples section to understand how the list will be arranged and named.

The lst_nlists() counts the total number of recursive list-elements inside a list.

Usage

lst_nlists(x)

lst_untree(x, margin, use.names = TRUE)

Arguments

x

a tree-like nested list.

margin

a single integer, indicating how the result should be arranged:

  • margin = 0 produces a simple flattened recursive vector (i.e. list) without dimensions.

  • margin = 1 produces a recursive matrix (i.e. a matrix of list-elements),
    with length(x) rows and n columns,
    where n = sapply(x, lst_nlists) |> max().
    Empty elements will be filled with list(NULL).

  • margin = 2 produces a recursive matrix (i.e. a matrix of list-elements),
    with length(x) columns and n rows,
    where n = sapply(x, lst_nlists) |> max().
    Empty elements will be filled with list(NULL).

use.names

Boolean, indicating if the result should be named.
See section "use.names" for more information.

Value

For lst_untree():

A non-nested (dimensional) list.

Note that if margin = 1 or margin = 2, lst_untree() returns a recursive matrix (i.e. a recursive array with 2 dimensions), not a data.frame.

To turn a nested list into a data.frame instead, one option would be to use:

rrapply(x, how = "melt")



For lst_nlists():

A single integer, giving the total number of recursive list-elements in the given list.


use.names

margin = 0 and use.names = TRUE
If margin = 0 and use.names = TRUE, every element in the flattened list will be named.
Names of nested elements, such as x[["A"]][["B"]][["C"]], will become "A.B.C", as that is the behaviour of the rapply function (which lst_untree() calls internally).
It is therefore advised not to use dots (".") in your list names, and use underscores ("_") instead, before calling lst_untree().
See the rrapply::rrapply function for renaming (and other forms of transforming) recursive subsets of lists.

margin = 1 and use.names = TRUE
If margin == 1 and use.names = TRUE, the rows of resulting recursive matrix will be equal to names(x), but recursive names will not be assigned.

margin = 2 and use.names = TRUE
If margin == 2 and use.names = TRUE, the columns of resulting recursive matrix will be equal to names(x), but recursive names will not be assigned.

use.names = FALSE
If use.names = FALSE, the result will not have any names assigned at all.

Examples


# show-casing how the list-elements are arranged and named ====

x <- list(
  A = list(
    A = list(A = "AAA", B = "AAB"),
    A = list(A  = "AA2A", B = "AA2B"),
    B = list(A = "ABA", B = "ABB"),
    C = letters
  ),
  Y = list(
    Z = list(Z = "YZZ", Y = "YZY"),
    Y = list(Z = "YYZ", Y = "YYY"),
    X = "YX"
  )
)


# un-tree column-wise:
sapply(x, lst_nlists) |> max() # number of rows `y` will have
#> [1] 7
y <- lst_untree(x, margin = 2L, use.names = TRUE)
dim(y)
#> [1] 7 2
print(y)
#>      A            Y    
#> [1,] "AAA"        "YZZ"
#> [2,] "AAB"        "YZY"
#> [3,] "AA2A"       "YYZ"
#> [4,] "AA2B"       "YYY"
#> [5,] "ABA"        "YX" 
#> [6,] "ABB"        NULL 
#> [7,] character,26 NULL 
sb2_x(y, n(1:3, 1:2), 1:ndims(y)) # vectorized selection of multiple recursive elements
#>      A      Y    
#> [1,] "AAA"  "YZZ"
#> [2,] "AAB"  "YZY"
#> [3,] "AA2A" "YYZ"


# un-tree row-wise:
sapply(x, lst_nlists) |> max() # number of columns `y` will have
#> [1] 7
y <- lst_untree(x, margin = 1L, use.names = TRUE)
dim(y)
#> [1] 2 7
print(y)
#>   [,1]  [,2]  [,3]   [,4]   [,5]  [,6]  [,7]        
#> A "AAA" "AAB" "AA2A" "AA2B" "ABA" "ABB" character,26
#> Y "YZZ" "YZY" "YYZ"  "YYY"  "YX"  NULL  NULL        
sb2_x(y, n(1:2, 1:3), 1:ndims(y))  # vectorized selection of multiple recursive elements
#>   [,1]  [,2]  [,3]  
#> A "AAA" "AAB" "AA2A"
#> Y "YZZ" "YZY" "YYZ" 


# simple flattened list:
y <- lst_untree(x, margin = 0, use.names = TRUE)
print(y)
#> $A.A.A
#> [1] "AAA"
#> 
#> $A.A.B
#> [1] "AAB"
#> 
#> $A.A.A
#> [1] "AA2A"
#> 
#> $A.A.B
#> [1] "AA2B"
#> 
#> $A.B.A
#> [1] "ABA"
#> 
#> $A.B.B
#> [1] "ABB"
#> 
#> $A.C
#>  [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s"
#> [20] "t" "u" "v" "w" "x" "y" "z"
#> 
#> $Y.Z.Z
#> [1] "YZZ"
#> 
#> $Y.Z.Y
#> [1] "YZY"
#> 
#> $Y.Y.Z
#> [1] "YYZ"
#> 
#> $Y.Y.Y
#> [1] "YYY"
#> 
#> $Y.X
#> [1] "YX"
#> 
y[["Y.Z.Y"]]
#> [1] "YZY"
x[[c("Y", "Z", "Y")]] # equivalent in the original list
#> [1] "YZY"


################################################################################

# showcasing that only list-elements are recursively flattened ====
# i.e. atomic vectors in recursive subsets remain atomic

x <- lapply(1:10, \(x)list(sample(letters), sample(1:10)))

sapply(x, lst_nlists) |> max()
#> [1] 2
y <- lst_untree(x, margin = 1)
dim(y)
#> [1] 10  2
print(y)
#>       [,1]         [,2]      
#>  [1,] character,26 integer,10
#>  [2,] character,26 integer,10
#>  [3,] character,26 integer,10
#>  [4,] character,26 integer,10
#>  [5,] character,26 integer,10
#>  [6,] character,26 integer,10
#>  [7,] character,26 integer,10
#>  [8,] character,26 integer,10
#>  [9,] character,26 integer,10
#> [10,] character,26 integer,10

lst_untree(x, margin = 1)
#>       [,1]         [,2]      
#>  [1,] character,26 integer,10
#>  [2,] character,26 integer,10
#>  [3,] character,26 integer,10
#>  [4,] character,26 integer,10
#>  [5,] character,26 integer,10
#>  [6,] character,26 integer,10
#>  [7,] character,26 integer,10
#>  [8,] character,26 integer,10
#>  [9,] character,26 integer,10
#> [10,] character,26 integer,10


################################################################################

# showcasing vectorized sub-setting ====
x <- lapply(1:10, \(x) list(
  list(sample(letters[1:10]), sample(LETTERS[1:10])),
  list(sample(month.abb), sample(month.name)),
  list(sample(1:10), rnorm(10))
))
y <- lst_untree(x, 1)

# getting the first recursive elements in the second level/depth in base R:
for(i in seq_along(x)) {
  x[[c(i, c(1L, 1L))]] |> print() # for-loop, slow
}
#>  [1] "d" "h" "j" "e" "i" "c" "a" "g" "b" "f"
#>  [1] "j" "i" "f" "d" "b" "a" "e" "g" "h" "c"
#>  [1] "a" "b" "j" "c" "f" "h" "i" "d" "g" "e"
#>  [1] "g" "j" "i" "f" "e" "b" "d" "h" "c" "a"
#>  [1] "h" "g" "f" "c" "d" "i" "a" "j" "b" "e"
#>  [1] "g" "f" "b" "i" "a" "c" "d" "e" "j" "h"
#>  [1] "e" "d" "c" "a" "f" "b" "g" "h" "i" "j"
#>  [1] "h" "i" "f" "c" "a" "e" "d" "j" "g" "b"
#>  [1] "h" "a" "d" "i" "g" "e" "b" "j" "c" "f"
#>  [1] "e" "d" "i" "b" "a" "c" "j" "h" "g" "f"

# the same, but vectorized using the untree'd list:
sb2_x(y, n(1:nrow(y), 1L), 1:ndims(y)) |> drop() |> print() # vectorized, fast
#> [[1]]
#>  [1] "d" "h" "j" "e" "i" "c" "a" "g" "b" "f"
#> 
#> [[2]]
#>  [1] "j" "i" "f" "d" "b" "a" "e" "g" "h" "c"
#> 
#> [[3]]
#>  [1] "a" "b" "j" "c" "f" "h" "i" "d" "g" "e"
#> 
#> [[4]]
#>  [1] "g" "j" "i" "f" "e" "b" "d" "h" "c" "a"
#> 
#> [[5]]
#>  [1] "h" "g" "f" "c" "d" "i" "a" "j" "b" "e"
#> 
#> [[6]]
#>  [1] "g" "f" "b" "i" "a" "c" "d" "e" "j" "h"
#> 
#> [[7]]
#>  [1] "e" "d" "c" "a" "f" "b" "g" "h" "i" "j"
#> 
#> [[8]]
#>  [1] "h" "i" "f" "c" "a" "e" "d" "j" "g" "b"
#> 
#> [[9]]
#>  [1] "h" "a" "d" "i" "g" "e" "b" "j" "c" "f"
#> 
#> [[10]]
#>  [1] "e" "d" "i" "b" "a" "c" "j" "h" "g" "f"
#>