Edited at

dplyr::filterの再確認

More than 1 year has passed since last update.


dplyr::filterの基礎

dplyr::filterはレコードをフィルタリングして必要なレコードを残すという関数です。dplyrパッケージに組み込まれていますので,使うためにはdplyrを読み込む必要があります:

library(dplyr)

#>
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#>
#> filter, lag
#> The following objects are masked from 'package:base':
#>
#> intersect, setdiff, setequal, union


使い方

filter(.data, ...)

.data

: 対象となるデータセット。

...

: .dataに含まれる変数で定義された論理式。


留意事項

関数のヘルプドキュメントに以下のような留意事項がありました:


  • データセットのrownamesは黙って削除される


    • もし残したかったらtibble::rownames_to_columnを実行しといてね



  • group_byしたデータセットの場合,うまく最適化されてない


    • filter処理する際はグループ化する前,もしくはungroupしてからね




実例

サンプルのデータセットを準備します:

d <- data.frame(x0 = 1:10,

x1 = 11:20,
s = sample(c("kosaki", "chitoge", "marika"), 10, replace = TRUE),
row.names = letters[1:10]
)
d
#> x0 x1 s
#> a 1 11 marika
#> b 2 12 kosaki
#> c 3 13 chitoge
#> d 4 14 chitoge
#> e 5 15 marika
#> f 6 16 kosaki
#> g 7 17 chitoge
#> h 8 18 marika
#> i 9 19 kosaki
#> j 10 20 chitoge


基本的な考え方

filterは,要するに...引数にTRUEもしくはFALSEの論理値ベクトルを与えればOKです。実際に直接指定することはないでしょうが,以下のコードでちゃんと動きます:

f <- c(TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, TRUE, TRUE, FALSE, FALSE)

filter(d, f)
#> x0 x1 s
#> 1 1 11 marika
#> 2 2 12 kosaki
#> 3 3 13 chitoge
#> 4 7 17 chitoge
#> 5 8 18 marika

ただし,この論理値ベクトルは行数と同一である必要があり,異なる場合エラーを返します。エラーが出ない特別な場合として,ベクトル長が1(要するにTRUEまたはFALSEがひとつだけ)がありますが,実質意味はないでしょう:

filter(d, TRUE)

#> x0 x1 s
#> a 1 11 marika
#> b 2 12 kosaki
#> c 3 13 chitoge
#> d 4 14 chitoge
#> e 5 15 marika
#> f 6 16 kosaki
#> g 7 17 chitoge
#> h 8 18 marika
#> i 9 19 kosaki
#> j 10 20 chitoge

フィルターが全くかからない,あるいはレコードが空っぽになる,というときはこういうケースに陥っていることもあるかと思います。


数値フィルタ

まずは単一条件についてです。

filter(d, x0==4)

#> x0 x1 s
#> 1 4 14 chitoge
filter(d, x0!=4)
#> x0 x1 s
#> 1 1 11 marika
#> 2 2 12 kosaki
#> 3 3 13 chitoge
#> 4 5 15 marika
#> 5 6 16 kosaki
#> 6 7 17 chitoge
#> 7 8 18 marika
#> 8 9 19 kosaki
#> 9 10 20 chitoge
filter(d, x0<4)
#> x0 x1 s
#> 1 1 11 marika
#> 2 2 12 kosaki
#> 3 3 13 chitoge
filter(d, x0>=4)
#> x0 x1 s
#> 1 4 14 chitoge
#> 2 5 15 marika
#> 3 6 16 kosaki
#> 4 7 17 chitoge
#> 5 8 18 marika
#> 6 9 19 kosaki
#> 7 10 20 chitoge

このあたりは出力と比較すればすぐにわかるかと思います。

次に複数条件についてです。

filter(d, x0<8 & x1>12)

#> x0 x1 s
#> 1 3 13 chitoge
#> 2 4 14 chitoge
#> 3 5 15 marika
#> 4 6 16 kosaki
#> 5 7 17 chitoge
filter(d, x0>8 | x1<15)
#> x0 x1 s
#> 1 1 11 marika
#> 2 2 12 kosaki
#> 3 3 13 chitoge
#> 4 4 14 chitoge
#> 5 9 19 kosaki
#> 6 10 20 chitoge
filter(d, xor(x0<8, x1>12))
#> x0 x1 s
#> 1 1 11 marika
#> 2 2 12 kosaki
#> 3 8 18 marika
#> 4 9 19 kosaki
#> 5 10 20 chitoge
filter(d, dplyr::between(x0, 3, 7))
#> x0 x1 s
#> 1 3 13 chitoge
#> 2 4 14 chitoge
#> 3 5 15 marika
#> 4 6 16 kosaki
#> 5 7 17 chitoge

xor(x, y)は「xかつy」の余事象となります。dplyr::betweenは,その名の通り区間を指定します。指定した範囲の値を含むものとなります。


文字列フィルタ

まず完全一致についてです。

filter(d, s=="kosaki")

#> x0 x1 s
#> 1 2 12 kosaki
#> 2 6 16 kosaki
#> 3 9 19 kosaki
filter(d, s!="chitoge")
#> x0 x1 s
#> 1 1 11 marika
#> 2 2 12 kosaki
#> 3 5 15 marika
#> 4 6 16 kosaki
#> 5 8 18 marika
#> 6 9 19 kosaki

このあたりは数値フィルタと同様です。

次に正規表現を利用した一致条件の指定です。この場合,stringr::str_ detect関数が便利です。

library(stringr)

filter(d, str_detect(s, "k"))
#> x0 x1 s
#> 1 1 11 marika
#> 2 2 12 kosaki
#> 3 5 15 marika
#> 4 6 16 kosaki
#> 5 8 18 marika
#> 6 9 19 kosaki
filter(d, str_detect(s, "^k"))
#> x0 x1 s
#> 1 2 12 kosaki
#> 2 6 16 kosaki
#> 3 9 19 kosaki

stringr::str_detect(string, pattern)stringで指定した文字列ベクトルから正規表現でpatternと一致するものをTRUE,それ以外をFALSEで返してきます。greplを使うよりも簡単でかつ高速に処理する(はず)なのでこちらをおすすめします。

複数の文字列に一致するor一致しないを指定する方法です。

filter(d, s %in% c("kosaki", "marika"))

#> x0 x1 s
#> 1 1 11 marika
#> 2 2 12 kosaki
#> 3 5 15 marika
#> 4 6 16 kosaki
#> 5 8 18 marika
#> 6 9 19 kosaki
filter(d, !(s %in% c("kosaki", "marika")))
#> x0 x1 s
#> 1 3 13 chitoge
#> 2 4 14 chitoge
#> 3 7 17 chitoge
#> 4 10 20 chitoge

%in%演算子は,左側の各要素が右側の要素のどれかにマッチするかを判定してTRUEもしくはFALSEの論理値ベクトルを返します。逆に右側の要素のどれにもマッチしないものを取り出したい場合,上記のようにします。ただ正直これはスマートなコードではないので,同じ処理をする %nin%を定義したり,あるいは実装したパッケージもあります。個人的にはbaseに組み込んでほしいところなのですが…。一応定義するとしたら,こういう感じでOKです:

'%nin%' <- function(x, y) !(x %in% y)

filter(d, s %nin% c("kosaki", "chitoge"))
#> x0 x1 s
#> 1 1 11 marika
#> 2 5 15 marika
#> 3 8 18 marika


NA処理

ちょcっとデータセットを変更します:

d_withNA <- d <- data.frame(x0 = 1:10,

x1 = 11:20,
s = sample(c("kosaki", "chitoge", NA), 10, replace = TRUE),
row.names = letters[1:10]
)
d_withNA
#> x0 x1 s
#> a 1 11 <NA>
#> b 2 12 kosaki
#> c 3 13 kosaki
#> d 4 14 <NA>
#> e 5 15 kosaki
#> f 6 16 chitoge
#> g 7 17 kosaki
#> h 8 18 chitoge
#> i 9 19 <NA>
#> j 10 20 <NA>

この場合,このような処理が可能です:

filter(d_withNA, is.na(s))

#> x0 x1 s
#> 1 1 11 <NA>
#> 2 4 14 <NA>
#> 3 9 19 <NA>
#> 4 10 20 <NA>
filter(d_withNA, !is.na(s))
#> x0 x1 s
#> 1 2 12 kosaki
#> 2 3 13 kosaki
#> 3 5 15 kosaki
#> 4 6 16 chitoge
#> 5 7 17 kosaki
#> 6 8 18 chitoge

is.naは「NAか否か」を判定してTRUEあるいはFALSEを返してきます。なのでこの場合sにNAがあるレコードを抽出してきます。そのため,!is.na(s)と否定することでsがNAではないレコードを抽出してくるようになります1

このあたりが使えれば,あとは条件を組み合わせることで望むようなレコード抽出ができるようになるでしょう。

Enjoy!





  1. 欠損値への対応としては,tidyrパッケージにも便利な関数が用意してあります。こちらにまとめてますので参照してください。