如何测试列表元素是否存在?


113

问题

我想测试列表中的元素是否存在,这是一个示例

foo <- list(a=1)
exists('foo') 
TRUE   #foo does exist
exists('foo$a') 
FALSE  #suggests that foo$a does not exist
foo$a
[1] 1  #but it does exist

在此示例中,我知道foo$a存在,但是测试返回FALSE

我看了看,?exists发现with(foo, exists('a')退货了TRUE,但是不明白为什么exists('foo$a')退货FALSE

问题

  • 为什么要exists('foo$a')退货FALSE
  • 是否使用with(...)首选方法?

1
也许!is.null(foo$a)(或!is.null(foo[["a"]])为了安全起见)?(或exists("a",where=foo)
Ben Bolker,

1
@BenBolker谢谢-会很好的回答;为什么选择后一种选择?
David LeBauer 2011年

3
@David部分匹配...尝试通过foo <- list(a1=1)
baptiste

Answers:


151

这实际上比您想象的要棘手。由于列表实际上可以(尽力而为)包含NULL元素,因此检查清单可能还不够is.null(foo$a)。更严格的测试可能是检查名称是否确实在列表中定义:

foo <- list(a=42, b=NULL)
foo

is.null(foo[["a"]]) # FALSE
is.null(foo[["b"]]) # TRUE, but the element "exists"...
is.null(foo[["c"]]) # TRUE

"a" %in% names(foo) # TRUE
"b" %in% names(foo) # TRUE
"c" %in% names(foo) # FALSE

... foo[["a"]]比更为安全foo$a,因为后者使用部分匹配,因此也可能匹配更长的名称:

x <- list(abc=4)
x$a  # 4, since it partially matches abc
x[["a"]] # NULL, no match

[更新]因此,回到问题为何exists('foo$a')不起作用。该exists函数仅检查环境中是否存在变量,而不检查对象的一部分是否存在。该字符串"foo$a"在文学上被解释为:是否有一个名为“ foo $ a”的变量?...答案是FALSE...

foo <- list(a=42, b=NULL) # variable "foo" with element "a"
"bar$a" <- 42   # A variable actually called "bar$a"...
ls() # will include "foo" and "bar$a" 
exists("foo$a") # FALSE 
exists("bar$a") # TRUE

2
现在还不清楚-是否有原因exists('foo$a') == FALSE
大卫勒鲍尔

这表明R中通常没有好的解决方案!人们可能想要更复杂的东西(例如测试是否$mylist[[12]]$out$mcerror已定义),而这些东西目前可能会变得非常复杂。
TMS

您是否知道@Jim 答案中指出的where论点?exists
David LeBauer 2014年

"bar$a" <- 42我真的希望这是无效的语法,并且exist(“ foo $ a”)天真地工作。
Andy V

44

检查命名元素的最佳方法是使用exist(),但是以上答案并未正确使用该函数。您需要使用的where参数检查变量的列表。

foo <- list(a=42, b=NULL)

exists('a', where=foo) #TRUE
exists('b', where=foo) #TRUE
exists('c', where=foo) #FALSE

8
exists()在列表上使用确实可以,但是我相信R在检查该名称的对象之前会内部将其强制到环境中,这样效率低下,如果存在任何未命名的元素,可能会导致错误。例如,如果您运行exists('a', list(a=1, 2)),它将给出错误:Error in list2env(list(a = 1, 2), NULL, <environment>) : attempt to use zero-length variable name。转换发生在这里:github.com/wch/r-source/blob/...
WCH

5

这是在其他答案中提出的方法的性能比较。

> foo <- sapply(letters, function(x){runif(5)}, simplify = FALSE)
> microbenchmark::microbenchmark('k' %in% names(foo), 
                                 is.null(foo[['k']]), 
                                 exists('k', where = foo))
Unit: nanoseconds
                     expr  min   lq    mean median   uq   max neval cld
      "k" %in% names(foo)  467  933 1064.31    934  934 10730   100  a 
      is.null(foo[["k"]])    0    0  168.50      1  467  3266   100  a 
 exists("k", where = foo) 6532 6998 7940.78   7232 7465 56917   100   b

如果您打算将列表用作多次访问的快速词典,则该is.null方法可能是唯一可行的选择。我假设它是O(1),而%in%方法是O(n)?


4

@ salient.salamander的略微修改版本,如果要检查完整路径,则可以使用。

Element_Exists_Check = function( full_index_path ){
  tryCatch({
    len_element = length(full_index_path)
    exists_indicator = ifelse(len_element > 0, T, F)
      return(exists_indicator)
  }, error = function(e) {
    return(F)
  })
}

3

尚未提出的一种解决方案是使用长度,该长度可以成功处理NULL。据我所知,除NULL外的所有值的长度都大于0。

x <- list(4, -1, NULL, NA, Inf, -Inf, NaN, T, x = 0, y = "", z = c(1,2,3))
lapply(x, function(el) print(length(el)))
[1] 1
[1] 1
[1] 0
[1] 1
[1] 1
[1] 1
[1] 1
[1] 1
[1] 1
[1] 1
[1] 3

因此,我们可以创建一个简单的函数,该函数可以同时使用命名索引和编号索引:

element.exists <- function(var, element)
{
  tryCatch({
    if(length(var[[element]]) > -1)
      return(T)
  }, error = function(e) {
    return(F)
  })
}

如果该元素不存在,则会导致tryCatch块捕获到超出范围的条件。


3

rlang::has_name() 也可以做到这一点:

foo = list(a = 1, bb = NULL)
rlang::has_name(foo, "a")  # TRUE
rlang::has_name(foo, "b")  # FALSE. No partial matching
rlang::has_name(foo, "bb")  # TRUE. Handles NULL correctly
rlang::has_name(foo, "c")  # FALSE

如您所见,它固有地处理了@Tommy显示的如何使用基数R的所有情况,并适用于带有未命名项的列表。我仍然建议exists("bb", where = foo)按照另一个答案中的建议,以提高可读性,但是has_name如果您有未命名的项目,则可以选择。


0

使用purrr::has_element要检查的的列表元素:

> x <- list(c(1, 2), c(3, 4))
> purrr::has_element(x, c(3, 4))
[1] TRUE
> purrr::has_element(x, c(3, 5))
[1] FALSE

如果元素嵌套/处于任何嵌套级别,它是否起作用?我检查了文档,这是不明确的
大卫LeBauer

@DavidLeBauer,不。在那种情况下,我会使用rapply(类似any(rapply(x, function(v) identical(v, c(3, 4)), how = 'unlist'))
Dmitry Zotikov
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.