金魚亭日常

読書,ガジェット,競技プログラミング

`dplyr::case_when()` の中身を先にlistに入れておく

ドキュメントには

patterns <- list(
  TRUE ~ as.character(x),
  x %%  5 == 0 ~ "fizz",
  x %%  7 == 0 ~ "buzz",
  x %% 35 == 0 ~ "fizz buzz"
)
case_when(!!! patterns)

みたいに書いてあるので, list に入れればいいかと思ったけど,少し違った.

dfdf.cond という data.frame があったときに,df を 列 numdf.condfrom から to までに入るか,でグループ分けして,グループを 列group に入れる,ということを考える.

df <- data.frame(num = c(1:14))
df
#>    num
#> 1    1
#> 2    2
#> 3    3
#> 4    4
#> 5    5
#> 6    6
#> 7    7
#> 8    8
#> 9    9
#> 10  10
#> 11  11
#> 12  12
#> 13  13
#> 14  14

df.cond <- data.frame(from = c(1, 5, 10), to = c(4, 9, 14), val = c("A", "B", 
  "C"), stringsAsFactors = FALSE)
df.cond
#>   from to val
#> 1    1  4   A
#> 2    5  9   B
#> 3   10 14   C

まとめると,

  • list ではなく, exprs を使う
    • unevaluated expression であることが必要
  • 各条件については,
    • 条件全体は exprcase_when のときに評価
    • df.cond に関する部分は UQ で 先に評価しておく

つまり,

library(dplyr)
library(rlang)
patterns <- exprs()
for (i in 1:nrow(df.cond)) {
  patterns <- append(patterns, expr(.data[["num"]] >= UQ(df.cond$from[i]) & 
    .data[["num"]] <= UQ(df.cond$to[i]) ~ UQ(df.cond$val[i])))
}
patterns
#> [[1]]
#> .data[["num"]] >= 1 & .data[["num"]] <= 4 ~ "A"
#> 
#> [[2]]
#> .data[["num"]] >= 5 & .data[["num"]] <= 9 ~ "B"
#> 
#> [[3]]
#> .data[["num"]] >= 10 & .data[["num"]] <= 14 ~ "C"

というようになり,

df <- df %>% mutate(group = case_when(!!!patterns))

#>    num group
#> 1    1     A
#> 2    2     A
#> 3    3     A
#> 4    4     A
#> 5    5     B
#> 6    6     B
#> 7    7     B
#> 8    8     B
#> 9    9     B
#> 10  10     C
#> 11  11     C
#> 12  12     C
#> 13  13     C
#> 14  14     C

という感じになる.

ちなみに,reprex::reprex() では case_when(!(!(!patterns))) となり,

#> Error in mutate_impl(.data, dots): Evaluation error: invalid argument type.

というエラーになった.