Wie teste ich, ob ein Listenelement vorhanden ist?


113

Problem

Ich möchte testen, ob ein Element einer Liste vorhanden ist. Hier ist ein Beispiel

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 diesem Beispiel weiß ich, dass foo$aes das gibt, aber der Test kehrt zurück FALSE.

Ich habe nachgesehen ?existsund festgestellt, dass es with(foo, exists('a')zurückkehrt TRUE, verstehe aber nicht, warum es exists('foo$a')zurückkehrt FALSE.

Fragen

  • Warum kehrt exists('foo$a')zurück FALSE?
  • Wird with(...)der bevorzugte Ansatz verwendet?

1
vielleicht !is.null(foo$a)(oder !is.null(foo[["a"]])um auf der sicheren Seite zu sein)? (oder exists("a",where=foo))
Ben Bolker

1
@ BenBolker danke - würde eine gute Antwort geben; Warum wird die letztere Option bevorzugt?
David LeBauer

3
@ David partielle Übereinstimmung ... versuchen Sie die oben mitfoo <- list(a1=1)
Baptiste

Antworten:


151

Das ist eigentlich etwas kniffliger als man denkt. Da eine Liste tatsächlich (mit etwas Aufwand) NULL-Elemente enthalten kann, reicht es möglicherweise nicht aus, sie zu überprüfen is.null(foo$a). Ein strengerer Test könnte darin bestehen, zu überprüfen, ob der Name tatsächlich in der Liste definiert ist:

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

... und foo[["a"]]ist sicherer alsfoo$a , da letztere eine teilweise Übereinstimmung verwendet und daher möglicherweise auch mit einem längeren Namen übereinstimmt:

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

[UPDATE] Also zurück zur Frage, warum exists('foo$a')nicht funktioniert. Die existsFunktion prüft nur, ob eine Variable in einer Umgebung vorhanden ist, nicht, ob Teile eines Objekts vorhanden sind. Die Zeichenfolge "foo$a"wird literarisch interpretiert: Gibt es eine Variable namens "foo $ a"? ... und die Antwort ist 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
es ist immer noch nicht klar - gibt es einen Grund warum exists('foo$a') == FALSE?
David LeBauer

Dies deutet darauf hin, dass es in R im Allgemeinen keine gute Lösung dafür gibt! Man könnte komplexere Dinge wollen (wie das Testen, wenn $mylist[[12]]$out$mcerrordefiniert ist), die derzeit höllisch kompliziert wären.
TMS

Wussten Sie von dem whereArgument, existsauf das Sie in der Antwort von @ Jim hingewiesen haben ?
David LeBauer

"bar$a" <- 42Ich wünschte wirklich, dies wäre eine ungültige Syntax und existiert ("foo $ a") im naiven Sinne.
Andy V

44

Der beste Weg, um nach benannten Elementen zu suchen exist(), ist die Verwendung . Die obigen Antworten verwenden die Funktion jedoch nicht richtig. Sie müssen das whereArgument verwenden, um nach der Variablen in der Liste zu suchen.

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

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

8
Die Verwendung exists()einer Liste funktioniert, aber ich glaube, dass R sie intern in eine Umgebung zwingt, bevor nach einem Objekt dieses Namens gesucht wird. Dies ist ineffizient und kann zu Fehlern führen, wenn unbenannte Elemente vorhanden sind. Wenn Sie beispielsweise ausführen exists('a', list(a=1, 2)), wird ein Fehler ausgegeben : Error in list2env(list(a = 1, 2), NULL, <environment>) : attempt to use zero-length variable name. Die Konvertierung erfolgt hier: github.com/wch/r-source/blob/…
wch

5

Hier ist ein Leistungsvergleich der vorgeschlagenen Methoden in anderen Antworten.

> 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

Wenn Sie vorhaben, die Liste als schnelles Wörterbuch zu verwenden, auf das häufig zugegriffen wird, ist der is.nullAnsatz möglicherweise die einzig praktikable Option. Ich nehme an, es ist O (1), während der %in%Ansatz O (n) ist.


4

Eine leicht modifizierte Version von @ salient.salamander, wenn man den vollständigen Pfad überprüfen möchte, kann dies verwendet werden.

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

Eine Lösung, die noch nicht verfügbar ist, ist die Verwendung von length, die NULL erfolgreich verarbeitet. Soweit ich das beurteilen kann, haben alle Werte außer NULL eine Länge größer als 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

So könnten wir eine einfache Funktion erstellen, die sowohl mit benannten als auch mit nummerierten Indizes funktioniert:

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

Wenn das Element nicht vorhanden ist, verursacht es eine Bedingung außerhalb der Grenzen, die vom tryCatch-Block abgefangen wird.


3

rlang::has_name() kann das auch:

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

Wie Sie sehen können, werden von Natur aus alle Fälle behandelt, in denen @Tommy gezeigt hat, wie mit Base R umgegangen wird, und es werden Listen mit unbenannten Elementen verwendet. Ich würde weiterhin empfehlen, exists("bb", where = foo)wie in einer anderen Antwort vorgeschlagen, um die Lesbarkeit zu verbessern, aber es has_nameist eine Alternative, wenn Sie unbenannte Elemente haben.


0

Verwenden Sie purrr::has_elementdiese Option , um den Wert eines Listenelements zu überprüfen :

> 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

Funktioniert es, wenn das Element verschachtelt ist / sich auf einer beliebigen Verschachtelungsebene befindet? Ich habe die Dokumente überprüft und es war nicht klar
David LeBauer

@ DavidLeBauer, nein. In diesem Fall würde ich rapply(so etwas wie any(rapply(x, function(v) identical(v, c(3, 4)), how = 'unlist')))
Dmitry Zotikov
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.