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")
)
)
Related
I have a list with many levels. I want to only keep elements at the 1st level.
Example list:
my_list <-
list(
a.1 = "some text",
b.1 = NA,
c.1 = integer(0),
d.1 = "some text",
e.1 = list(a.2 = "some text", b.2 = "a2"),
f.1 = list(c.2 = "some text", d.2 = integer(10), e.2 = list(a.3 = "some deep text"))
)
... and I'd like to end up with:
my_list2 <-
list(
a.1 = "some text",
b.1 = NA,
c.1 = integer(0),
d.1 = "some text"
)
Given the real list is messy and many levels deep I'd like to be able to use something like purrr::keep to simply remove further nested items.
I have tried using keep but the predicate functions throw back errors:
map_depth(my_list, 1, ~ keep(.x, vec_depth(.x) > 1))
Error in probe(.x, .p, ...) : length(.p) == length(.x) is not TRUE
Thanks.
Or maybe this solution in tidyverse:
library(purrr)
my_list %>%
keep(~ vec_depth(.x) == 1)
$a.1
[1] "some text"
$b.1
[1] NA
$c.1
integer(0)
$d.1
[1] "some text"
You could subset your list to only include items that are not themselves lists using:
my_list[!sapply(my_list, is.list)]
#> $a.1
#> [1] "some text"
#>
#> $b.1
#> [1] NA
#>
#> $c.1
#> integer(0)
#>
#> $d.1
#> [1] "some text"
Using collapse::atomic_elem to "extract [...] the atomic [...] elements at the top-level of the list tree"
collapse::atomic_elem(my_list)
# $a.1
# [1] "some text"
#
# $b.1
# [1] NA
#
# $c.1
# integer(0)
#
# $d.1
# [1] "some text"
You can try Filter + is.list like below
> Filter(Negate(is.list),my_list)
$a.1
[1] "some text"
$b.1
[1] NA
$c.1
integer(0)
$d.1
[1] "some text"
Another approach using rrapply::rrapply() (extended version of base rapply):
library(rrapply)
rrapply(my_list, condition = \(x, .xpos) length(.xpos) == 1, how = "prune") |>
str()
#> List of 4
#> $ a.1: chr "some text"
#> $ b.1: logi NA
#> $ c.1: int(0)
#> $ d.1: chr "some text"
This may be useful as it is easily modified to handle other filter conditions as well. For instance,
rrapply(my_list, condition = \(x) x == "some text", how = "prune") |>
str()
#> List of 4
#> $ a.1: chr "some text"
#> $ d.1: chr "some text"
#> $ e.1:List of 1
#> ..$ a.2: chr "some text"
#> $ f.1:List of 1
#> ..$ c.2: chr "some text"
rrapply(my_list, condition = \(x, .xname) grepl("a", .xname), how = "prune") |>
str()
#> List of 3
#> $ a.1: chr "some text"
#> $ e.1:List of 1
#> ..$ a.2: chr "some text"
#> $ f.1:List of 1
#> ..$ e.2:List of 1
#> .. ..$ a.3: chr "some deep text"
We have a string with curly brackets to represent nested lists:
x <- "{{1,2,3,4},{1,2,3,4,{Axe,Bat,Cat,Donkey}},{1,2,3,4}}"
How can we convert it to a nested list R object?
So far I have below solution, it is not ideal. The idea is to convert it to a valid JSON format, then use jsonlite::fromJSON. I would like to skip/improve quoting step, so that numbers read are numeric. Yes, I could loop through the nested list and convert "numbers" to numeric, but would like to avoid.
Any other non-JSON solutions are welcome, too.
library(jsonlite)
# translate brackets to JSON square brackets
x <- chartr("{}", "[]", x)
# wrap in quotes
x <- gsub("(\\w+)", '"\\1"', x)
# finally read as JSON
fromJSON(x)
# [[1]]
# [1] "1" "2" "3" "4"
#
# [[2]]
# [[2]][[1]]
# [1] "1"
#
# [[2]][[2]]
# [1] "2"
#
# [[2]][[3]]
# [1] "3"
#
# [[2]][[4]]
# [1] "4"
#
# [[2]][[5]]
# [1] "Axe" "Bat" "Cat" "Donkey"
#
#
# [[3]]
# [1] "1" "2" "3" "4"
This solution does not use any packages. It replaces innermost lists with c(...) and then quotes the words that start with a letter and replaces { and } with list( and ) respectively. Then it parses and evaluates that to get the result.
x1 <- gsub("\\{([^{]+)\\}", "c(\\1)", x) # x is from question
x2 <- gsub("([a-zA-Z]\\w+)", "'\\1'", x1)
x3 <- gsub("\\{", "list(", x2)
x4 <- gsub("\\}", ")", x3)
result <- eval(parse(text = x4))
str(result)
giving:
List of 3
$ : num [1:4] 1 2 3 4
$ :List of 5
..$ : num 1
..$ : num 2
..$ : num 3
..$ : num 4
..$ : chr [1:4] "Axe" "Bat" "Cat" "Donkey"
$ : num [1:4] 1 2 3 4
This could also be written as a pipeline using magrittr:
library(magrittr)
x %>%
gsub("\\{([^{]+)\\}", "c(\\1)", .) %>%
gsub("([a-zA-Z]\\w+)", "'\\1'", .) %>%
gsub("\\{", "list(", .) %>%
gsub("\\}", ")", .) %>%
parse(text = .) %>%
eval
You could do:
jsonlite::fromJSON(txt=gsub('([A-Za-z]+)','"\\1"',chartr('{}','[]',x)))
[[1]]
[1] 1 2 3 4
[[2]]
[[2]][[1]]
[1] 1
[[2]][[2]]
[1] 2
[[2]][[3]]
[1] 3
[[2]][[4]]
[1] 4
[[2]][[5]]
[1] "Axe" "Bat" "Cat" "Donkey"
[[3]]
[1] 1 2 3 4
Or you could use alpha ie:
jsonlite::fromJSON(txt=gsub('([[:alpha:]]+)','"\\1"',chartr('{}','[]',x)))
The issue here is to obtain a good regex such that it does not include the numeric elements.
I want to interleave interElem after every 2 list elements.
Data:
listi <- c(rbind(letters[1:4], list(c(13,37))))
interElem <- c("inter","leavistan")
looks like:
> listi
[[1]]
[1] "a"
[[2]]
[1] 13 37
[[3]]
[1] "b"
[[4]]
[1] 13 37
[[5]]
[1] "c"
[[6]]
[1] 13 37
[[7]]
[1] "d"
[[8]]
[1] 13 37
>
Desired result (list element numbers are not accurate)
> listi
[[1]]
[1] "a"
[[2]]
[1] 13 37
[[XXX]]
[1] "inter" "leavistan"
[[3]]
[1] "b"
[[4]]
[1] 13 37
[[XXX]]
[1] "inter" "leavistan"
[[5]]
[1] "c"
[[6]]
[1] 13 37
[[XXX]]
[1] "inter" "leavistan"
[[7]]
[1] "d"
[[8]]
[1] 13 37
>
Here's the length of the new list
len = length(listi) + max(0, floor((length(listi) - 1) / 2))
and the index of the elements that should be the original values
idx = seq_len(len) %% 3 != 0
Use these to create a new list and insert the old and interstitial values
res = vector("list", len)
res[idx] = l
res[!idx] = list(v)
Package as a function for robustness and reuse.
fun = function(l, v, n = 2) {
## validate arguments
stopifnot(
is.numeric(n), length(n) == 1, !is.na(n),
is.list(l), is.vector(v)
)
## length and index for original values
len = length(l) + max(0, floor((length(l) - 1) / n))
idx = seq_len(len) %% (n + 1) != 0
## create and populate result
res = vector("list", len)
res[idx] = l
res[!idx] = list(v)
res
}
with
> str(fun(listi, interElem))
List of 11
$ : chr "a"
$ : num [1:2] 13 37
$ : chr [1:2] "inter" "leavistan"
$ : chr "b"
$ : num [1:2] 13 37
$ : chr [1:2] "inter" "leavistan"
$ : chr "c"
$ : num [1:2] 13 37
$ : chr [1:2] "inter" "leavistan"
$ : chr "d"
$ : num [1:2] 13 37
We can create a grouping variable to split every 2 elements with gl, then append the 'interElem' at the end of each nested list and flatten it with do.call(c
res <- head(do.call(c, lapply(split(listi, as.integer(gl(length(listi), 2,
length(listi)))), function(x) c(x, list(interElem )))), -1)
names(res) <- NULL
Or another option is to convert it to matrix, rbind with 'interElem' and concatenate to list
head(c(rbind(matrix(listi, nrow=2), list(interElem))), -1)
#[[1]]
#[1] "a"
#[[2]]
#[1] 13 37
#[[3]]
#[1] "inter" "leavistan"
#[[4]]
#[1] "b"
#[[5]]
#[1] 13 37
#[[6]]
#[1] "inter" "leavistan"
#[[7]]
#[1] "c"
#[[8]]
#[1] 13 37
#[[9]]
#[1] "inter" "leavistan"
#[[10]]
#[1] "d"
#[[11]]
#[1] 13 37
Or we can use append in a for loop
listn <- listi
i1 <- seq(2, length(listi), by = 2)
i2 <- i1 + (seq_along(i1) - 1)
for(i in seq_along(i2)) listn <- append(listn, list(interElem), after = i2[i])
head(listn, -1)
This works on your example and on lists with odd length, including 2 an 1. If the length of the list is odd, matrix() and cbind() rise warnings. Empty list as an entry returns insert list. All what I am trying to do here is to form the vector of indices for a following subsetting of the aggregated listiand list(interElem).
l <- length(listi)
i <- rbind(matrix(1:l, nrow = 2), rep(l+1,l%/%2))[1:(l + l%/%2 - (l+1)%%2)]
c(listi, list(interElem))[i]
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
>
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)
}