133

Problem

I would like to test if an element of a list exists, here is an example

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

In this example, I know that foo$a exists, but the test returns FALSE.

I looked in ?exists and have found that with(foo, exists('a') returns TRUE, but do not understand why exists('foo$a') returns FALSE.

Questions

  • Why does exists('foo$a') return FALSE?
  • Is use of with(...) the preferred approach?
Community
  • 1
  • 1
David LeBauer
  • 31,011
  • 31
  • 115
  • 189

7 Answers7

181

This is actually a bit trickier than you'd think. Since a list can actually (with some effort) contain NULL elements, it might not be enough to check is.null(foo$a). A more stringent test might be to check that the name is actually defined in the list:

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

...and foo[["a"]] is safer than foo$a, since the latter uses partial matching and thus might also match a longer name:

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

[UPDATE] So, back to the question why exists('foo$a') doesn't work. The exists function only checks if a variable exists in an environment, not if parts of a object exist. The string "foo$a" is interpreted literary: Is there a variable called "foo$a"? ...and the answer is 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
Tommy
  • 39,997
  • 12
  • 90
  • 85
  • 3
    it is still not clear - is there a reason why `exists('foo$a') == FALSE`? – David LeBauer Oct 11 '11 at 02:45
  • This suggests there is generally no good solution for this in R! One might want more complex things (like testing if `$mylist[[12]]$out$mcerror` is defined) which would currently be complicated as hell. – Tomas Sep 11 '14 at 09:51
  • Were you aware of the `where` argument to `exists` pointed out in @Jim's [answer](http://stackoverflow.com/a/25875605/199217)? – David LeBauer Sep 16 '14 at 22:10
  • `"bar$a" <- 42` I really wish this was invalid syntax and exists("foo$a") worked in the naive sense. – Andy V Oct 21 '14 at 19:50
54

The best way to check for named elements is to use exist(), however the above answers are not using the function properly. You need to use the where argument to check for the variable within the list.

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

exists('a', where=foo) #TRUE
exists('b', where=foo) #TRUE
exists('c', where=foo) #FALSE
Jim
  • 4,687
  • 29
  • 30
  • 9
    Using `exists()` on a list does work, but I believe that R internally coerces it to an environment before checking for an object of that name, which is inefficient and can result in errors if there are any unnamed elements. For example if you run `exists('a', list(a=1, 2))`, it will give an error: `Error in list2env(list(a = 1, 2), NULL, ) : attempt to use zero-length variable name`. The conversion happens here: https://github.com/wch/r-source/blob/af7f52f70101960861e5d995d3a4bec010bc89e6/src/main/envir.c#L1943 – wch Dec 05 '16 at 19:51
6

One solution that hasn't come up yet is using length, which successfully handles NULL. As far as I can tell, all values except NULL have a length greater than 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

Thus we could make a simple function that works with both named and numbered indices:

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

If the element doesn't exist, it causes an out-of-bounds condition caught by the tryCatch block.

5

Here is a performance comparison of the proposed methods in other answers.

> 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

If you are planing to use the list as a fast dictionary accessed many times, then the is.null approach might be the only viable option. I assume it is O(1), while the %in% approach is O(n)?

Davor Josipovic
  • 5,296
  • 1
  • 39
  • 57
4

A slight modified version of @salient.salamander , if one wants to check on full path, this can be used.

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)
  })
}
Soumya Boral
  • 1,191
  • 14
  • 28
3

rlang::has_name() can do this too:

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

As you can see, it inherently handles all the cases that @Tommy showed how to handle using base R and works for lists with unnamed items. I would still recommend exists("bb", where = foo) as proposed in another answer for readability, but has_name is an alternative if you have unnamed items.

Jonas Lindeløv
  • 5,442
  • 6
  • 31
  • 54
0

Use purrr::has_element to check against the value of a list 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
Dmitry Zotikov
  • 2,133
  • 15
  • 12