使用带逗号的as.formula函数

7
我希望能够从用户动态地获取条件,因此我创建了一个闪亮的应用程序,从输入字段中获取它们。问题在于,对于带有逗号的字符向量,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”并转义逗号,但是没有成功。

我不知道你想做什么。我认为你需要提供一些数据示例才能得到答案。编辑问题并添加一些样本数据。你考虑过使用 ifelse()normal R operators 吗?!除了代码转换之外,我不看到在这里使用 dplyr 的任何理由。它会减慢你的速度,在这种情况下还会产生不必要的问题。 - 5th
移除了闪亮标签,... - Tonio Liebrand
这是一个在使用Shiny时可能会发生的问题(将文本输入作为命令的一部分)。在我看来,这个标签是相关的。 - moodymudskipper
3个回答

7

你收集输入的方式不太实用。你的问题在于你试图解析这样的代码:

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()命令查看splice-unquoting的实际操作:
rlang::qq_show(mutate(result = case_when(!!!fs)))
#> mutate(result = case_when(condition1 ~ 1, condition2 ~2, condition3 ~ 3))

4
借鉴@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),然后才能实时评估它(这样它就在正确的环境中被评估,而不需要进一步的技巧)。

谢谢!这是一个我没有想到的有用的不同视角。 - InterruptedException

1
你需要将每个条件放在列表中,并使用quosures和quasiquotation(!!!)使其正常工作。我将使用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

1
据我理解,OP必须从一个字符串开始作为他的shiny应用程序中的用户输入字段。 - moodymudskipper

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接