Given a list of lists my goal is to reverse its structure (R language).
So, I want to bring the elements of the nested lists to be elements of the tier one list.
Probably an example better specifies my purpose. Given:
z <- list(z1 = list(a = 1, b = 2, c = 3), z2 = list(b = 4, a = 1, c = 0))
I want an output equivalent to the subsequent R object:
o <- list(a = list(z1 = 1, z2 = 1), b = list(z1 = 2, z2 = 4), c = list(z1 = 3, z2 = 0))
I created my own solution, which I am attaching below, but let me know if there is some better.
revert_list_str_1 <- function(ls) { res <- lapply(names(ls[[1]]), function(n, env) { name <- paste(n, 'elements', sep = '_') assign(name, vector('list', 0)) inner <- sapply(ls, function(x) { assign(name, c(get(name), x[which(names(x) == n)])) }) names(inner) <- names(ls) inner }) names(res) <- names(ls[[1]]) res }
Executing str(revert_list_str_1(z))
I obtain the subsequent output, corresponding to what I wanted.
List of 3 $ a:List of 2 ..$ z1: num 1 ..$ z2: num 1 $ b:List of 2 ..$ z1: num 2 ..$ z2: num 4 $ c:List of 2 ..$ z1: num 3 ..$ z2: num 0
But as I said I'd like to investigate (and learn) the existence of a more elegant and dynamic solution.
In fact my solution works fully only if all the nested lists have the same names (also in different order). This because of names(ls[[1]])
. I would also point out that it acts only on lists of 2 levels, like the one reported.
So, do you know other solutions that are more dynamic? Can rapply
and/or Filter
functions be useful for this task?
end edit 1.
I've done a little analysis of the proposes solutions, thans you all !. The analysis consists of verifying the following points for all functions:
In all this cases the classification 'yes' is understood positively execept for point 2.1.
This are all the functions I've considered (the comments relate to the analysis items mentioned above):
# yes 1.1 # yes 1.2 # yes 2.1, not 2.2, not 2.3 revert_list_str_1 <- function(ls) { # @leodido # see above } # not 1.1 # not 1.2 # not 2.1, not 2.2, not 2.3 revert_list_str_2 <- function(ls) { # @mnel # convert each component of list to a data.frame # so rbind.data.frame so named elements are matched x <- data.frame((do.call(rbind, lapply(ls, data.frame)))) # convert each column into an appropriately named list o <- lapply(as.list(x), function(i, nam) as.list(`names<-`(i, nam)), nam = rownames(x)) o } # yes 1.1 # yes 1.2 # yes 2.1, not 2.2, yes 2.3 revert_list_str_3 <- function(ls) { # @mnel # unique names nn <- Reduce(unique, lapply(ls, names)) # convert from matrix to list `[` used to ensure correct ordering as.list(data.frame(do.call(rbind,lapply(ls, `[`, nn)))) } # yes 1.1 # yes 1.2 # yes 2.1, not 2.2, yes 2.3 revert_list_str_4 <- function(ls) { # @Josh O'Brien # get sub-elements in same order x <- lapply(ls, `[`, names(ls[[1]])) # stack and reslice apply(do.call(rbind, x), 2, as.list) } # not 1.1 # not 1.2 # not 2.1, not 2.2, not 2.3 revert_list_str_5 <- function(ls) { # @mnel apply(data.frame((do.call(rbind, lapply(ls, data.frame)))), 2, as.list) } # not 1.1 # not 1.2 # not 2.1, yes 2.2, yes 2.3 revert_list_str_6 <- function(ls) { # @baptiste + @Josh O'Brien b <- recast(z, L2 ~ L1) apply(b, 1, as.list) } # yes 1.1 # yes 1.2 # not 2.1, yes 2.2, yes 2.3 revert_list_str_7 <- function(ll) { # @Josh O'Brien nms <- unique(unlist(lapply(ll, function(X) names(X)))) ll <- lapply(ll, function(X) setNames(X[nms], nms)) ll <- apply(do.call(rbind, ll), 2, as.list) lapply(ll, function(X) X[!sapply(X, is.null)]) }
From this analysis emerges that:
revert_list_str_7
and revert_list_str_6
are the most flexible regarding the names of the nested listrevert_list_str_4
, revert_list_str_3
followed by my own function are complete enough, good trade-offs.revert_list_str_7
. To complete the work I've done some little benchmarks (with microbenchmark
R package) on this 4 functions (times = 1000 for each benchmark).
BENCHMARK 1
Input:
list(z1 = list(a = 1, b = 2, c = 3), z2 = list(a = 0, b = 3, d = 22, f = 9))
.
Results:
Unit: microseconds expr min lq median uq max 1 func_1 250.069 467.5645 503.6420 527.5615 2028.780 2 func_3 204.386 393.7340 414.5485 429.6010 3517.438 3 func_4 89.922 173.7030 189.0545 194.8590 1669.178 4 func_6 11295.463 20985.7525 21433.8680 21934.5105 72476.316 5 func_7 348.585 387.0265 656.7270 691.2060 2393.988
Winner: revert_list_str_4
.
BENCHMARK 2
Input:
list(z1 = list(a = 1, b = 2, c = 'ciao'), z2 = list(a = 0, b = 3, c = 5))
.
revert_list_str_6
excluded because it does not support different type of nested child elements.
Results:
Unit: microseconds expr min lq median uq max 1 func_1 249.558 483.2120 502.0915 550.7215 2096.978 2 func_3 210.899 387.6835 400.7055 447.3785 1980.912 3 func_4 92.420 170.9970 182.0335 192.8645 1857.582 4 func_7 257.772 469.9280 477.8795 487.3705 2035.101
Winner: revert_list_str_4
.
BENCHMARK 3
Input:
list(z1 = list(a = 1, b = m, c = 'ciao'), z2 = list(a = 0, b = 3, c = m))
.
m
is a matrix 3x3 of integers and revert_list_str_6
has been excluded again.
Results:
Unit: microseconds expr min lq median uq max 1 func_1 261.173 484.6345 503.4085 551.6600 2300.750 2 func_3 209.322 393.7235 406.6895 449.7870 2118.252 3 func_4 91.556 174.2685 184.5595 196.2155 1602.983 4 func_7 252.883 474.1735 482.0985 491.9485 2058.306
Winner: revert_list_str_4
. Again!
end edit 2.
First of all: thanks to all, wonderful solutions.
In my opinion if you know in advance that you list will have nested list with the same names reverse_str_4
is the winner as best compromise between performances and support for different types.
The most complete solution is revert_list_str_7
although the full flexibility induces an average of about 2.5 times a worsening of performances compared to reverse_str_4
(useful if your nested list have different names).
Reverse linked list is a linked list created to form a linked list by reversing the links of the list. The head node of the linked list will be the last node of the linked list and the last one will be the head node.
To reverse a list in Python, you can use negative slicing: As you want to slice the whole list, you can omit the start and stop values altogether. To reverse the slicing, specify a negative step value. As you want to include each value in the reversed list, the step size should be -1.
Edit:
Here's a more flexible version that will work on lists whose elements don't necessarily contain the same set of sub-elements.
fun <- function(ll) { nms <- unique(unlist(lapply(ll, function(X) names(X)))) ll <- lapply(ll, function(X) setNames(X[nms], nms)) ll <- apply(do.call(rbind, ll), 2, as.list) lapply(ll, function(X) X[!sapply(X, is.null)]) } ## An example of an 'unbalanced' list z <- list(z1 = list(a = 1, b = 2), z2 = list(b = 4, a = 1, c = 0)) ## Try it out fun(z)
Original answer
z <- list(z1 = list(a = 1, b = 2, c = 3), z2 = list(b = 4, a = 1, c = 0)) zz <- lapply(z, `[`, names(z[[1]])) ## Get sub-elements in same order apply(do.call(rbind, zz), 2, as.list) ## Stack and reslice
The problem was that do.call rbind was not calling rbind.data.frame
which does some matching of names. rbind.data.frame
should work, because data.frames are lists and each sublist is a list, so we could just call it directly.
apply(do.call(rbind.data.frame, z), 1, as.list)
However, while this may be succicint, it is slow because do.call(rbind.data.frame, ...)
is inherently slow.
Something like (in two steps)
# convert each component of z to a data.frame # so rbind.data.frame so named elements are matched x <- data.frame((do.call(rbind, lapply(z, data.frame)))) # convert each column into an appropriately named list o <- lapply(as.list(x), function(i,nam) as.list(`names<-`(i, nam)), nam = rownames(x)) o $a $a$z1 [1] 1 $a$z2 [1] 1 $b $b$z1 [1] 2 $b$z2 [1] 4 $c $c$z1 [1] 3 $c$z2 [1] 0
And an alternative
# unique names nn <- Reduce(unique,lapply(z, names)) # convert from matrix to list `[` used to ensure correct ordering as.list(data.frame(do.call(rbind,lapply(z, `[`, nn))))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With