转置嵌套列表

transpose nested list

我有一个列表结构,代表 table 像这样交给我

> l = list(list(1, 4), list(2, 5), list(3, 6))
> str(l)
List of 3
 $ :List of 2
  ..$ : num 1
  ..$ : num 4
 $ :List of 2
  ..$ : num 2
  ..$ : num 5
 $ :List of 2
  ..$ : num 3
  ..$ : num 6

我想把它转换成这个

> lt = list(x = c(1, 2, 3), y = c(4, 5, 6))
> str(lt)
List of 2
 $ x: num [1:3] 1 2 3
 $ y: num [1:3] 4 5 6

我已经编写了一个函数,它使用 Reduce 以非常简单的方式执行此操作,但我觉得必须有更聪明的方法来执行此操作。

感谢任何帮助, 谢谢


基准

谢谢大家!非常感激。对答案进行基准测试并为更大的测试用例选择最快的答案:

f1 = function(l) {
  k <- length(unlist(l)) / length(l) 
  lapply(seq_len(k), function(i) sapply(l, "[[", i))
}

f2 = function(l) {
  n <- length(l[[1]])
  split(unlist(l, use.names = FALSE), paste0("x", seq_len(n)))
}

f3 = function(l) {
  split(do.call(cbind, lapply(l, unlist)), seq(unique(lengths(l))))
}

f4 = function(l) { 
  l %>% 
    purrr::transpose() %>%
    map(unlist)
}

f5 = function(l) {
  # bind lists together into a matrix (of lists)
  temp <- Reduce(rbind, l)
  # split unlisted values using indices of columns
  split(unlist(temp), col(temp))
}

f6 = function(l) {
  data.table::transpose(lapply(l, unlist))
}

microbenchmark::microbenchmark(
  lapply     = f1(l),
  split_seq  = f2(l),
  unique     = f3(l),
  tidy       = f4(l),
  Reduce     = f5(l),
  dt         = f6(l),
  times      = 10000
)

Unit: microseconds
      expr     min       lq     mean   median       uq      max neval
    lapply 165.057 179.6160 199.9383 186.2460 195.0005 4983.883 10000
 split_seq  85.655  94.6820 107.5544  98.5725 104.1175 4609.378 10000
    unique 144.908 159.6365 182.2863 165.9625 174.7485 3905.093 10000
      tidy  99.547 122.8340 141.9482 129.3565 138.3005 8545.215 10000
    Reduce 172.039 190.2235 216.3554 196.8965 206.8545 3652.939 10000
        dt  98.072 106.6200 120.0749 110.0985 116.0950 3353.926 10000

我们可以使用

library(tidyverse)
r1 <- l %>% 
        transpose %>%
        map(unlist)
identical(r1, unname(lt))
#[1] TRUE

这是取消列出每个列表的一个想法,即

split(do.call(cbind, lapply(l, unlist)), seq(unique(lengths(l))))

这给出了,

$`1`
[1] 1 2 3

$`2`
[1] 4 5 6

在两行中使用 Reducesplit 的第二个基本 R 方法是

# bind lists together into a matrix (of lists)
temp <- Reduce(rbind, l)
# split unlisted values using indices of columns
split(unlist(temp), col(temp))
$`1`
[1] 1 2 3

$`2`
[1] 4 5 6

这假定每个列表项都具有相同数量的元素。如果需要,您可以在第二行添加名称 setNames:

setNames(split(unlist(temp), col(temp)), c("x", "y"))

对于具体示例,您可以使用这种非常简单的方法:

split(unlist(l), c("x", "y"))
#$x
#[1] 1 2 3
#
#$y
#[1] 4 5 6

它回收 x-y 向量并在其上拆分。


要将其概括为每个列表中的 "n" 个元素,您可以使用:

l = list(list(1, 4, 5), list(2, 5, 5), list(3, 6, 5)) # larger test case

split(unlist(l, use.names = FALSE), paste0("x", seq_len(length(l[[1L]]))))
# $x1
# [1] 1 2 3
# 
# $x2
# [1] 4 5 6
# 
# $x3
# [1] 5 5 5

这假定 l 顶层的所有列表元素都具有相同的长度,如您的示例所示。

sapply 提取 l 每个分量的第 i 个元素,创建一个数字向量,lapply 将其应用于 1:2(因为有 k=2 l 的每个组件中的元素)。

如果您知道 k 是 2,那么第一行可以替换为 k <- 2。另请注意,在第一行中,我们除以 max(..., 1) 以避免在 l 是零长度列表的情况下除以 0。

下面的代码给出了问题中显示的输出;但是,主题指的是嵌套列表,如果我们想要列表列表而不是数字向量列表,那么我们可以将 sapply 替换为 lapply.

k <- length(unlist(l)) / max(length(l) , 1)
lapply(seq_len(k), function(i) sapply(l, "[[", i))

给予:

[[1]]
[1] 1 2 3

[[2]]
[1] 4 5 6