如何操作嵌套列表中的NULL元素?

21

我有一个嵌套列表,其中包含NULL元素,我想用其他东西替换它们。例如:

l <- list(
  NULL,
  1,
  list(
    2,
    NULL,
    list(
      3,
      NULL
    )
  )
)

我希望将NULL元素替换为NA。最自然的方法是使用rapply递归地循环遍历列表。我尝试过:

rapply(l, function(x) NA, classes = "NULL", how = "replace")
rapply(l, function(x) if(is.null(x)) NA else x, how = "replace")

很遗憾,这两种方法都不起作用,因为 rapply 显然会忽略 NULL 元素。

我该如何操作嵌套列表中的 NULL 元素?


2
我最终放弃了使用rapply,而是使用了某种我可以预测其工作方式的东西,这样rapply2(l, function(x) if (is.null(x)) NA else x)就能正常工作了。 - rawr
1
这里有一篇有趣的帖子,讨论了这个问题,但并没有完全回答你如何解决这个问题。 - steveb
在steveb的评论中,他说得很到位。很抱歉@Owen似乎不再花太多时间在这里了。他的帖子总是非常有见地。 - IRTFM
基本上,每个人都想做同样的事情,即重新编写他们自己的递归lapply函数。在我看来,这有点过于繁琐了;也许这应该存在于base R中。 - shayaa
1
@rawr 我想知道你是否在编写这个注释后更改了rapply2()的行为。 - jazzurro
显示剩余2条评论
5个回答

12

我会选择使用一个版本的rapply,它不会对NULL表现出奇怪的行为。这是我能想到的最简单的实现:

simple_rapply <- function(x, fn)
{
  if(is.list(x))
  {
    lapply(x, simple_rapply, fn)
  } else
  {
    fn(x)
  }
}

(如评论中@rawr所提及的rawr::rapply2,是一种更为复杂的尝试。)

现在我可以使用以下方法进行替换:

simple_rapply(l, function(x) if(is.null(x)) NA else x)

11

这是William Dunlap在2010年当Rhelp上问及此问题时提出的建议:

replaceInList <- function (x, FUN, ...) 
  {
      if (is.list(x)) {
          for (i in seq_along(x)) {
              x[i] <- list(replaceInList(x[[i]], FUN, ...))
          }
          x
      }
      else FUN(x, ...)
  }
 replaceInList(l, function(x)if(is.null(x))NA else x)

6

这是一个hack(技巧),但就hack而言,我认为我对它还算满意。

lna <- eval(parse(text = gsub("NULL", "NA", deparse(l))))

str(lna)
#> List of 3
#> $ : logi NA
#> $ : num 1
#> $ :List of 3
#> ..$ : num 2
#> ..$ : logi NA
#> ..$ :List of 2
#> .. ..$ : num 3
#> .. ..$ : logi NA

更新:

如果出于某种原因,您需要将"NULL"作为字符条目输入列表中(极端情况,很少见?),您仍然可以使用上面的技巧,因为它替换的是字符串的内容,而不是引号,因此仅需要另一步操作。

l2 <- list(
  NULL,
  1,
  list(
    2,
    "NULL",
    list(
      3,
      NULL
    )
  )
)

lna2   <- eval(parse(text = gsub("NULL", "NA", deparse(l2))))
lna2_2 <- eval(parse(text = gsub('\\"NA\\"', '\"NULL\"', deparse(lna2))))

str(lna2_2)
#> List of 3
#> $ : logi NA
#> $ : num 1
#> $ :List of 3
#> ..$ : num 2
#> ..$ : chr "NULL"
#> ..$ :List of 2
#> .. ..$ : num 3
#> .. ..$ : logi NA 

这是一个非常好的技巧,但我担心eval中的黑魔法。所以这个例子很好,但是一个名为“NULL”的字符串会让它混淆。 - Richie Cotton
3
看到这种将 R 转换为宏处理器的尝试有点有趣。不是“糟糕”,只是有趣。 - IRTFM
尝试使用字符串“NULL”进行实验,引号不变,所以您只需要第二步...编辑。 - Jonathan Carroll
@JonathanCarroll 另一个恶劣的边缘情况:如果元素名称之一是“NA”,则第二个版本会出错。(该名称将被转换为“NULL”。) - Richie Cotton
@42- 我认为这很糟糕。而且(取决于列表的内容)可能极其低效。想象一下解析/分析包含一些大对象的列表。 - Roland
显示剩余3条评论

5

我将替换操作包含在sapply内,这样对我来说更易读/理解,尽管不太通用。

 replace_null <- function(x) {
  lapply(x, function(x) {
    if (is.list(x)){
      replace_null(x)
      } else{
        if(is.null(x)) NA else(x)
      } 
    })
}

replace_null(l)

不要使用sapplysimplify = FALSE,而是使用lapply。同时应该避免使用ifelse - Roland
@shayaa 使用 ifelse 而不是 ifelse 意味着你会在非向量输入时遇到问题。尝试将数据框或公式包含为列表的一个元素。 - Richie Cotton
我明白了,而且它也没有向量化的好处,因为这些列表元素中的一个不能是c(1,NULL,1),因为向量会将该元素删除。我想写if(is.null(x)) NA else(x)只是感觉有点笨重。不过我还是会进行编辑。 - shayaa

3

这也可以使用rrapply()rrapply包中完成。以下是我们可以用几种不同的方式将嵌套列表中的NULL元素替换为NA值:

library(rrapply)

l <- list(
    NULL,
    1,
    list(
        2,
        NULL,
        list(
            3,
            NULL
        )
    )
)

## replace NULL by NA using only f
rrapply(l, f = function(x) if(is.null(x)) NA else x, how = "replace")
#> [[1]]
#> [1] NA
#> 
#> [[2]]
#> [1] 1
#> 
#> [[3]]
#> [[3]][[1]]
#> [1] 2
#> 
#> [[3]][[2]]
#> [1] NA
#> 
#> [[3]][[3]]
#> [[3]][[3]][[1]]
#> [1] 3
#> 
#> [[3]][[3]][[2]]
#> [1] NA

## replace NULL by NA using condition argument
rrapply(l, condition = is.null, f = function(x) NA, how = "replace")
#> [[1]]
#> [1] NA
#> 
#> [[2]]
#> [1] 1
#> 
#> [[3]]
#> [[3]][[1]]
#> [1] 2
#> 
#> [[3]][[2]]
#> [1] NA
#> 
#> [[3]][[3]]
#> [[3]][[3]][[1]]
#> [1] 3
#> 
#> [[3]][[3]][[2]]
#> [1] NA

## replace NULL by NA using condition and deflt arguments 
rrapply(l, condition = Negate(is.null), deflt = NA, how = "list")
#> [[1]]
#> [1] NA
#> 
#> [[2]]
#> [1] 1
#> 
#> [[3]]
#> [[3]][[1]]
#> [1] 2
#> 
#> [[3]][[2]]
#> [1] NA
#> 
#> [[3]][[3]]
#> [[3]][[3]][[1]]
#> [1] 3
#> 
#> [[3]][[3]][[2]]
#> [1] NA

我们也可以通过将 how = "prune" 来完全剪掉列表中的 NULL 元素:

## keep only non-NULL elements
rrapply(l, condition = Negate(is.null), how = "prune")
#> [[1]]
#> [1] 1
#> 
#> [[2]]
#> [[2]][[1]]
#> [1] 2
#> 
#> [[2]][[2]]
#> [[2]][[2]][[1]]
#> [1] 3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接