Использование 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