cucumber flesh

Rを中心としたデータ分析・統計解析らへんの話題をしていくだけ

否定条件: stringr 1.4.0で追加された引数negageとpurrrを使った文字列以外のさまざまなデータへの適用例

要約

f:id:u_ribo:20190224083558p:plain

  • 条件に一致しない要素に対してTRUEを返す否定条件をさまざまなデータ型に適用する例を紹介します
    • 文字列データに対しては stringrパッケージ(バージョン1.4.0以降)またはstringiのnegate引数を利用する
    • purrrパッケージのnegate()compose()を用いることで、文字列以外のデータにも適用可能な否定処理を実行できる
  • これらの関数を使った処理では、!演算子を使わずに記述できるためパイプ処理とも親和性が高い

はじめに

ある条件のもとで一致しない、つまり偽 (FALSE)となる要素を 真 (TRUE) とするためにRでは!演算子を使います。

# 1は2と等しくない
1 != 2
## [1] TRUE

また、grep(invert = )のように否定条件の指定が引数で与えられている関数もあります。

# 文字列dを含まない要素の位置を参照する
grep("d", c("abc", "def", "ghi"), invert = TRUE)
## [1] 1 3

この記事ではこのような!演算子を使わない否定条件の処理をさまざまなデータに対して実行する例を紹介します。まずは先日(2月10日)、バージョン1.4.0に更新されたRでの文字列処理の定番であるstringrパッケージによる文字列データへの適用例をみます。続いて、文字列以外の多様なデータ形式に否定処理を行うためにpurrrパッケージの関数を使った処理を示します。

stringr 1.4.0に実装されたnegate引数

stringr 1.4.0では、str_detect()などの主要な関数に引数negateが追加されました。この引数は与えられたパターンに一致しない要素を検出する機能をもちます。引数には論理値を指定し、TRUEを与えた際に否定条件を成立させます。

3つの要素を含んだ文字列ベクトルについて、“a”を含まない“melon”が TRUE となる処理を行います。まずは否定ではない、つまり“a”を含む要素にTRUEを返す処理を確認します。

library(stringr)

fruits <- c("banana", "apple", "melon")

str_detect(fruits, "a")
## [1]  TRUE  TRUE FALSE

目的はこの逆です。引数negateにTRUEを与えて結果をみましょう。

str_detect(fruits, "a", negate = TRUE)
## [1] FALSE FALSE  TRUE

否定条件に一致する要素である“melon”に対してTRUEを得ることができました。

これまでの否定条件は、!演算子を処理の先頭に置くことで指定していました。しかしこの形式だと次のようにパイプ処理と相性がいまいちなようにに思えます。

!str_detect(fruits, "a")
# パイプ処理で否定処理を記述する例
# 1. {}で無名関数化する
fruits %>% {
  !str_detect(., "a")
}
# 2. purr::map_lgl()を使う
fruits %>% 
  purrr::map_lgl(~ !str_detect(.x, "a"))

negate引数が実装されたことで適用する関数の中で処理が完結するようになります。negate引数はもともとstringiパッケージにあったものだそうです(初めて知りました)。stringrパッケージに実装してくれた id:yutannihilation さんに感謝です。

# stringiにもnegate引数は存在する
stringi::stri_detect(fruits, regex = "a", negate = TRUE)

purrrパッケージで否定条件を成立させる

さて、文字列の場合にはstringrでnegate引数を利用すれば良いという話でした。それでは、文字列以外でnegateのような否定の処理を行うにはどうすれば良いでしょうか。シンプルな答えは!演算子を使うことです。ですがここではpurrrパッケージに用意されたnegate()compose()による否定処理を紹介します。

library(purrr, warn.conflicts = FALSE)

まずはnegate()です。この関数は、関数を生成する特殊な関数です。具体的には記述した条件を否定する関数を生成します。引数.pに成立させたくない条件を記述します。例えばNULLでない要素を与えた時にTRUEを返してほしい際は下記のようにします。

fn <- 
  negate(is.null)

fn
## function (x) 
## {
##     !.Primitive("is.null")(x)
## }

!is.null(x)として機能する関数ができました。引数に値を渡して実行結果を確認してみましょう。

# is.null(x) が成立せず FALSE となります
fn(x = NULL)
## [1] FALSE
list(NULL, 1, "a") %>% 
  purrr::map_lgl(~ fn(x = .x))
## [1] FALSE  TRUE  TRUE

便利ですね。では次はnetage()を使って数値が素数でないかを判定したいと思います。primesパッケージに素数かどうかの判定をするis_prime()があるのでそれを用います。

# 1から6までの実数を用意します
nums <- 
  seq(1L, 6L)

primesには素数である場合にTRUEとなる関数が用意されています。その否定処理は次のように!で実現できますがnegate()で同じ処理ができます。

primes::is_prime(nums)
## [1] FALSE  TRUE  TRUE FALSE  TRUE FALSE
nums %>% 
  negate(~ primes::is_prime(.x))()
## [1]  TRUE FALSE FALSE  TRUE FALSE  TRUE

ここで注意するのがnegate(.p = )()のように括弧が後ろに付いている点です。これはパイプ処理をしているせいもありますが、この書き方は見慣れません (パイプ処理を使わない場合は negate(~ primes::is_prime(.x))(nums) とします)。この書き方が嫌な場合は文字列の処理で示したようにmap_lgl()で記述することも可能です。

nums %>% 
  map_lgl(
  negate(~ primes::is_prime(.x)))

map()で各要素に対して処理を適用するようにしているのでやや冗長に感じます。それではpurrrでの否定処理の2番目の例としてcompose()を紹介しましょう。compose()(g•f)(x) = g(f(x))として機能する合成関数を生成する関数です。この合成関数の機能を使うことで、関数を作る必要が生じますが、コードを見通しの良いものにできます。

is_false_prime <- 
  # 作成する関数 (unmatch_str_a()の第一引数に与えた値が str_detect)
  compose(`!`, ~ primes::is_prime(x = .x))
# negate()を使う場合は compose(negate(~ primes::is_prime(.x)))
is_false_prime(nums)
## [1]  TRUE FALSE FALSE  TRUE FALSE  TRUE

論理値ではなく、マッチしなかった値そのものがほしい場合はkeep()で要素を取り出せます。stringrで文字列データを扱う場合はstr_subset()です。

nums %>% 
  keep(negate(~ primes::is_prime(.x)))
## [1] 1 4 6
nums %>% 
  keep(is_false_prime)
fruits %>% 
  str_subset("a", negate = TRUE)
## [1] "melon"

それでは!