如何在R中使用dplyr::mutate中的表达式

4
我想基于给定的字符向量添加新列。 例如,在下面的示例中,我想添加在expr中定义的d列。
library(magrittr)

data <- tibble::tibble(
  a = c(1, 2),
  b = c(3, 4)
)

expr <- "d = a + b"

just as below:

data %>%
  dplyr::mutate(d = a + b)

# # A tibble: 2 x 3
#       a     b     d
#   <dbl> <dbl> <dbl>
# 1     1     3     4
# 2     2     4     6

然而,在下面的代码中,虽然计算本身(即添加)是有效的,但新列的名称与我的预期不同。
data %>%
  dplyr::mutate(!!rlang::parse_expr(expr))

# # A tibble: 2 x 3
#       a     b `d = a + b`
#   <dbl> <dbl>       <dbl>
# 1     1     3           4
# 2     2     4           6

data %>%
  dplyr::mutate(!!rlang::parse_quo(expr, env = rlang::global_env()))

# # A tibble: 2 x 3
#       a     b `d = a + b`
#   <dbl> <dbl>       <dbl>
# 1     1     3           4
# 2     2     4           6

data %>%
  dplyr::mutate(rlang::eval_tidy(rlang::parse_expr(expr)))

# # A tibble: 2 x 3
#       a     b `rlang::eval_tidy(rlang::parse_expr(expr))`
#   <dbl> <dbl>                                       <dbl>
# 1     1     3                                           4
# 2     2     4                                           6

如何正确使用dplyr :: mutate中的表达式?

我的问题类似于这个问题,但在我的例子中,新变量(d)及其定义(a + b)以单个字符向量形式给出(expr)。

3个回答

3

首先让我们看一下dplyr::mutate使用什么样的表达式来创建命名变量:我们需要一个包含表达式的命名列表,以根据该表达式创建基于该列表元素名称的变量。

library(tidyverse)

data <- tibble::tibble(
  a = c(1, 2),
  b = c(3, 4)
)

expr <- "d = a + b"
# let's rewrite the string above as named list containing an expression.
expr2 <- list(d = expr(a + b))

# this works as expected:
data %>% 
  mutate(!!! expr2)

#> # A tibble: 2 x 3
#>       a     b     d
#>   <dbl> <dbl> <dbl>
#> 1     1     3     4
#> 2     2     4     6

现在我们只需要一个函数,将一个字符串转换为一个名为列表且包含等式右边表达式的内容。名称需要是等式左边的内容。我们可以通过正则表达式操作实现这一点。最后,我们需要将等式右侧的字符串转换为表达式。我们可以使用基础R的str2lang实现此目的。
create_expr_ls <- function(str_expr) {
  expr_nm <- str_extract(str_expr, "^\\w+")
  expr_code <- str_replace_all(str_expr, "(^\\w+\\s?=\\s?)(.*)", "\\2")
  set_names(list(str2lang(expr_code)), expr_nm)
}

expr3 <- create_expr_ls(expr)

data %>% 
  mutate(!!! expr3)

#> # A tibble: 2 x 3
#>       a     b     d
#>   <dbl> <dbl> <dbl>
#> 1     1     3     4
#> 2     2     4     6

该示例是使用 reprex package(版本为0.3.0),于2022-01-23创建的。


谢谢!我明白了mutate内部的工作原理。 - Koopa

1
为了获得变异列所需的名称,您仍然可以使用相同的语法,并将结果分配给具有首选名称的列。要获取此名称,您可以使用正则表达式查找等于符号(=)之前的内容,然后删除可能存在的任何前导或尾随空格。
expr <- "x = a * b"
col_name <- trimws(str_extract(expr,"[^=]+"))

data %>%
   dplyr::mutate(!!col_name := !!rlang::parse_expr(expr))
# A tibble: 2 × 3
      a     b     x
  <dbl> <dbl> <dbl>
1     1     3     3
2     2     4     8

data %>%
   dplyr::mutate(!!col_name := !!rlang::parse_quo(expr, env = rlang::global_env()))
# A tibble: 2 × 3
      a     b     x
  <dbl> <dbl> <dbl>
1     1     3     3
2     2     4     8
 
data %>%
   dplyr::mutate(!!col_name := rlang::eval_tidy(rlang::parse_expr(expr)))
# A tibble: 2 × 3
      a     b     x
  <dbl> <dbl> <dbl>
1     1     3     3
2     2     4     8

谢谢,但我无法提前知道新变量的名称。可能会出现 expr <- "x = a * b" 的情况。 - Koopa
我已经编辑了答案,这样它就可以自动提取新列的名称。 - ekolima
谢谢!提取新列名的方法简单而美妙! - Koopa

1

这些都可以使用。第二个与第一个类似,但不需要在搜索路径中包含rlang。如果expr中没有d=部分,则第三个和第四个也有效,此时使用默认名称。最后一个仅使用基本的R语言,也是最短的。

data %>% mutate(within(., !!parse_expr(expr)))

data %>% mutate(within(., !!parse(text = expr)))

data %>% mutate(data, !!parse_expr(sprintf("tibble(%s)", expr)))

data %>% { eval_tidy(parse_expr(sprintf("mutate(., %s)", expr))) }

within(data, eval(parse(text = expr)))  # base R

注意

假设以下前提:

library(dplyr)
library(rlang)

# input
data <- tibble(a = c(1, 2), b = c(3, 4))
expr <- "d = a + b"

谢谢!我没想到要使用within - Koopa

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