还原列表结构

时间:2013-03-07 04:34:42

标签: r list

目标

鉴于列表清单,我的目标是改变其结构(R语言)。

因此,我想将嵌套列表的元素作为第一层列表的元素。

可能更好地说明我的目的。给出:

z <- list(z1 = list(a = 1, b = 2, c = 3), z2 = list(b = 4, a = 1, c = 0))

我想要一个等效于后续R对象的输出:

o <- list(a = list(z1 = 1, z2 = 1), b = list(z1 = 2, z2 = 4), c = list(z1 = 3, z2 = 0))

我的解决方案

我创建了自己的解决方案,我将在下面附上,但请告诉我是否有更好的解决方案。

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
}

执行str(revert_list_str_1(z))我获得了与我想要的相对应的后续输出。

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

但正如我所说我想调查(并学习)更优雅和动态解决方案的存在

实际上只有当所有嵌套列表具有相同的名称(也以不同的顺序)时,我的解决方案才能完全运行。这是因为names(ls[[1]])。我还要指出,它只对2个级别的列表起作用,就像报告的那个级别一样。

那么,你知道其他更有活力的解决方案吗? rapply和/或Filter函数可以用于此任务吗?

结束编辑1。

建议解决方案的分析

我已经对建议的解决方案做了一些分析,你们所有人!。 分析包括验证所有功能的以下几点:

  1. 接受的类(嵌套列表元素)
      如果有不同类型的元素(如果它们是原子的)
    1. ,则
    2. 类型也会被保留
    3. 保留的元素中包含的对象(例如矩阵)
  2. 考虑的列(对于列我的意思是嵌套列表的名称)
    1. 没有忽略常见的列(在这种情况下,“不是”的分类是正面理解
    2. 不保留常见列
    3. 当列不匹配时(仅基于第一个嵌套列表的名称)
  3. 在所有这些情况下分类'是'被理解为第2.1点

    这是我考虑过的所有函数评论与上述分析项目相关):

    # 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)])
    }
    

    注意事项

    从这个分析中可以看出:

    • 函数revert_list_str_7revert_list_str_6是关于嵌套列表名称的最灵活
    • 函数revert_list_str_4revert_list_str_3后面跟我自己的函数足够完整,有很好的权衡。
    • 绝对函数中最完整revert_list_str_7

    基准

    为了完成这项工作,我在这4个函数上做了一些小基准测试(使用microbenchmark R软件包)(每个基准测试 times = 1000 )。

    基准1

    输入:

    list(z1 = list(a = 1, b = 2, c = 3), z2 = list(a = 0, b = 3, d = 22, f = 9))

    结果:

    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
    

    获胜者:revert_list_str_4

    基准2

    输入:

    list(z1 = list(a = 1, b = 2, c = 'ciao'), z2 = list(a = 0, b = 3, c = 5))

    revert_list_str_6被排除,因为它不支持不同类型的嵌套子元素。

    结果:

    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
    

    获胜者:revert_list_str_4

    基准3

    输入:

    list(z1 = list(a = 1, b = m, c = 'ciao'), z2 = list(a = 0, b = 3, c = m))

    m是一个3x3的整数矩阵,revert_list_str_6已被排除。

    结果:

    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
    

    获胜者:revert_list_str_4。再次!

    结束编辑2.

    结论

    首先:感谢所有精彩的解决方案。

    在我看来,如果您提前知道您的列表将具有相同名称的嵌套列表reverse_str_4,那么获胜者将成为表演和支持不同类型之间的最佳折衷。

    最完整的解决方案是revert_list_str_7,尽管与reverse_str_4相比,完全灵活性导致平均性能下降约2.5倍(如果您的嵌套列表具有不同的名称,则非常有用)。

6 个答案:

答案 0 :(得分:12)


编辑 - 从@Josh O'Briens建议和我自己的改进工作

问题是do.call rbind没有调用rbind.data.frame,它会对名称进行一些匹配。 rbind.data.frame应该有效,因为data.frames是列表,每个子列表都是一个列表,所以我们可以直接调用它。

apply(do.call(rbind.data.frame, z), 1, as.list)

然而,虽然这可能是succicint,但它很慢,因为do.call(rbind.data.frame, ...)本来就很慢。


像(分两步)

 # 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

另类

# 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))))

答案 1 :(得分:12)

修改

这是一个更灵活的版本,可用于列表,其元素不一定包含同一组子元素。

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)

原始回答

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

答案 2 :(得分:6)

重塑可以让你走近,

library(reshape)
b = recast(z, L2~L1)
split(b[,-1], b$L2)

答案 3 :(得分:4)

这个简单的解决方案怎么样,这个解决方案完全一般,而且几乎和Josh O&Brien的原始答案一样快,这个答案假定了常见的内部名称(#4)。

zv <- unlist(unname(z), recursive=FALSE)
ans <- split(setNames(zv, rep(names(z), lengths(z))), names(zv))

以下是一个通用版本,它没有名称,而且非常健壮:

invertList <- function(z) {
    zv <- unlist(unname(z), recursive=FALSE)
    zind <- if (is.null(names(zv))) sequence(lengths(z)) else names(zv)
    if (!is.null(names(z)))
        zv <- setNames(zv, rep(names(z), lengths(z)))
    ans <- split(zv, zind)
    if (is.null(names(zv))) 
        ans <- unname(ans)
    ans
}

答案 4 :(得分:2)

最近发布的purrr包含一个功能transpose,其目的是为了恢复&#39;列表结构。 transpose函数有一个主要警告,它匹配位置而不是名称https://cran.r-project.org/web/packages/purrr/purrr.pdf。这意味着它不是上述基准测试1的正确工具。因此,我只考虑下面的基准2和3。

基准2

B2 <- list(z1 = list(a = 1, b = 2, c = 'ciao'), z2 = list(a = 0, b = 3, c = 5))

revert_list_str_8 <-  function(ll) { # @z109620
  transpose(ll)
}

microbenchmark(revert_list_str_1(B2), revert_list_str_3(B2), revert_list_str_4(B2), revert_list_str_7(B2), revert_list_str_8(B2), times = 1e3)
Unit: microseconds
                  expr     min       lq       mean   median       uq      max neval
 revert_list_str_1(B2) 228.752 254.1695 297.066646 268.8325 293.5165 4501.231  1000
 revert_list_str_3(B2) 211.645 232.9070 277.149579 250.9925 278.6090 2512.361  1000
 revert_list_str_4(B2)  79.673  92.3810 112.889130 100.2020 111.4430 2522.625  1000
 revert_list_str_7(B2) 237.062 252.7030 293.978956 264.9230 289.1175 4838.982  1000
 revert_list_str_8(B2)   2.445   6.8440   9.503552   9.2880  12.2200  148.591  1000

显然,transpose功能是赢家!它还使用了更少的代码。

基准3

B3 <- list(z1 = list(a = 1, b = m, c = 'ciao'), z2 = list(a = 0, b = 3, c = m))

microbenchmark(revert_list_str_1(B3), revert_list_str_3(B3), revert_list_str_4(B3), revert_list_str_7(B3), revert_list_str_8(B3), times = 1e3)

 Unit: microseconds
                  expr     min       lq       mean  median      uq      max neval
 revert_list_str_1(B3) 229.242 253.4360 280.081313 266.877 281.052 2818.341  1000
 revert_list_str_3(B3) 213.600 232.9070 271.793957 248.304 272.743 2739.646  1000
 revert_list_str_4(B3)  80.161  91.8925 109.713969  98.980 108.022 2403.362  1000
 revert_list_str_7(B3) 236.084 254.6580 287.274293 264.922 280.319 2718.628  1000
 revert_list_str_8(B3)   2.933   7.3320   9.140367   9.287  11.243   55.233  1000

同样,transpose优于其他所有人。

上述基准测试的问题在于它们专注于非常小的列表。出于这个原因,嵌套在函数1-7中的众多循环不会造成太多问题。随着列表的大小以及迭代次数的增加,transpose的速度增益可能会增加。

purrr包真棒!它比恢复列表要多得多。结合dplyr包,purrr包可以使用强大的功能编程范例完成大部分编码。感谢哈德利的领主!

答案 5 :(得分:0)

我想为这个有价值的收藏(我已经转过很多遍)添加进一步的解决方案:

revert_list_str_9 <- function(x) do.call(Map, c(c, x))

如果这是代码高尔夫,那么我们将有明显的赢家!当然,这要求各个列表条目的顺序相同。可以使用上面的各种想法来扩展它,例如

revert_list_str_10 <- function(x) {
  nme <- names(x[[1]]) # from revert_list_str_4
  do.call(Map, c(c, lapply(x, `[`, nme)))
}

revert_list_str_11 <- function(x) {
  nme <- Reduce(unique, lapply(x, names)) # from revert_list_str_3
  do.call(Map, c(c, lapply(x, `[`, nme)))
}

在性能方面也不算太差。如果对物料进行了正确分类,我们将有一个新的基准R解决方案可以胜任。如果没有的话,时间安排仍然很有竞争性。

z <- list(z1 = list(a = 1, b = 2, c = 3), z2 = list(b = 4, a = 1, c = 0))

microbenchmark::microbenchmark(
  revert_list_str_1(z), revert_list_str_2(z),  revert_list_str_3(z),
  revert_list_str_4(z), revert_list_str_5(z),  revert_list_str_7(z),
  revert_list_str_9(z), revert_list_str_10(z), revert_list_str_11(z),
  times = 1e3
)

#> Unit: microseconds
#>                   expr     min       lq      mean   median       uq       max
#>   revert_list_str_1(z)  51.946  60.9845  67.72623  67.2540  69.8215  1293.660
#>   revert_list_str_2(z) 461.287 482.8720 513.21260 490.5495 498.8110  1961.542
#>   revert_list_str_3(z)  80.180  89.4905  99.37570  92.5800  95.3185  1424.012
#>   revert_list_str_4(z)  19.383  24.2765  29.50865  26.9845  29.5385  1262.080
#>   revert_list_str_5(z) 499.433 525.8305 583.67299 533.1135 543.4220 25025.568
#>   revert_list_str_7(z)  56.647  66.1485  74.53956  70.8535  74.2445  1309.346
#>   revert_list_str_9(z)   6.128   7.9100  11.50801  10.2960  11.5240  1591.422
#>  revert_list_str_10(z)   8.455  10.9500  16.06621  13.2945  14.8430  1745.134
#>  revert_list_str_11(z)  14.953  19.8655  26.79825  22.1805  24.2885  2084.615

不幸的是,这不是出于创造,而是出于@thelatemail的考虑。