to be continued...

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

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

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

R論理演算子の数とmutate()での条件分岐

初めましてのブログです。最近はダーリン・イン・ザ・フランキス見てます。

今回は、

Rにおける理論演算子(&,|)の数によるエラーtidyverse::mutate()を用いた条件分岐 をいくらか紹介するコーナー

昨日まで、私はRにおける論理演算子である& ,|は二つ並べて使うものだと思っていました。そこでこんなエラーと出会います。

まず、こんなデータセットがあります。

R version 3.5.3 (2019-03-11) -- "Great Truth"
Copyright (C) 2019 The R Foundation for Statistical Computing
Platform: x86_64-w64-mingw32/x64 (64-bit)

>> library(tidyverse)
> umr <- data.frame(
+   ID = as.factor(1:18),
+   method = as.factor(c("A","B","B", "A","A","A","A","A",
+                        "B","B","B","B","A", "B","B","B","B","B")),
+   critelia = c(1,0,0,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1)
+ ) %>% 
+   as.tibble %>% print()
# A tibble: 18 x 3
   ID    method critelia
   <fct> <fct>     <dbl>
 1 1     A             1
 2 2     B             0
 3 3     B             0
 4 4     A             1
 5 5     A             1
 6 6     A             1
 7 7     A             1
 8 8     A             1
 9 9     B             1
10 10    B             0
11 11    B             0
12 12    B             0
13 13    A             1
14 14    B             1
15 15    B             1
16 16    B             1
17 17    B             1
18 18    B             1

ID : 参加者ID

method : 試した方法(例えば、2種類のダイエット方法)

critelia : 成功したか(例えば、1は成功、0は失敗)

※架空のデータセット


このデータに対して、以下のような条件分岐を行いたい

method == A かつcritelia == 1なら新たな列に`1

または

method == Bかつcritelia == 0なら新たな列に1

それ以外は0 を入れる

新たな列をclear_orderと名付け、で私はこうした。

> umr %>%
+   dplyr::mutate(
+     clear_order = ifelse((method == "A" && critelia == 1) ||
+                            (method == "B" && critelia == 0), 1, 0)
+   ) %>%print()
# A tibble: 18 x 4
   ID    method critelia clear_order
   <fct> <fct>     <dbl>       <dbl>
 1 1     A             1           1
 2 2     B             0           1
 3 3     B             0           1
 4 4     A             1           1
 5 5     A             1           1
 6 6     A             1           1
 7 7     A             1           1
 8 8     A             1           1
 9 9     B             1           1
10 10    B             0           1
11 11    B             0           1
12 12    B             0           1
13 13    A             1           1
14 14    B             1           1
15 15    B             1           1
16 16    B             1           1
17 17    B             1           1
18 18    B             1           1

全部1やん...

どうやらこれは、理論演算子が関係しているようだ。

詳しくは、以下の記事を参照↓↓

Rの理論演算子&と&&、|と||の違い

要するに
理論演算子1つで要素ごとに演算を行い、2つだとベクトルの1つ目の要素のみを演算する

つまり、今回のエラーは理論演算子を1つにすれば解決する。

Let's go!!

> umr %>%
+   dplyr::mutate(
+     clear_order = ifelse((method == "A" & critelia == 1) |
+                            (method == "B" & critelia == 0), 1, 0)
+   ) %>% print()
# A tibble: 18 x 4
   ID    method critelia clear_order
   <fct> <fct>     <dbl>       <dbl>
 1 1     A             1           1
 2 2     B             0           1
 3 3     B             0           1
 4 4     A             1           1
 5 5     A             1           1
 6 6     A             1           1
 7 7     A             1           1
 8 8     A             1           1
 9 9     B             1           0
10 10    B             0           1
11 11    B             0           1
12 12    B             0           1
13 13    A             1           1
14 14    B             1           0
15 15    B             1           0
16 16    B             1           0
17 17    B             1           0
18 18    B             1           0

tidyverse::mutate()を使って要素を一つずつ評価したいときは&|の理論演算子は1つで行うべきなんですね。学びが深いなぁ...

mutate(new = if_else(条件A │ 条件B, 1, 0))

ちなみに、今回このエラーをとある秘密集団に投げかけたところ、別解を得られたので紹介。


別解1. case_when()を使ったパターン

> umr %>% 
+   dplyr::mutate(
+     clear_order = case_when(
+       critelia == 0 & method == "A" ~ 1,
+       critelia == 0 & method == "B" ~ 1,
+       TRUE ~ 0
+     )
+   ) %>% print()
# A tibble: 18 x 4
   ID    method critelia clear_order
   <fct> <fct>     <dbl>       <dbl>
 1 1     A             1           0
 2 2     B             0           1
 3 3     B             0           1
 4 4     A             1           0
 5 5     A             1           0
 6 6     A             1           0
 7 7     A             1           0
 8 8     A             1           0
 9 9     B             1           0
10 10    B             0           1
11 11    B             0           1
12 12    B             0           1
13 13    A             1           0
14 14    B             1           0
15 15    B             1           0
16 16    B             1           0
17 17    B             1           0
18 18    B             1           0

case_when()も複数の条件を指定することが出来きる

~の左辺が論理式(条件)

~の右辺に置き換える値

って感じ。今回のケースは比較的論理式のコードが短いから良いが、コードが長くなるようであれば、こっちのほうが可読性が高い気がする。

case_when()およびmutate()については以下を参照↓↓

列の変換 - mutate関数


別解2. めっちゃ短いパターン

> umr %>% mutate(clear_order = ((method == "A") == critelia) %>% 
+                as.integer()) %>% print()
# A tibble: 18 x 4
   ID    method critelia clear_order
   <fct> <fct>     <dbl>       <int>
 1 1     A             1           1
 2 2     B             0           1
 3 3     B             0           1
 4 4     A             1           1
 5 5     A             1           1
 6 6     A             1           1
 7 7     A             1           1
 8 8     A             1           1
 9 9     B             1           0
10 10    B             0           1
11 11    B             0           1
12 12    B             0           1
13 13    A             1           1
14 14    B             1           0
15 15    B             1           0
16 16    B             1           0
17 17    B             1           0
18 18    B             1           0

おいおい、瞬殺だよ...

これは短い。知らなかったけど、RはTRUEを1、FALSEを0の整数として扱うんですね。

  1. (method == "A")"A"ならTRUE(1), "B"ならFALSE(0)に変換
  2. criteliaは元から0,1データなので合致か否かでclear_order行にTRUE,FALSEを返す。
  3. as.integer()TRUE,FALSEを1,0に変換

as.integer()を使わないとTRUE,FALSEで返ってくるよ

ナイス、スマート


別解3. 自作関数を使ったパターン

> judge_order <- function(method, critelia) {
+   #空のベクトルを生成
+   res <- vector()
+   #要素ごとに評価するためにfor文
+   for (i in 1:length(method)) {
+     if (method[i] == "A" & critelia[i] == 1) {
+       res[i] <- 1
+     } else if (method[i] == "B" & critelia[i] == 0) {
+       res[i] <- 1
+     } else {
+       res[i] <- 0
+     }
+   }
+   return(res)
+ }
> umr %>% mutate(clear_order = judge_order(method, critelia)) %>% print()
# A tibble: 18 x 4
   ID    method critelia clear_order
   <fct> <fct>     <dbl>       <dbl>
 1 1     A             1           1
 2 2     B             0           1
 3 3     B             0           1
 4 4     A             1           1
 5 5     A             1           1
 6 6     A             1           1
 7 7     A             1           1
 8 8     A             1           1
 9 9     B             1           0
10 10    B             0           1
11 11    B             0           1
12 12    B             0           1
13 13    A             1           1
14 14    B             1           0
15 15    B             1           0
16 16    B             1           0
17 17    B             1           0
18 18    B             1           0

実は、最初は自作関数を使ってトライして失敗しました...

詳しくは分からないがtidyval案件で、難しいみたい

to be continued...