Jak sprawdzić, czy element listy istnieje?


113

Problem

Chciałbym sprawdzić, czy istnieje element listy, oto przykład

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

W tym przykładzie wiem, że foo$aistnieje, ale test powraca FALSE.

Zajrzałem ?existsi znalazłem, że with(foo, exists('a')powraca TRUE, ale nie rozumiem, dlaczego exists('foo$a')wraca FALSE.

pytania

  • Dlaczego exists('foo$a')wraca FALSE?
  • Czy jest stosowane with(...)preferowane podejście?

1
może !is.null(foo$a)(lub !is.null(foo[["a"]])po bezpiecznej stronie)? (lub exists("a",where=foo))
Ben Bolker

1
@BenBolker thanks - byłoby dobrą odpowiedzią; dlaczego preferowana jest ta druga opcja?
David LeBauer

3
@David częściowe dopasowanie ... wypróbuj powyższe zfoo <- list(a1=1)
baptiste

Odpowiedzi:


151

W rzeczywistości jest to trochę trudniejsze niż myślisz. Ponieważ lista może faktycznie (przy pewnym wysiłku) zawierać elementy NULL, sprawdzenie może nie wystarczyć is.null(foo$a). Bardziej rygorystycznym testem może być sprawdzenie, czy nazwa jest rzeczywiście zdefiniowana na liście:

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

... i foo[["a"]]jest bezpieczniejszy niż foo$a, ponieważ ten ostatni używa dopasowania częściowego, a zatem może również dopasować dłuższą nazwę:

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

[AKTUALIZACJA] Wracając do pytania, dlaczego exists('foo$a')nie działa. PlikexistsJedyna funkcja sprawdza czy zmienna istnieje w środowisku, jeżeli nie częściami exist obiektu. Łańcuch "foo$a"jest interpretowany literacko: czy istnieje zmienna o nazwie „foo $ a”? ... a odpowiedź brzmi 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
nadal nie jest jasne - czy jest powód, dla którego exists('foo$a') == FALSE?
David LeBauer

Sugeruje to, że generalnie nie ma na to dobrego rozwiązania w R! Można chcieć bardziej złożonych rzeczy (takich jak testowanie, jeśli $mylist[[12]]$out$mcerrorjest zdefiniowane), które obecnie byłyby bardzo skomplikowane.
TMS

Byłeś świadomy whereargument existswskazano w @ Jima odpowiedź ?
David LeBauer,

"bar$a" <- 42Naprawdę chciałbym, żeby to była nieprawidłowa składnia i istnienie ("foo $ a") działało w naiwnym sensie.
Andy V,

44

Najlepszym sposobem sprawdzenia nazwanych elementów jest użycie exist(), jednak powyższe odpowiedzi nie używają tej funkcji poprawnie. Trzeba użyć whereargumentu aby sprawdzić zmiennej wewnątrz listy.

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

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

8
Używanie exists()na liście działa, ale uważam, że R wewnętrznie wymusza użycie go do środowiska przed sprawdzeniem obiektu o tej nazwie, co jest nieefektywne i może powodować błędy, jeśli są jakieś nienazwane elementy. Na przykład, jeśli uruchomić exists('a', list(a=1, 2)), to daje błąd: Error in list2env(list(a = 1, 2), NULL, <environment>) : attempt to use zero-length variable name. Konwersja odbywa się tutaj: github.com/wch/r-source/blob/ ...
wch

5

Oto porównanie wydajności proponowanych metod w innych odpowiedziach.

> 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

Jeśli planujesz używać listy jako szybkiego słownika, do którego uzyskuje się wiele razy, to is.nullpodejście może być jedyną realną opcją. Zakładam, że to jest O (1), podczas gdy %in%podejście to O (n)?


4

Lekko zmodyfikowana wersja @ salient.salamander, jeśli ktoś chce sprawdzić pełną ścieżkę, może być użyty.

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

Jednym z rozwiązań, które jeszcze się nie pojawiło, jest użycie długości, która z powodzeniem obsługuje NULL. O ile wiem, wszystkie wartości z wyjątkiem NULL mają długość większą niż 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

W ten sposób moglibyśmy stworzyć prostą funkcję, która działa zarówno z nazwanymi, jak i numerowanymi indeksami:

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

Jeśli element nie istnieje, powoduje to stan poza zakresem przechwycony przez blok tryCatch.


3

rlang::has_name() też może to zrobić:

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

Jak widać, z natury obsługuje on wszystkie przypadki, które @Tommy pokazał, jak obsługiwać przy użyciu podstawowego R i działa dla list z nienazwanymi elementami. Nadal polecałbym exists("bb", where = foo)zgodnie z propozycją w innej odpowiedzi na czytelność, ale has_namejest to alternatywa, jeśli masz nienazwane elementy.


0

Służy purrr::has_elementdo sprawdzania wartości elementu listy:

> 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

Czy to działa, jeśli element jest zagnieżdżony / na dowolnym poziomie zagnieżdżenia? Sprawdziłem dokumenty i nie było to jasne
David LeBauer

@DavidLeBauer, no. W takim przypadku rapplyany(rapply(x, function(v) identical(v, c(3, 4)), how = 'unlist'))
użyłbym
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.