リスト要素が存在するかどうかをテストする方法は?


113

問題

リストの要素が存在するかどうかをテストしたいのですが、ここに例があります

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

この例では、それfoo$aが存在することはわかっていますが、テストはを返しますFALSE

調べた?existsところ、その結果with(foo, exists('a')が返ってくることがわかりましたがTRUE、なぜexists('foo$a')返ってくるのかわかりませんFALSE

ご質問

  • なぜexists('foo$a')戻るのFALSEですか?
  • with(...)推奨されるアプローチを使用していますか?

1
多分!is.null(foo$a)(または!is.null(foo[["a"]])安全のために)?(またはexists("a",where=foo)
ベンボルカー、2011年

1
@BenBolkerありがとう-良い答えになります。なぜ後者のオプションが好ましいのですか?
David LeBauer、

3
@デビッド部分一致...して上にしてみてくださいfoo <- list(a1=1)
バティスト

回答:


151

これは実際にはあなたが考えるよりも少しトリッキーです。リストには実際に(多少の努力を払って)NULL要素を含めることができるため、を確認するだけでは不十分な場合がありますis.null(foo$a)。より厳密なテストは、名前が実際にリストで定義されていることを確認することです。

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

... 後者は部分一致を使用しているため、より長い名前にも一致する可能性があるため、foo[["a"]]より安全ですfoo$a

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

[更新]では、なぜexists('foo$a')動作しないのかという質問に戻ります。このexists関数は、変数が環境に存在するかどうかを確認するだけで、オブジェクトの一部が存在するかどうかは確認しません。文字列"foo$a"は文字どおりに解釈されます。「foo $ a」という変数はありますか?...そして答えは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
それはまだ明確ではありません-理由はありexists('foo$a') == FALSEますか?
David LeBauer、2011年

これは、Rにはこれに対する一般的な良い解決策がないことを示唆しています $mylist[[12]]$out$mcerror現在は地獄のように複雑になる、より複雑なものが必要な場合があります(定義されている場合のテストなど)。
TMS

@ジムの答えで指摘されているwhere議論を知っexistsていましたか?
David LeBauer 2014

"bar$a" <- 42これが無効な構文であり、exists( "foo $ a")が単純な意味で機能することを本当に望みます。
アンディV

44

名前付き要素を確認する最良の方法はを使用することexist()ですが、上記の回答は関数を適切に使用していません。リストwhere変数を確認するには、引数を使用する必要があります。

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

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

8
exists()リストでの使用は機能しますが、Rはその名前のオブジェクトをチェックする前に内部的に環境に強制変換すると考えています。これは非効率であり、名前のない要素があるとエラーが発生する可能性があります。たとえば、を実行exists('a', list(a=1, 2))すると、エラーが発生しますError in list2env(list(a = 1, 2), NULL, <environment>) : attempt to use zero-length variable name。変換はここに起こる:github.com/wch/r-source/blob/...
WCH

5

ここでは、他の回答で提案された方法のパフォーマンス比較を示します。

> 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

何度もアクセスする高速辞書としてリストを使用する場合は、このis.nullアプローチが唯一の実行可能なオプションとなる可能性があります。私はそれがO(1)であると思いますが、%in%アプローチはO(n)ですか?


4

@ salient.salamanderのわずかに変更されたバージョン。フルパスで確認したい場合は、これを使用できます。

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

まだ登場していない解決策の1つは、NULLを正常に処理する長さを使用することです。私の知る限りでは、NULLを除くすべての値の長さが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

したがって、名前付きインデックスと番号付きインデックスの両方で機能する単純な関数を作成できます。

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

要素が存在しない場合は、tryCatchブロックによって範囲外の状態がキャッチされます。


3

rlang::has_name() これも行うことができます:

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

ご覧のとおり、@ TommyがベースRを使用して処理する方法を示し、名前のないアイテムを含むリストで機能するすべてのケースを本質的に処理します。exists("bb", where = foo)読みやすさのために別の回答で提案されているように私はまだお勧めしhas_nameますが、名前のないアイテムがある場合の代替手段です。


0

リスト要素のpurrr::has_elementをチェックするために使用します。

> 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

要素がネストされている場合/ネストの任意のレベルで機能しますか?私はドキュメントをチェックしましたが、明確ではありませんでした
David LeBauer

@DavidLeBauer、いいえ。その場合、私はrapply(のようなものany(rapply(x, function(v) identical(v, c(3, 4)), how = 'unlist')))を使用します
Dmitry Zotikov
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.