Comment tester si un élément de liste existe?

113

Problème

Je voudrais tester si un élément d'une liste existe, voici un exemple

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

Dans cet exemple, je sais que cela foo$aexiste, mais le test revient FALSE.

J'ai regardé ?existset j'ai trouvé que les with(foo, exists('a')retours TRUE, mais je ne comprends pas pourquoi les exists('foo$a')retours FALSE.

Des questions

  • Pourquoi exists('foo$a')revient-il FALSE?
  • L' with(...)approche est-elle privilégiée?
David LeBauer
la source
1
peut-être !is.null(foo$a)(ou !is.null(foo[["a"]])pour être du bon côté)? (ou exists("a",where=foo))
Ben Bolker
1
@BenBolker merci - ferait une bonne réponse; pourquoi cette dernière option est-elle préférée?
David LeBauer
3
@David correspondance partielle ... essayez ce qui précède avecfoo <- list(a1=1)
baptiste

Réponses:

151

C'est en fait un peu plus délicat que vous ne le pensez. Puisqu'une liste peut en fait (avec quelques efforts) contenir des éléments NULL, cela peut ne pas être suffisant pour vérifier is.null(foo$a). Un test plus strict pourrait consister à vérifier que le nom est bien défini dans la liste:

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

... et foo[["a"]]est plus sûr que foo$a, puisque ce dernier utilise une correspondance partielle et peut donc également correspondre à un nom plus long:

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

[MISE À JOUR] Donc, revenons à la question de savoir pourquoi exists('foo$a')ne fonctionne pas. La existsfonction vérifie uniquement si une variable existe dans un environnement, pas si des parties d'un objet existent. La chaîne "foo$a"est interprétée littéralement: y a-t-il une variable appelée "foo $ a"? ... et la réponse est 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
la source
2
ce n'est toujours pas clair - y a-t-il une raison pour laquelle exists('foo$a') == FALSE?
David LeBauer
Cela suggère qu'il n'y a généralement pas de bonne solution pour cela dans R! On pourrait vouloir des choses plus complexes (comme tester si $mylist[[12]]$out$mcerrorest défini) qui seraient actuellement compliquées comme l'enfer.
TMS du
Étiez-vous au courant de l' whereargument existssouligné dans la réponse de @ Jim ?
David LeBauer
"bar$a" <- 42Je souhaite vraiment que ce soit une syntaxe invalide et existe ("foo $ a") a fonctionné dans le sens naïf.
Andy V
44

La meilleure façon de vérifier les éléments nommés est de les utiliser exist(), mais les réponses ci-dessus n'utilisent pas correctement la fonction. Vous devez utiliser l' whereargument pour rechercher la variable dans la liste.

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

exists('a', where=foo) #TRUE
exists('b', where=foo) #TRUE
exists('c', where=foo) #FALSE
Jim
la source
8
L'utilisation exists()sur une liste fonctionne, mais je pense que R la contraint en interne à un environnement avant de rechercher un objet de ce nom, ce qui est inefficace et peut entraîner des erreurs s'il y a des éléments sans nom. Par exemple , si vous exécutez exists('a', list(a=1, 2)), il vous donnera une erreur: Error in list2env(list(a = 1, 2), NULL, <environment>) : attempt to use zero-length variable name. La conversion se produit ici: github.com/wch/r-source/blob/…
wch
5

Voici une comparaison des performances des méthodes proposées dans d'autres réponses.

> 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

Si vous prévoyez d'utiliser la liste comme un dictionnaire rapide auquel vous avez accédé plusieurs fois, l' is.nullapproche pourrait être la seule option viable. Je suppose que c'est O (1), tandis que l' %in%approche est O (n)?

Davor Josipovic
la source
4

Une version légèrement modifiée de @ salient.salamander, si l'on veut vérifier le chemin complet, cela peut être utilisé.

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
la source
3

Une solution qui n'a pas encore été trouvée consiste à utiliser length, qui gère avec succès NULL. Pour autant que je sache, toutes les valeurs sauf NULL ont une longueur supérieure à 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

Ainsi, nous pourrions créer une fonction simple qui fonctionne avec les index nommés et numérotés:

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

Si l'élément n'existe pas, il provoque une condition hors limites interceptée par le bloc tryCatch.

saillant.salamandre
la source
3

rlang::has_name() peut faire ça aussi:

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

Comme vous pouvez le voir, il gère intrinsèquement tous les cas que @Tommy a montré comment gérer en utilisant la base R et fonctionne pour les listes avec des éléments sans nom. Je recommanderais toujours exists("bb", where = foo)comme proposé dans une autre réponse pour la lisibilité, mais has_namec'est une alternative si vous avez des éléments sans nom.

Jonas Lindeløv
la source
0

Utilisez purrr::has_elementpour vérifier la valeur d'un élément de liste:

> 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
la source
Cela fonctionne-t-il si l'élément est imbriqué / à n'importe quel niveau d'imbrication? J'ai vérifié la documentation et ce n'était pas clair
David LeBauer
@DavidLeBauer, non. Dans ce cas, j'utiliserais rapply(quelque chose comme any(rapply(x, function(v) identical(v, c(3, 4)), how = 'unlist')))
Dmitry Zotikov