Использование as.formula с запятой

Я хотел бы получить условия динамически от пользователя, поэтому я создал блестящее приложение, которое получает их из поля ввода. Проблема в том, что as.formula не работает для вектора символов с запятой (без него работает нормально).

Код:

all_conditions = 
  "condition1 ~ 0,
   condition2 ~ 1,
   condition3 ~ 2"

 my_dataset %>% group_by(id) %>%
  summarise(FLAG = case_when(
      as.formula(all_conditions) )
   )

Я получил:

Ошибка оценки::2:100: неожиданно ','

Я пытался использовать paste и избежать запятой безуспешно.

3 ответа

Решение

Способ сбора входных данных не очень практичен для работы. Ваша проблема в том, что вы пытаетесь разобрать код, который выглядит следующим образом:

var1, var2, var3

Попробуйте набрать это в консоли R, вы получите ту же ошибку:

#> Error: unexpected ',' in "var1,"

Поэтому прежде всего реорганизуйте ваш код, чтобы собрать входные данные в виде двух векторов:

cnds <- c("condition1", "condition2", "condition3")
vals <- c("1", "2", "3")

Теперь у вас есть два варианта преобразования этих строк в код R: анализ или создание символов. Первый используется, когда вы ожидаете произвольный код R, а второй - когда вы ожидаете имена переменных или столбцов. Можете ли вы определить различия?

rlang::parse_exprs(c("foo", "bar()", "100"))
#> [[1]]
#> foo
#>
#> [[2]]
#> bar()
#>
#> [[3]]
#> [1] 100

rlang::syms(c("foo", "bar()", "100"))
#> [[1]]
#> foo
#>
#> [[2]]
#> `bar()`
#>
#> [[3]]
#> `100`

В вашем случае вам, вероятно, понадобится разбор, потому что условия будут R-кодом. Итак, начнем с разбора обоих векторов:

cnds <- map(cnds, rlang::parse_expr)
vals <- map(vals, rlang::parse_expr)

Я картирую parse_expr() вместо использования множественного числа parse_exprs() потому что последний может вернуть список, который длиннее, чем его ввод. Например parse_exprs(c("foo; bar", "baz; bam")) превращает 2 строки в список из 4 выражений. parse_expr() возвращает ошибку, если строка содержит более одного выражения и поэтому является более надежной в нашем случае.

Теперь мы можем сопоставить эти два списка LHS и RHS и создать формулы. Одним простым способом является использование квазиквотации для создания формул, заключая в кавычки каждую LHS и соответствующую ей RHS:

fs <- map2(cnds, vals, function(c, v) rlang::expr(!!c ~ !!v))

Результатом является список выражений формул, которые можно вставить в case_when():

data %>% mutate(result = case_when(!!!fs))

использование rlang::qq_show() чтобы точно увидеть, что делает сплайс-кавычка:

rlang::qq_show(mutate(result = case_when(!!!fs)))
#> mutate(result = case_when(condition1 ~ 1, condition2 ~2, condition3 ~ 3))

Занимая пример @phiver, вы можете сделать:

conditions <- "gear == 3 ~ 0, gear == 4 ~ 1, TRUE ~ 2"
mtcars %>% group_by(vs) %>% 
  mutate(FLAG = eval(parse(text=sprintf("case_when(%s)",conditions))))
# # A tibble: 32 x 12
# # Groups:   vs [2]
#      mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb  FLAG
#    <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#  1  21.0     6 160.0   110  3.90 2.620 16.46     0     1     4     4     1
#  2  21.0     6 160.0   110  3.90 2.875 17.02     0     1     4     4     1
#  3  22.8     4 108.0    93  3.85 2.320 18.61     1     1     4     1     1
#  4  21.4     6 258.0   110  3.08 3.215 19.44     1     0     3     1     0
#  5  18.7     8 360.0   175  3.15 3.440 17.02     0     0     3     2     0
#  6  18.1     6 225.0   105  2.76 3.460 20.22     1     0     3     1     0
#  7  14.3     8 360.0   245  3.21 3.570 15.84     0     0     3     4     0
#  8  24.4     4 146.7    62  3.69 3.190 20.00     1     0     4     2     1
#  9  22.8     4 140.8    95  3.92 3.150 22.90     1     0     4     2     1
# 10  19.2     6 167.6   123  3.92 3.440 18.30     1     0     4     4     1

Идея в том, что вы не можете оценить вашу строку в одиночку, так как она сама по себе не является правильным синтаксисом, поэтому сначала нам нужно создать правильный вызов вокруг нее (здесь используется sprintf) и затем мы можем оценить его на лету (чтобы он оценивался в правильной среде без дополнительных хитростей).

Вы должны поместить каждое условие в список и использовать кавычки и кавычки (!!!), чтобы заставить его работать. Я буду использовать mtcars в качестве примера, следуя вашему примеру кода.

library(dplyr)
# create list of quosures
conditions <- list(quo(gear == 3 ~ 0), 
     quo(gear == 4 ~ 1),
     quo(TRUE ~ 2))


mtcars %>% group_by(vs) %>% 
  mutate(FLAG = case_when(!!! conditions)) # quasiquotation using !!! to splice the list
# A tibble: 32 x 12
# Groups:   vs [2]
     mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb  FLAG
   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
 1  21       6  160    110  3.9   2.62  16.5     0     1     4     4     1
 2  21       6  160    110  3.9   2.88  17.0     0     1     4     4     1
 3  22.8     4  108     93  3.85  2.32  18.6     1     1     4     1     1
 4  21.4     6  258    110  3.08  3.22  19.4     1     0     3     1     0
 5  18.7     8  360    175  3.15  3.44  17.0     0     0     3     2     0
 6  18.1     6  225    105  2.76  3.46  20.2     1     0     3     1     0
 7  14.3     8  360    245  3.21  3.57  15.8     0     0     3     4     0
 8  24.4     4  147.    62  3.69  3.19  20       1     0     4     2     1
 9  22.8     4  141.    95  3.92  3.15  22.9     1     0     4     2     1
10  19.2     6  168.   123  3.92  3.44  18.3     1     0     4     4     1
# ... with 22 more rows
Другие вопросы по тегам