Come verificare se l'elemento della lista esiste?


113

Problema

Vorrei verificare se esiste un elemento di una lista, ecco un esempio

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 questo esempio, so che foo$aesiste, ma il test ritorna FALSE.

Ho guardato dentro ?existse ho scoperto che with(foo, exists('a')ritorna TRUE, ma non capisco perché exists('foo$a')ritorni FALSE.

Domande

  • Perché exists('foo$a')ritorna FALSE?
  • Viene utilizzato with(...)l'approccio preferito?

1
forse !is.null(foo$a)(o !is.null(foo[["a"]])per essere al sicuro)? (o exists("a",where=foo))
Ben Bolker

1
@BenBolker grazie - sarebbe una buona risposta; perché è preferibile quest'ultima opzione?
David LeBauer

3
@David abbinamento parziale ... prova quanto sopra confoo <- list(a1=1)
baptiste

Risposte:


151

Questo in realtà è un po 'più complicato di quanto pensi. Poiché una lista può effettivamente (con qualche sforzo) contenere elementi NULL, potrebbe non essere sufficiente per controllare is.null(foo$a). Un test più rigoroso potrebbe essere quello di verificare che il nome sia effettivamente definito nell'elenco:

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

... ed foo[["a"]]è più sicuro di foo$a, poiché quest'ultimo utilizza la corrispondenza parziale e quindi potrebbe anche corrispondere a un nome più lungo:

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

[AGGIORNAMENTO] Quindi, torniamo alla domanda perché exists('foo$a')non funziona. La existsfunzione controlla solo se una variabile esiste in un ambiente, non se esistono parti di un oggetto. La stringa "foo$a"è interpretata letteraria: esiste una variabile chiamata "foo $ a"? ... e la risposta è 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
non è ancora chiaro - c'è una ragione per cui exists('foo$a') == FALSE?
David LeBauer

Ciò suggerisce che generalmente non esiste una buona soluzione per questo in R! Si potrebbero desiderare cose più complesse (come testare se $mylist[[12]]$out$mcerrorè definito) che attualmente sarebbero complicate da morire.
TMS

Eri a conoscenza wheredell'argomento da existssottolineare nella risposta di @ Jim ?
David LeBauer

"bar$a" <- 42Vorrei davvero che questa fosse una sintassi non valida e che esistesse ("foo $ a") funzionasse in senso ingenuo.
Andy V

44

Il modo migliore per verificare la presenza di elementi denominati è utilizzare exist(), tuttavia le risposte precedenti non utilizzano correttamente la funzione. È necessario utilizzare l' whereargomento per verificare la variabile all'interno dell'elenco.

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

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

8
L'utilizzo exists()su un elenco funziona, ma credo che R lo costringa internamente a un ambiente prima di verificare la presenza di un oggetto con quel nome, il che è inefficiente e può causare errori se sono presenti elementi senza nome. Per esempio se si esegue exists('a', list(a=1, 2)), darà un errore: Error in list2env(list(a = 1, 2), NULL, <environment>) : attempt to use zero-length variable name. La conversione avviene qui: github.com/wch/r-source/blob/…
wch

5

Ecco un confronto delle prestazioni dei metodi proposti in altre risposte.

> 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

Se hai intenzione di utilizzare l'elenco come un dizionario veloce a cui si accede molte volte, l' is.nullapproccio potrebbe essere l'unica opzione praticabile. Suppongo che sia O (1), mentre l' %in%approccio è O (n)?


4

Una versione leggermente modificata di @ salient.salamander, se si desidera controllare il percorso completo, può essere utilizzata.

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

Una soluzione che non è ancora stata trovata sta usando length, che gestisce con successo NULL. Per quanto ne so, tutti i valori tranne NULL hanno una lunghezza maggiore di 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

Quindi potremmo creare una semplice funzione che funzioni sia con indici denominati che numerati:

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

Se l'elemento non esiste, provoca una condizione di fuori limite rilevata dal blocco tryCatch.


3

rlang::has_name() può fare anche questo:

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

Come puoi vedere, gestisce intrinsecamente tutti i casi che @Tommy ha mostrato come gestire usando la base R e funziona per elenchi con elementi senza nome. Consiglierei comunque exists("bb", where = foo)come proposto in un'altra risposta per la leggibilità, ma has_nameè un'alternativa se hai elementi senza nome.


0

Utilizzare purrr::has_elementper confrontare il valore di un elemento dell'elenco:

> 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

Funziona se l'elemento è annidato / a qualsiasi livello di annidamento? Ho controllato i documenti e non era chiaro
David LeBauer

@DavidLeBauer, no. In tal caso, rapplyany(rapply(x, function(v) identical(v, c(3, 4)), how = 'unlist'))
userei
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.