【问题标题】:Direct update (replace) of sparse data frame is slow and inefficient稀疏数据帧直接更新(替换)速度慢,效率低
【发布时间】:2014-07-26 23:53:32
【问题描述】:

我正在尝试读取数十万个 JSON 文件,并最终将它们放入 dplyr 对象中。但是 JSON 文件不是简单的键值解析,它们需要大量的预处理。预处理是编码的,并且对效率有很好的作用。但我面临的挑战是将每条记录有效地加载到单个对象(data.table 或 dplyr 对象)中。

这是非常稀疏的数据,我将有 2000 多个变量,这些变量大部分都将丢失。每条记录可能有一百个变量集。变量将是字符、逻辑和数字的混合体,我知道每个变量的模式。

我认为避免 R 为每次更新(或一次添加一行)复制对象的最佳方法是创建一个空数据框,然后在从 JSON 文件中提取特定字段后更新它们。但是在数据框中执行此操作非常慢,移动到数据表或 dplyr 对象要好得多,但仍希望将其减少到几分钟而不是几小时。请参阅下面的示例:

timeMe <- function() {
  set.seed(1)
  names = paste0("A", seq(1:1200))

  # try with a data frame
  # outdf <- data.frame(matrix(NA, nrow=100, ncol=1200, dimnames=list(NULL, names)))
  # try with data table
  outdf <- data.table(matrix(NA, nrow=100, ncol=1200, dimnames=list(NULL, names)))

  for(i in seq(100)) {
    # generate 100 columns (real data is in json)
    sparse.cols <- sample(1200, 100)
    # Each record is coming in as a list
    # Each column is either a character, logical, or numeric
    sparse.val <- lapply(sparse.cols, function(i) {
      if(i < 401) {  # logical
        sample(c(TRUE, FALSE), 1) 
      } else if (i < 801) {  # numeric
        sample(seq(10), 1)
      } else { # character
        sample(LETTERS, 1)
      }
    })  # now we have a list with values to populate
    names(sparse.val) <- paste0("A", sparse.cols)

    # and here is the challenge and what takes a long time.
    # want to assign the ith row and the named column with each value
    for(x in names(sparse.val)) {
      val=sparse.val[[x]]
      # this is where the bottleneck is.
      # for data frame
      # outdf[i, x] <- val
      # for data table
      outdf[i, x:=val]
    }
  }  
  outdf
}

我认为每个列的模式可能已在每次更新时设置和重置,但我也通过预先设置每个列类型来尝试这样做,但这并没有帮助。

对我来说,使用 data.frame(上面已注释掉)运行此示例大约需要 22 秒,转换为 data.table 需要 5 秒。我希望有人知道幕后发生的事情,并能提供一种更快的方法来在这里填充数据表。

【问题讨论】:

  • 您是否已将所有 JSON 文件预解析为列表,或者您是否在循环中一次执行一个?

标签: r data.table dplyr


【解决方案1】:

我遵循你的代码,除了你构造sparse.val的部分。您分配列的方式存在小错误。在尝试优化时不要忘记检查答案是否正确:)。

一、data.table的创建:

既然您说您已经知道列的类型,那么预先生成正确的类型很重要。否则,当你这样做时:DT[, LHS := RHS]RHS 类型不等于LHS,RHS 将被强制转换为 LHS 类型。在您的情况下,所有数字和字符值都将转换为逻辑值,因为所有列都是逻辑类型。这不是你想要的。

因此,创建矩阵无济于事(所有列都属于同一类型)+ 它也很慢。相反,我会这样做:

rows = 100L
cols = 1200L
outdf <- setDT(lapply(seq_along(cols), function(i) {
    if (i < 401L) rep(NA, rows)
    else if (i >= 402L & i < 801L) rep(NA_real_, rows)
    else rep(NA_character_, rows)
}))

现在我们设置了正确的类型。接下来,我认为应该是i &gt;= 402L &amp; i &lt; 801L。否则,您将前 401 列分配为逻辑列,然后将前 801 列分配为数字列,鉴于您预先知道列的类型,这没有多大意义,对吧?

二、做names(.) &lt;-:

行:

names(sparse.val) <- paste0("A", sparse.cols)

会创建一个副本,并不是真正需要的。因此我们将删除这一行。

三、耗时的for循环:

for(x in names(sparse.val)) {
    val=sparse.val[[x]]
    outdf[i, x:=val]
}

实际上并没有做你认为它正在做的事情。它没有将val 中的值分配给分配给x 的名称。相反,它(每次)(过度)写入名为x 的列。检查你的输出。


这不是优化的一部分。这只是为了让您知道您在这里真正想要做什么。

for(x in names(sparse.val)) {
    val=sparse.val[[x]]
    outdf[i, (x) := val]
}

注意x 周围的(。现在,将对其进行评估,x 中包含的值将成为val 的值将被分配到的列。这有点微妙,我理解。但是,这是必要的,因为它允许将列 x 创建为 DT[, x := val],您实际上希望将 val 分配给 x


回到优化,好消息是,您的 for 循环耗时很简单:

set(outdf, i=i, j=paste0("A", sparse.cols), value = sparse.val)

这就是data.table通过引用进行子分配 功能派上用场的地方!

把它们放在一起:

你的最终函数如下所示:

timeMe2 <- function() {
    set.seed(1L)

    rows = 100L
    cols = 1200L
    outdf <- as.data.table(lapply(seq_len(cols), function(i) {
        if (i < 401L) rep(NA, rows)
        else if (i >= 402L & i < 801L) rep(NA_real_, rows)
        else sample(rep(NA_character_, rows))
    }))
    setnames(outdf, paste0("A", seq(1:1200)))

    for(i in seq(100)) {
        sparse.cols <- sample(1200L, 100L)
        sparse.val <- lapply(sparse.cols, function(i) {
            if(i < 401L) sample(c(TRUE, FALSE), 1) 
            else if (i >= 402 & i < 801L) sample(seq(10), 1)
            else sample(LETTERS, 1)
        })
        set(outdf, i=i, j=paste0("A", sparse.cols), value = sparse.val)
    }  
    outdf
}

通过这样做,您的解决方案在我的系统上需要 9.84 秒,而上面的功能需要 0.34 秒,大​​约提高了 29 倍。我认为这是您正在寻找的结果。请验证。

HTH

【讨论】:

  • 这极大地改进了时间,并且 set() 并不是代码中最慢的调用。您正确设置了一个名为“x”的列,我发现名称中的空格周围存在另一个缺陷(单独和固定的问题)。此外,您最初用于创建数据表的方法非常优雅,也有助于简化代码。谢谢!
猜你喜欢
  • 2013-12-25
  • 2016-09-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-12
  • 2021-02-01
  • 2022-12-17
  • 1970-01-01
相关资源
最近更新 更多