R 中子子列表元素的向量化替换

cb1*_*b14 6 loops r vectorization nested-lists

我有一个有点复杂的数据结构(嵌套列表)y,定义为:

x <- list(
  list(1, "a", 2, "b", 0.1),
  list(3, "c", 4, "d", 0.2),
  list(5, "e", 6, "f", 0.3)
)
y <- rep(list(x), 10)
Run Code Online (Sandbox Code Playgroud)

我还有一个数据框df,定义为:

df <- data.frame(
  x1 = c( 0.33, 1.67, -0.62, -0.56,  0.17, 0.73,  0.59,  0.56, -0.22, 1.49),
  x2 = c(-0.82, 1.22,  0.65,  0.54, -2.26, 1.21, -0.44, -0.92, -0.56, 0.50),
  x3 = c(-0.16, 0.49, -0.82, -0.71,  0.13, 1.22,  1.23, -0.01, -1.11, 0.97)
)
Run Code Online (Sandbox Code Playgroud)

其中列名并不重要。

我想将所有和替换y[[i]][[j]][[5]]为。我的 Python/Julia 大脑在循环中工作得最好,因此我通过循环,然后循环 的元素(每个 的副本)来完成此操作,如下所示:df[[i, j]]ijyyx

for (i in seq_along(y)) {
  for (j in 1:3) {
    y[[i]][[j]][[5]] <- df[[i, j]]
  }
}
Run Code Online (Sandbox Code Playgroud)

这可行,但对于我更大的数据集来说,它真的很慢。所以我试图向量化嵌套for循环。我一直在尝试Map():

y_new <- y
for (j in 1:3) {
  y_new <- Map(function(sublist, value) { sublist[[5]] <- value; sublist },
               y_new, df[, j])
}
Run Code Online (Sandbox Code Playgroud)

但上面的方法不起作用,因为identical(y, y_new)returns FALSE。我认为我缺少一定程度的子集化。

Map()我根本没有结婚。我只是在寻找嵌套循环的最快替代方案for。

Mik*_*gan 6

@akrun 关于取消列出和重新列出的建议非常优雅且非常惯用(“类似 R”)。

但我希望在没有强制的情况下做到这一点,尤其是从数字到字符再返回,这可能会很慢并导致精度损失。像这样的事情会更快更安全:

unlist0 <- function(x) unlist(x, recursive = FALSE, use.names = FALSE)
split0 <- function(x, f) unname(split(x, f))

n <- length(y) # 10
n1 <- length(y[[1L]]) # 3
n11 <- length(y[[1L]][[1L]]) # 5

uy <- unlist0(unlist0(y))
uy[seq.int(n11, n * n1 * n11, n11)] <- as.list(t(df))
suy <- split0(split0(uy, gl(n * n1, n11)), gl(n, n1))
Run Code Online (Sandbox Code Playgroud)

这是一个基准:

unlist0 <- function(x) unlist(x, recursive = FALSE, use.names = FALSE)
split0 <- function(x, f) unname(split(x, f))

n <- length(y) # 10
n1 <- length(y[[1L]]) # 3
n11 <- length(y[[1L]][[1L]]) # 5

library(purrr)

microbenchmark::microbenchmark(
colebrookson =
    {
        ans <- y
        for (i in seq_len(n))
            for (j in seq_len(n1))
                ans[[i]][[j]][[n11]] <- df[[i, j]]
        ans
    },
TarJae =
    {
        map2(y, asplit(df, 1L), ~ map2(.x, .y, ~ { .x[[n11]] <- .y; .x }))
    },
akrun.1 = 
    {
        Map(function(u, v) Map(function(uu, vv) { uu[5L] <- vv; uu }, u, v), y, asplit(df, 1L))
    },
akrun.2 = 
    {
        uy <- unlist(y)
        uy[seq.int(n11, n * n1 * n11, n11)] <- c(t(df))
        type.convert(relist(uy, y), as.is = TRUE)
    },
`Mikael Jagan` =
    {
        uy <- unlist0(unlist0(y))
        uy[seq.int(n11, n * n1 * n11, n11)] <- as.list(t(df))
        split0(split0(uy, gl(n * n1, n11)), gl(n, n1))
    },
times = 1000L
)
Run Code Online (Sandbox Code Playgroud)
Unit: microseconds
         expr      min        lq       mean    median       uq       max neval
 colebrookson 1116.635 1171.6365 1318.72441 1195.2115 1238.077 16936.567  1000
       TarJae  297.783  314.8390  365.66412  331.7105  352.026  1554.679  1000
      akrun.1   76.096   82.4305   96.82716   87.0840   91.676  2076.117  1000
      akrun.2 1206.343 1231.1685 1345.24661 1244.2270 1261.222  5023.197  1000
 Mikael Jagan   35.465   40.9590   51.61260   45.7765   50.594  1271.984  1000
Run Code Online (Sandbox Code Playgroud)

一些备注:

  • 不要阅读小例子的基准测试。
  • 我怀疑只有第四个和第五个答案可以很好地扩展,因为只有它们对替换进行矢量化。
  • 从 的意义上来说,只有第四个答案与第一个答案不同identical。由于精度损失而有所不同。

  • “unlist”的问题在于,默认情况下(“recursive = TRUE”)它会连接树的所有叶子,如果这些叶子具有不同类型,则会导致强制。调用“unlist0”两次可确保我们只删除两层嵌套,留下一个保留其元素类型的列表。“relist”的问题在于它假设我们_did_执行了递归取消列出。因此,我们使用“split0”两次(使用不同的分组)来一一反转“unlist0”调用_而不引入名称_,而“split”本身就是这样做的。 (2认同)