【问题标题】:Is there a more efficient way to replace NULL with NA in a list?有没有更有效的方法在列表中用 NA 替换 NULL?
【发布时间】:2014-04-04 18:15:29
【问题描述】:

我经常遇到如下结构的数据:

employees <- list(
    list(id = 1,
             dept = "IT",
             age = 29,
             sportsteam = "softball"),
    list(id = 2,
             dept = "IT",
             age = 30,
             sportsteam = NULL),
    list(id = 3,
             dept = "IT",
             age = 29,
             sportsteam = "hockey"),
    list(id = 4,
             dept = NULL,
             age = 29,
             sportsteam = "softball"))

在许多情况下,此类列表可能有数千万个项目,因此内存问题和效率始终是一个问题。

我想将列表转换为数据框,但如果我运行:

library(data.table)
employee.df <- rbindlist(employees)

由于 NULL 值,我得到错误。我的正常策略是使用如下函数:

nullToNA <- function(x) {
    x[sapply(x, is.null)] <- NA
    return(x)
}

然后:

employees <- lapply(employees, nullToNA)
employee.df <- rbindlist(employees)

返回

   id dept age sportsteam
1:  1   IT  29   softball
2:  2   IT  30         NA
3:  3   IT  29     hockey
4:  4   NA  29   softball

但是,nullToNA 函数在应用于 1000 万个案例时非常慢,因此如果有更有效的方法会很好。

似乎减慢了进程的一点是 is.null 函数一次只能应用于一项(不像 is.na 可以一次性扫描完整列表)。

关于如何在大型数据集上有效执行此操作的任何建议?

【问题讨论】:

  • 你试过 do.call 和 rbind 吗?像这样employee.df&lt;-do.call("rbind",employees)
  • 如果为null,原始数据是否实际上是“NULL”,还是只是空的?
  • 原始数据有NULL值。它是通过抓取 JSON 文件并通过 rjson 转换生成的。
  • 在一般情况下,如果原始(抓取的)数据集已经是一个 data.frame(例如,来自XML::readHTMLTable()),并且 NULL 单元格只是长度为 0 的字符串,请使用以下:df &lt;- data.frame(apply(df, c(1,2), FUN=function(x) ifelse(x=="",NA,x)))

标签: r performance list null


【解决方案1】:

R 中的许多效率问题都是通过首先将原始数据更改为使后续流程尽可能快速和简单的形式来解决的。通常,这是矩阵形式。

如果您将所有数据与rbind 放在一起,您的nullToNA 函数就不再需要搜索嵌套列表,因此sapply 可以更有效地发挥其作用(通过矩阵查找)。从理论上讲,这应该会使过程更快。

顺便说一句,问得好。

> dat <- do.call(rbind, lapply(employees, rbind))
> dat
     id dept age sportsteam
[1,] 1  "IT" 29  "softball"
[2,] 2  "IT" 30  NULL      
[3,] 3  "IT" 29  "hockey"  
[4,] 4  NULL 29  "softball"

> nullToNA(dat)
     id dept age sportsteam
[1,] 1  "IT" 29  "softball"
[2,] 2  "IT" 30  NA        
[3,] 3  "IT" 29  "hockey"  
[4,] 4  NA   29  "softball"

【讨论】:

  • 这肯定比我目前的方法快。但是,对于串联失去 rbindlist 的速度是一种耻辱,因为这比 do.call(rbind, employees) 快得多。不过,这可能只是这里的权衡。
  • 那你为什么不这样做lapply(employees, function(x) ifelse(x == "NULL", NA, x))然后使用rbindlist
  • 这会非常简洁,但是生成的列(转换为 data.frame 之后)是列表,这会产生问题。见dat = data.frame(dat); dat[,1]..
  • 大家,这是一个非常糟糕的答案。请不要使用它。 @JonM - 如果可以,请删除复选标记,以便我删除它。
【解决方案2】:

两步法在与rbind 结合后创建一个数据框:

employee.df<-data.frame(do.call("rbind",employees))

现在替换 NULL,我使用“NULL”,因为 R 在加载数据时不会输入 NULL,而是在加载数据时将其作为字符读取。

employee.df.withNA <- sapply(employee.df, function(x) ifelse(x == "NULL", NA, x))

【讨论】:

    【解决方案3】:

    我发现一个更易于阅读的 tidyverse 解决方案是编写一个适用于单个元素的函数并将其映射到所有 NULL。

    我将使用@rich-scriven 的 rbind 和 lapply 方法创建一个矩阵,然后将其转换为数据框。

    library(magrittr)
    
    dat <- do.call(rbind, lapply(employees, rbind)) %>% 
      as.data.frame()
    
    dat
    #>   id dept age sportsteam
    #> 1  1   IT  29   softball
    #> 2  2   IT  30       NULL
    #> 3  3   IT  29     hockey
    #> 4  4 NULL  29   softball
    

    那么我们可以在深度为2处使用purrr::modify_depth()来申请replace_x()

    replace_x <- function(x, replacement = NA_character_) {
      if (length(x) == 0 || length(x[[1]]) == 0) {
        replacement
      } else {
        x
      }
    }
    
    out <- dat %>% 
      purrr::modify_depth(2, replace_x)
    
    out
    #>   id dept age sportsteam
    #> 1  1   IT  29   softball
    #> 2  2   IT  30         NA
    #> 3  3   IT  29     hockey
    #> 4  4   NA  29   softball
    

    【讨论】:

      【解决方案4】:

      我经常发现do.call() 函数难以阅读。我每天使用的一个解决方案(MySQL 输出包含"NULL" 字符值):

      NULL2NA <- function(df) {
        df[, 1:length(df)][df[, 1:length(df)] == 'NULL'] <- NA
        return(df)
      }
      

      但对于所有解决方案:请记住,没有na.rm = TRUENA 不能用于计算,但使用NULL 可以。 NaN 给出了同样的问题。 例如:

      > mean(c(1, 2, 3))
      2
      
      > mean(c(1, 2, NA, 3))
      NA
      
      > mean(c(1, 2, NULL, 3))
      2
      
      > mean(c(1, 2, NaN, 3))
      NaN
      

      【讨论】:

        【解决方案5】:

        所有这些解决方案(我认为)都隐藏了数据表仍然丢失列表而不是向量列表的事实(我在我的应用程序中也没有注意到,直到它在 := 期间开始抛出意外错误之前)。 试试这个:

        data.table(t(sapply(employees, function(x) unlist(lapply(x, function(x) ifelse(is.null(x),NA,x))))))

        我相信它可以正常工作,但我不确定它是否会受到缓慢的影响并且可以进一步优化。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-07-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-11-05
          相关资源
          最近更新 更多