to be continued...

Rや心理統計学の備忘録的な

There are three kinds of lies: lies, damned lies, and statistics.

- 嘘には三種類ある。嘘、大嘘、そして統計だ -
Benjamn Disraeli(19世紀のイギリス首相)

ごん(R)、お前だったのか...ベクトルをリサイクルしてくれていたのは...

初めに

どうもです。最近はガルパンを見ています。

(カチューシャが頭から離れない)

(最終章第二話面白かったです)

生きていると、R上でのベクトルのリサイクルを感じたいですよね。

今日は、Rで起こるベクトルリサイクルの性質と留意すべきポイントについてのお話です

こんなミス知らないうちにやっていそう....

結論

  • Rは論理式でベクトル評価をするとき、両ベクトルの長さが等しくないと短い方のベクトルを長いベクトルの要素数と等しくなるまでリサイクルする(論理式だけじゃないかも...)
  • 上述のため、エラーは返されないが予期せぬアウトプットになっているかもしれないことを留意せよ

現象

> sessionInfo()
R version 3.6.0 (2019-04-26)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 18.04.2 LTS

> library(tidylog)
# library(tidyverse)でも大丈夫

data("diamonds")
# A tibble: 53,940 x 10
   carat cut       color clarity depth table price     x     y     z
   <dbl> <ord>     <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>
 1 0.23  Ideal     E     SI2      61.5    55   326  3.95  3.98  2.43
 2 0.21  Premium   E     SI1      59.8    61   326  3.89  3.84  2.31
 3 0.23  Good      E     VS1      56.9    65   327  4.05  4.07  2.31
 4 0.290 Premium   I     VS2      62.4    58   334  4.2   4.23  2.63
 5 0.31  Good      J     SI2      63.3    58   335  4.34  4.35  2.75
 6 0.24  Very Good J     VVS2     62.8    57   336  3.94  3.96  2.48
 7 0.24  Very Good I     VVS1     62.3    57   336  3.95  3.98  2.47
 8 0.26  Very Good H     SI1      61.9    55   337  4.07  4.11  2.53
 9 0.22  Fair      E     VS2      65.1    61   337  3.87  3.78  2.49
10 0.23  Very Good H     VS1      59.4    61   338  4     4.05  2.39
# … with 53,930 more rows

今回は53940行あるdiamondsデータを使います

みんなのR[第二版]の第12章「dplyrによる高速なグルーピング処理」を読んでいたときのこと。

filterについて、条件式を複数用いて抽出する際の書き方として論理和(積)を使う方法と%in%(in演算子)を使う方法が書かれています。

参考までに↓↓↓

(dplyr::filterの再確認)

(%in% operator in R)

2つをそれぞれ書いてみると

> diamonds %>% filter(cut %in% c("Ideal", "Good"))
filter: removed 27483 out of 53940 rows (51%)
# A tibble: 26,457 x 10
   carat cut   color clarity depth table price     x     y     z
   <dbl> <ord> <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>
 1  0.23 Ideal E     SI2      61.5    55   326  3.95  3.98  2.43
 2  0.23 Good  E     VS1      56.9    65   327  4.05  4.07  2.31
# … with 26,447 more rows

> diamonds %>% filter(cut == "Ideal" | cut == "Good")
filter: removed 27483 out of 53940 rows (51%)
# A tibble: 26,457 x 10
   carat cut   color clarity depth table price     x     y     z
   <dbl> <ord> <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>
 1  0.23 Ideal E     SI2      61.5    55   326  3.95  3.98  2.43
 2  0.23 Good  E     VS1      56.9    65   327  4.05  4.07  2.31
# … with 26,447 more rows

という感じになります。

どちらも残っている要素は26457となっています。

ここでin演算子について「え?いらなくね?」と考えた愚かな私。

試しに書いてみると

> diamonds %>% filter(cut == c("Ideal", "Good"))
filter: removed 40711 out of 53940 rows (75%)
# A tibble: 13,229 x 10
   carat cut   color clarity depth table price     x     y     z
   <dbl> <ord> <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>
 1  0.23 Ideal E     SI2      61.5    55   326  3.95  3.98  2.43
# … with 13,219 more rows

行けたわ...

あり?要素が13229になってる。大体半分くらい?

どういうことだってばよ?

説明

ある塾で聞いてみると、

「基本的に、2つのベクトルオブジェクト(A , B)があって、それをマッチさせるような処理( 例えば A + B とか A == B など)をする場合、かつその二つの長さが違う場合には、R言語は短い方のベクトルをリサイクルして長さを合わせようとします。」

という解を得られました。

記号の書き方と集合の問題ですね。

in演算子は、要素がベクトルに属するか否かを返すものです。

対して、filter(cut == c("Ideal", "Good"))という書き方は、

元のdaiamondsデータが53940行要素あるのに対して、c("Ideal", "Good")の2つの要素しかありません。

他の言語ではわかりませんが、

Rでは論理式を評価するときにどちらかのベクトルが短いと短い方をリサイクルして長さを合わせようとするのです。

つまり、"Ideal", "Good"を26970回リサイクルして53940要素に増やすのです。

感動....!!!

例えば、diamonds %>% filter(cut == "Ideal")と書いたとき、R上では

> diamonds %>% slice(1:10) %>% select(carat,cut,color) %>% 
+   mutate(cut_check = rep("Ideal",10),
+          answer = cut_check == cut)

# A tibble: 10 x 5
   carat cut       color cut_check answer
   <dbl> <ord>     <ord> <chr>     <lgl> 
 1 0.23  Ideal     E     Ideal     TRUE  
 2 0.21  Premium   E     Ideal     FALSE 
 3 0.23  Good      E     Ideal     FALSE 
 4 0.290 Premium   I     Ideal     FALSE 
 5 0.31  Good      J     Ideal     FALSE 
 6 0.24  Very Good J     Ideal     FALSE 
 7 0.24  Very Good I     Ideal     FALSE 
 8 0.26  Very Good H     Ideal     FALSE 
 9 0.22  Fair      E     Ideal     FALSE 
10 0.23  Very Good H     Ideal     FALSE 

こんな感じで、Idealという単一要素をデータ行分リサイクルしています。

ごん、お前だったのか...ベクトルをリサイクルしてくれていたのは...

そして、TRUEだけを抽出しています。

(cut_checkanswerを擬似的にたした)

ということはdiamonds %>% filter(cut == c("Ideal", "Good"))では、

> diamonds %>% slice(1:10) %>% select(carat,cut,color) %>% 
+   mutate(cut_check = rep(c("Ideal","Good"),5),
+          answer = cut_check == cut)
# A tibble: 10 x 5
   carat cut       color cut_check answer
   <dbl> <ord>     <ord> <chr>     <lgl> 
 1 0.23  Ideal     E     Ideal     TRUE  
 2 0.21  Premium   E     Good      FALSE 
 3 0.23  Good      E     Ideal     FALSE 
 4 0.290 Premium   I     Good      FALSE 
 5 0.31  Good      J     Ideal     FALSE 
 6 0.24  Very Good J     Good      FALSE 
 7 0.24  Very Good I     Ideal     FALSE 
 8 0.26  Very Good H     Good      FALSE 
 9 0.22  Fair      E     Ideal     FALSE 
10 0.23  Very Good H     Good      FALSE 

c("Ideal", "Good")を元データに足りるまで繰り返し続けるわけです(暁美ほむら?)

53940は3の倍数でもあるので

> diamonds %>% filter(cut == c("Ideal", "Good", "premium"))
filter: removed 45079 out of 53940 rows (84%)
# A tibble: 8,861 x 10
   carat cut   color clarity depth table price     x     y     z
   <dbl> <ord> <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>
 1  0.23 Ideal E     SI2      61.5  55     326  3.95  3.98  2.43
 2  0.31 Good  J     SI2      63.3  58     335  4.34  4.35  2.75
 3  0.3  Good  J     SI1      64    55     339  4.25  4.28  2.73
 4  0.31 Good  H     SI1      64    54     402  4.29  4.31  2.75
 5  0.33 Ideal I     SI2      61.8  55     403  4.49  4.51  2.78
 6  0.26 Good  D     VS1      58.4  63     403  4.19  4.24  2.46
 7  0.23 Ideal G     VS1      61.9  54     404  3.93  3.95  2.44
 8  0.35 Ideal I     VS1      60.9  57     552  4.54  4.59  2.78
 9  0.3  Ideal D     SI1      62.1  56     552  4.3   4.33  2.68
10  0.32 Ideal I     VVS1     62    55.3   553  4.39  4.42  2.73
# … with 8,851 more rows

いけます。更に要素数が減っています。

53940では割り切れない7にしてみると

> diamonds %>% filter(cut == c("Ideal", "Good", "premium","umr","kirie","ebina","Shilfinford"))
filter: removed 50186 out of 53940 rows (93%)
# A tibble: 3,754 x 10
   carat cut   color clarity depth table price     x     y     z
   <dbl> <ord> <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>
 1 0.23  Ideal E     SI2      61.5    55   326  3.95  3.98  2.43
 2 0.23  Good  E     VS1      64.1    59   402  3.83  3.85  2.46
 3 0.26  Good  D     VS1      58.4    63   403  4.19  4.24  2.46
 4 0.3   Ideal D     SI1      62.1    56   552  4.3   4.33  2.68
 5 0.75  Ideal G     SI1      62.2    55  2760  5.87  5.8   3.63
 6 0.8   Ideal F     SI2      59.9    59  2762  6.01  6.07  3.62
 7 0.580 Ideal F     VVS1     61.7    56  2772  5.33  5.37  3.3 
 8 0.71  Good  E     VS2      59.2    61  2772  5.8   5.88  3.46
 9 0.72  Ideal G     SI1      61.8    56  2776  5.72  5.75  3.55
10 0.71  Good  F     VS2      57.8    60  2777  5.87  5.9   3.4 
# … with 3,744 more rows
 警告メッセージ: 
1:  `==.default`(cut, c("Ideal", "Good", "premium", "umr", "kirie",: 
   長いオブジェクトの長さが短いオブジェクトの長さの倍数になっていません 
2:  is.na(e1) | is.na(e2): 
   長いオブジェクトの長さが短いオブジェクトの長さの倍数になっていません 

いや、いけるのかい!!!

でも、警告メッセージがしっかり出ていますね。

今回は、filter()で行いましたが、subset()でも当然起きます。

このように、今回はRの親切心について触れることができました。

間違った書き方でも評価はされてしますので、%in%を使うことをお忘れないように(自戒)

to be continued...