読者です 読者をやめる 読者になる 読者になる

ushi-goroshiの雑記帳

統計や機械学習の話題を中心に、思うがままに

dplyr::filterの注意点

最近すっかりHadley信者になってしまいデータ加工にもdplyrをよく使っているのだけれど、filterで少し躓いてしまったのでメモ。

まずは{dplyr}と{dtplyr}を読み込む:

library(dplyr)
library(dtplyr)

やりたかった処理とは以下のようなもので、irisを例とするとSpeciesごとにfilter内での条件を変えたかった。

> iris %>% 
+    filter(Species == "setosa", Sepal.Length >= 3 | 
+              Species == "versicolor", Sepal.Length >= 1 | 
+              Species == "virginica", Sepal.Length >= 2) %>% tbl_dt()

Source: local data table [50 x 5]

# tbl_dt [50 × 5]
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
          <dbl>       <dbl>        <dbl>       <dbl>  <fctr>
1           5.1         3.5          1.4         0.2  setosa
2           4.9         3.0          1.4         0.2  setosa
3           4.7         3.2          1.3         0.2  setosa
4           4.6         3.1          1.5         0.2  setosa
5           5.0         3.6          1.4         0.2  setosa
6           5.4         3.9          1.7         0.4  setosa
7           4.6         3.4          1.4         0.3  setosa
8           5.0         3.4          1.5         0.2  setosa
9           4.4         2.9          1.4         0.2  setosa
10          4.9         3.1          1.5         0.1  setosa
# ... with 40 more rows

しかしこのOutputにはsetosaしか残っておらず、どうやらOR条件("|")以下が評価されていないようなのである。。

{dplyr}のfilterでは"&"もAND条件として使用可能なので、そちらで試してみるとうまく行く。

> iris %>% 
+    filter(Species == "setosa" & Sepal.Length >= 3 | 
+              Species == "versicolor" & Sepal.Length >= 1 | 
+              Species == "virginica" & Sepal.Length >= 2) %>% 
tbl_dt()
Source: local data table [150 x 5]

# tbl_dt [150 × 5]
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
          <dbl>       <dbl>        <dbl>       <dbl>  <fctr>
1           5.1         3.5          1.4         0.2  setosa
2           4.9         3.0          1.4         0.2  setosa
3           4.7         3.2          1.3         0.2  setosa
4           4.6         3.1          1.5         0.2  setosa
5           5.0         3.6          1.4         0.2  setosa
6           5.4         3.9          1.7         0.4  setosa
7           4.6         3.4          1.4         0.3  setosa
8           5.0         3.4          1.5         0.2  setosa
9           4.4         2.9          1.4         0.2  setosa
10          4.9         3.1          1.5         0.1  setosa
# ... with 140 more rows

数値などは一切変えていないが、150行抽出されている。
しかし以下の2つは同じ結果を返すので、","と"&"だけの問題ではなさそう。

# ","でつなぐ
> iris %>% 
+    filter(Species == "setosa" , Sepal.Length >= 5) %>% tbl_dt()
Source: local data table [30 x 5]

# tbl_dt [30 × 5]
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
          <dbl>       <dbl>        <dbl>       <dbl>  <fctr>
1           5.1         3.5          1.4         0.2  setosa
2           5.0         3.6          1.4         0.2  setosa
3           5.4         3.9          1.7         0.4  setosa
4           5.0         3.4          1.5         0.2  setosa
5           5.4         3.7          1.5         0.2  setosa
6           5.8         4.0          1.2         0.2  setosa
7           5.7         4.4          1.5         0.4  setosa
8           5.4         3.9          1.3         0.4  setosa
9           5.1         3.5          1.4         0.3  setosa
10          5.7         3.8          1.7         0.3  setosa
# ... with 20 more rows
# "&"でつなぐ
> iris %>% 
+    filter(Species == "setosa" & Sepal.Length >= 5) %>% tbl_dt()
Source: local data table [30 x 5]

# tbl_dt [30 × 5]
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
          <dbl>       <dbl>        <dbl>       <dbl>  <fctr>
1           5.1         3.5          1.4         0.2  setosa
2           5.0         3.6          1.4         0.2  setosa
3           5.4         3.9          1.7         0.4  setosa
4           5.0         3.4          1.5         0.2  setosa
5           5.4         3.7          1.5         0.2  setosa
6           5.8         4.0          1.2         0.2  setosa
7           5.7         4.4          1.5         0.4  setosa
8           5.4         3.9          1.3         0.4  setosa
9           5.1         3.5          1.4         0.3  setosa
10          5.7         3.8          1.7         0.3  setosa
# ... with 20 more rows

"|"も組み合わせて使うと異なる結果が返るのだろうか。

> iris %>% 
+    filter(Species == "setosa" , Sepal.Length >= 5 | Species == "virginica") %>%
+    tbl_dt()

Source: local data table [30 x 5]

# tbl_dt [30 × 5]
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
          <dbl>       <dbl>        <dbl>       <dbl>  <fctr>
1           5.1         3.5          1.4         0.2  setosa
2           5.0         3.6          1.4         0.2  setosa
3           5.4         3.9          1.7         0.4  setosa
4           5.0         3.4          1.5         0.2  setosa
5           5.4         3.7          1.5         0.2  setosa
6           5.8         4.0          1.2         0.2  setosa
7           5.7         4.4          1.5         0.4  setosa
8           5.4         3.9          1.3         0.4  setosa
9           5.1         3.5          1.4         0.3  setosa
10          5.7         3.8          1.7         0.3  setosa
# ... with 20 more rows
> iris %>% 
+    filter(Species == "setosa" & Sepal.Length >= 5 | Species == "virginica") %>%
+    tbl_dt()
Source: local data table [80 x 5]

# tbl_dt [80 × 5]
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
          <dbl>       <dbl>        <dbl>       <dbl>  <fctr>
1           5.1         3.5          1.4         0.2  setosa
2           5.0         3.6          1.4         0.2  setosa
3           5.4         3.9          1.7         0.4  setosa
4           5.0         3.4          1.5         0.2  setosa
5           5.4         3.7          1.5         0.2  setosa
6           5.8         4.0          1.2         0.2  setosa
7           5.7         4.4          1.5         0.4  setosa
8           5.4         3.9          1.3         0.4  setosa
9           5.1         3.5          1.4         0.3  setosa
10          5.7         3.8          1.7         0.3  setosa
# ... with 70 more rows

ちなみにfilter内ではAND/OR条件の優先順位を付けるために括弧を使うことができるが、以下のように","を括弧で括る書き方はエラーとなる:

# ","を括弧でくくるとエラーになる
> iris %>% 
+    filter((Species == "setosa" , Sepal.Length >= 5)) %>%
Error: unexpected ',' in:
"iris %>% 
   filter((Species == "setosa" ,"
# "&"や"|"なら大丈夫
> iris %>% 
+    filter((Species == "setosa" & Sepal.Length >= 5)) %>%
+    tbl_dt()
Source: local data table [30 x 5]

# tbl_dt [30 × 5]
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
          <dbl>       <dbl>        <dbl>       <dbl>  <fctr>
1           5.1         3.5          1.4         0.2  setosa
2           5.0         3.6          1.4         0.2  setosa
3           5.4         3.9          1.7         0.4  setosa
4           5.0         3.4          1.5         0.2  setosa
5           5.4         3.7          1.5         0.2  setosa
6           5.8         4.0          1.2         0.2  setosa
7           5.7         4.4          1.5         0.4  setosa
8           5.4         3.9          1.3         0.4  setosa
9           5.1         3.5          1.4         0.3  setosa
10          5.7         3.8          1.7         0.3  setosa
# ... with 20 more rows
> 
> iris %>% 
+    filter((Species == "setosa" | Sepal.Length >= 5)) %>%
+    tbl_dt()
Source: local data table [148 x 5]

# tbl_dt [148 × 5]
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
          <dbl>       <dbl>        <dbl>       <dbl>  <fctr>
1           5.1         3.5          1.4         0.2  setosa
2           4.9         3.0          1.4         0.2  setosa
3           4.7         3.2          1.3         0.2  setosa
4           4.6         3.1          1.5         0.2  setosa
5           5.0         3.6          1.4         0.2  setosa
6           5.4         3.9          1.7         0.4  setosa
7           4.6         3.4          1.4         0.3  setosa
8           5.0         3.4          1.5         0.2  setosa
9           4.4         2.9          1.4         0.2  setosa
10          4.9         3.1          1.5         0.1  setosa
# ... with 138 more rows

原因が明確になったわけではないが、ひとまずANDの指定には"&"を使ったほうが良さそうだ。