Bagaimana cara menguji apakah elemen daftar ada?


113

Masalah

Saya ingin menguji apakah ada elemen daftar, berikut adalah contohnya

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

Dalam contoh ini, saya tahu itu foo$aada, tetapi tes kembali FALSE.

Saya melihat ke dalam ?existsdan telah menemukan with(foo, exists('a')pengembalian itu TRUE, tetapi tidak mengerti mengapa exists('foo$a')kembaliFALSE .

Pertanyaan

  • Mengapa exists('foo$a')kembaliFALSE ?
  • Apakah penggunaan with(...)pendekatan yang disukai?

1
mungkin !is.null(foo$a)(atau !is.null(foo[["a"]])berada di sisi yang aman)? (atau exists("a",where=foo))
Ben Bolker

1
@BenBolker terima kasih - akan membuat jawaban yang bagus; mengapa opsi terakhir lebih disukai?
David LeBauer

3
@David pencocokan parsial ... coba yang di atas denganfoo <- list(a1=1)
baptiste

Jawaban:


151

Ini sebenarnya sedikit lebih rumit dari yang Anda kira. Karena daftar sebenarnya (dengan sedikit usaha) dapat berisi elemen NULL, mungkin tidak cukup untuk diperiksa is.null(foo$a). Tes yang lebih ketat mungkin untuk memeriksa bahwa nama tersebut benar-benar ditentukan dalam daftar:

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

... dan foo[["a"]]lebih aman daripada foo$a, karena yang terakhir menggunakan pencocokan sebagian sehingga mungkin juga cocok dengan nama yang lebih panjang:

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

[UPDATE] Jadi, kembali ke pertanyaan mengapa exists('foo$a')tidak berhasil. The existsFungsi hanya memeriksa apakah variabel ada di lingkungan, tidak jika bagian dari yang ada objek. String "foo$a"tersebut ditafsirkan secara literer: Apakah ada variabel yang disebut "foo $ a"? ... dan jawabannya adalah 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
masih belum jelas - apakah ada alasan mengapa exists('foo$a') == FALSE?
David LeBauer

Ini menunjukkan umumnya tidak ada solusi yang baik untuk ini di R! Seseorang mungkin menginginkan hal-hal yang lebih kompleks (seperti pengujian jika $mylist[[12]]$out$mcerrorditentukan) yang saat ini akan menjadi rumit sekali.
TMS

Apakah Anda mengetahui whereargumen yang existsditunjukkan dalam jawaban @ Jim ?
David LeBauer

"bar$a" <- 42Saya benar-benar berharap ini adalah sintaks yang tidak valid dan ada ("foo $ a") berfungsi dalam arti naif.
Andy V

44

Cara terbaik untuk memeriksa elemen bernama adalah dengan menggunakan exist(), namun jawaban di atas tidak menggunakan fungsi dengan benar. Anda perlu menggunakan whereargumen untuk memeriksa variabel dalam daftar.

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

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

8
Menggunakan exists()pada daftar memang berfungsi, tetapi saya percaya bahwa R secara internal memaksanya ke lingkungan sebelum memeriksa objek dengan nama itu, yang tidak efisien dan dapat mengakibatkan kesalahan jika ada elemen yang tidak disebutkan namanya. Sebagai contoh jika Anda menjalankan exists('a', list(a=1, 2)), itu akan memberikan kesalahan: Error in list2env(list(a = 1, 2), NULL, <environment>) : attempt to use zero-length variable name. Konversi terjadi di sini: github.com/wch/r-source/blob/…
wch

5

Berikut adalah perbandingan kinerja dari metode yang diusulkan dalam jawaban lain.

> 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

Jika Anda berencana untuk menggunakan daftar sebagai kamus cepat yang diakses berkali-kali, maka is.nullpendekatan tersebut mungkin satu-satunya pilihan yang layak. Saya berasumsi itu adalah O (1), sedangkan %in%pendekatannya adalah O (n)?


4

Sedikit modifikasi dari @ salient.salamander, jika ingin mengecek di jalur lengkap, ini bisa digunakan.

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

Salah satu solusi yang belum muncul adalah menggunakan panjang, yang berhasil menangani NULL. Sejauh yang saya tahu, semua nilai kecuali NULL memiliki panjang lebih besar dari 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

Jadi kita bisa membuat fungsi sederhana yang bekerja dengan indeks bernama dan nomor:

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

Jika elemen tidak ada, itu menyebabkan kondisi di luar batas ditangkap oleh blok tryCatch.


3

rlang::has_name() bisa melakukan ini juga:

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

Seperti yang Anda lihat, ini secara inheren menangani semua kasus yang @Tommy tunjukkan cara menangani menggunakan basis R dan berfungsi untuk daftar dengan item yang tidak disebutkan namanya. Saya masih akan merekomendasikan exists("bb", where = foo)seperti yang diusulkan dalam jawaban lain untuk keterbacaan, tetapi has_namemerupakan alternatif jika Anda memiliki item yang tidak disebutkan namanya.


0

Gunakan purrr::has_elementuntuk memeriksa nilai elemen daftar:

> 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

Apakah ini berfungsi jika elemen disarangkan / pada tingkat bertingkat apa pun? Saya memeriksa dokumen dan tidak jelas
David LeBauer

@DavidLeBauer, tidak. Dalam hal ini, saya akan menggunakan rapply(sesuatu seperti any(rapply(x, function(v) identical(v, c(3, 4)), how = 'unlist')))
Dmitry Zotikov
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.