在 mutate 右侧的重新编码中使用基于 tidyeval 的非标准评估
Use of tidyeval based non-standard evaluation in recode in right-hand side of mutate
考虑一个 tibble,其中每一列都是一个可以取很多值的字符向量——假设 "A" 到 "F"。
library(tidyverse)
sample_df <- tibble(q1 = c("A", "B", "C"), q2 = c("B", "B", "A"))
我希望创建一个函数,该函数将列名作为参数,并重新编码该列,以便任何答案 "A" 变为 NA,否则 df 将按原样返回。以这种方式设计它的原因是为了适应更广泛的管道,该管道使用给定的列执行一系列操作。
有很多方法可以做到这一点。但我有兴趣了解什么是最好的惯用 tidy_eval/tidyverse 方法。首先,问题名称需要位于 mutate 动词的左侧,因此我们适当地使用 !!
和 :=
运算符。但是,右手边应该放什么?
fix_question <- function(df, question) {
df %>% mutate(!!question := recode(... something goes here...))
}
fix_question(sample_df, "q1") # should produce a tibble whose first column is (NA, "B", "C")
我最初的想法是这样可行:
df %>% mutate(!!question := recode(!!question, "A" = NA_character_))
当然,函数内部的 bang-bang on 只是 returns 文字字符串(例如 "q1")。我最终采取了一种感觉像是 hacky 的方式来引用右侧的数据,使用基本 R [[
运算符并依赖于 dplyr 的 .
构造,并且它有效,所以在感觉我已经解决了我的潜在问题:
df %>% mutate(!!question := recode(.[[question]], "A" = NA_character_))
我有兴趣从非常擅长 tidyeval 的人那里得到反馈,关于是否有更惯用的方法来做到这一点,希望看到一个有效的例子能加深我对 tidyeval 函数集的理解一般来说。有什么想法吗?
这里,在:=
的右边,我们可以指定sym
转换为symbol,然后求值(!!
)
fix_question <- function(df, question) {
df %>%
mutate(!!question := recode(!! rlang::sym(question), "A" = NA_character_))
}
fix_question(sample_df, "q1")
# A tibble: 3 x 2
# q1 q2
# <chr> <chr>
#1 <NA> B
#2 B B
#3 C A
适用于引用和未引用输入的更好方法是 ensym
fix_question <- function(df, question) {
question <- ensym(question)
df %>%
mutate(!!question := recode(!! question, "A" = NA_character_))
}
fix_question(sample_df, q1)
# A tibble: 3 x 2
# q1 q2
# <chr> <chr>
#1 <NA> B
#2 B B
#3 C A
fix_question(sample_df, "q1")
# A tibble: 3 x 2
# q1 q2
# <chr> <chr>
#1 <NA> B
#2 B B
#3 C A
如果您有rlang >= 0.4.0,您现在可以使用"curly curly"方法。
感谢@eipi10 的解释:
这将 quote-then-unquote 的两步过程合二为一,所以 {{question}}
等价于 !!enquo(question)
fix_question <- function(df, question){
df %>% mutate({{question}} := recode({{question}}, A = NA_character_))
}
fix_question(sample_df, q1)
# # A tibble: 3 x 2
# q1 q2
# <chr> <chr>
# 1 NA B
# 2 B B
# 3 C A
请注意,与 ensym
方法不同,这不适用于角色名称。更糟糕的是,它做了错误的事情,而不是仅仅给出一个错误。
fix_question(sample_df, 'q1')
# # A tibble: 3 x 2
# q1 q2
# <chr> <chr>
# 1 q1 B
# 2 q1 B
# 3 q1 A
您还可以允许将重新编码值的向量作为参数输入,从而使函数更加灵活。例如:
library(tidyverse)
sample_df <- tibble(q1 = c("A", "B", "C"), q2 = c("B", "B", "A"))
fix_question <- function(df, question, recode.vec) {
df %>% mutate({{question}} := recode({{question}}, !!!recode.vec))
}
fix_question(sample_df, q1, c(A=NA_character_, B="Was B"))
q1 q2
1 <NA> B
2 Was B B
3 C A
请注意 recode.vec
是 "unquote-spliced" 和 !!!
。你可以看到这个例子是做什么的,改编自 Programming with dplyr vignette(搜索 "splice" 以查看相关示例)。请注意 !!!
"splices" 如何将这些值对重新编码到 recode
函数中,以便它们用作 recode
中的 ...
参数。
x = c("A", "B", "C")
args = c(A=NA_character_, B="Was B")
quo(recode(x, !!!args))
<quosure>
expr: ^recode(x, A = <chr: NA>, B = "Was B")
env: global
如果您想潜在地 运行 多列上的重新编码函数,您可以将它变成一个只接受列名和重新编码向量的函数。这种方法似乎对管道更友好。
fix_question <- function(question, recode.vec) {
recode({{question}}, !!!recode.vec)
}
sample_df %>%
mutate_at(vars(matches("q")), list(~fix_question(., c(A=NA_character_, B="Was B"))))
q1 q2
1 <NA> Was B
2 Was B Was B
3 C <NA>
或重新编码单个列:
sample_df %>%
mutate(q1 = fix_question(q1, c(A=NA_character_, B="Was B")))
考虑一个 tibble,其中每一列都是一个可以取很多值的字符向量——假设 "A" 到 "F"。
library(tidyverse)
sample_df <- tibble(q1 = c("A", "B", "C"), q2 = c("B", "B", "A"))
我希望创建一个函数,该函数将列名作为参数,并重新编码该列,以便任何答案 "A" 变为 NA,否则 df 将按原样返回。以这种方式设计它的原因是为了适应更广泛的管道,该管道使用给定的列执行一系列操作。
有很多方法可以做到这一点。但我有兴趣了解什么是最好的惯用 tidy_eval/tidyverse 方法。首先,问题名称需要位于 mutate 动词的左侧,因此我们适当地使用 !!
和 :=
运算符。但是,右手边应该放什么?
fix_question <- function(df, question) {
df %>% mutate(!!question := recode(... something goes here...))
}
fix_question(sample_df, "q1") # should produce a tibble whose first column is (NA, "B", "C")
我最初的想法是这样可行:
df %>% mutate(!!question := recode(!!question, "A" = NA_character_))
当然,函数内部的 bang-bang on 只是 returns 文字字符串(例如 "q1")。我最终采取了一种感觉像是 hacky 的方式来引用右侧的数据,使用基本 R [[
运算符并依赖于 dplyr 的 .
构造,并且它有效,所以在感觉我已经解决了我的潜在问题:
df %>% mutate(!!question := recode(.[[question]], "A" = NA_character_))
我有兴趣从非常擅长 tidyeval 的人那里得到反馈,关于是否有更惯用的方法来做到这一点,希望看到一个有效的例子能加深我对 tidyeval 函数集的理解一般来说。有什么想法吗?
这里,在:=
的右边,我们可以指定sym
转换为symbol,然后求值(!!
)
fix_question <- function(df, question) {
df %>%
mutate(!!question := recode(!! rlang::sym(question), "A" = NA_character_))
}
fix_question(sample_df, "q1")
# A tibble: 3 x 2
# q1 q2
# <chr> <chr>
#1 <NA> B
#2 B B
#3 C A
适用于引用和未引用输入的更好方法是 ensym
fix_question <- function(df, question) {
question <- ensym(question)
df %>%
mutate(!!question := recode(!! question, "A" = NA_character_))
}
fix_question(sample_df, q1)
# A tibble: 3 x 2
# q1 q2
# <chr> <chr>
#1 <NA> B
#2 B B
#3 C A
fix_question(sample_df, "q1")
# A tibble: 3 x 2
# q1 q2
# <chr> <chr>
#1 <NA> B
#2 B B
#3 C A
如果您有rlang >= 0.4.0,您现在可以使用"curly curly"方法。
感谢@eipi10 的解释:
这将 quote-then-unquote 的两步过程合二为一,所以 {{question}}
等价于 !!enquo(question)
fix_question <- function(df, question){
df %>% mutate({{question}} := recode({{question}}, A = NA_character_))
}
fix_question(sample_df, q1)
# # A tibble: 3 x 2
# q1 q2
# <chr> <chr>
# 1 NA B
# 2 B B
# 3 C A
请注意,与 ensym
方法不同,这不适用于角色名称。更糟糕的是,它做了错误的事情,而不是仅仅给出一个错误。
fix_question(sample_df, 'q1')
# # A tibble: 3 x 2
# q1 q2
# <chr> <chr>
# 1 q1 B
# 2 q1 B
# 3 q1 A
您还可以允许将重新编码值的向量作为参数输入,从而使函数更加灵活。例如:
library(tidyverse)
sample_df <- tibble(q1 = c("A", "B", "C"), q2 = c("B", "B", "A"))
fix_question <- function(df, question, recode.vec) {
df %>% mutate({{question}} := recode({{question}}, !!!recode.vec))
}
fix_question(sample_df, q1, c(A=NA_character_, B="Was B"))
q1 q2 1 <NA> B 2 Was B B 3 C A
请注意 recode.vec
是 "unquote-spliced" 和 !!!
。你可以看到这个例子是做什么的,改编自 Programming with dplyr vignette(搜索 "splice" 以查看相关示例)。请注意 !!!
"splices" 如何将这些值对重新编码到 recode
函数中,以便它们用作 recode
中的 ...
参数。
x = c("A", "B", "C")
args = c(A=NA_character_, B="Was B")
quo(recode(x, !!!args))
<quosure>
expr: ^recode(x, A = <chr: NA>, B = "Was B")
env: global
如果您想潜在地 运行 多列上的重新编码函数,您可以将它变成一个只接受列名和重新编码向量的函数。这种方法似乎对管道更友好。
fix_question <- function(question, recode.vec) {
recode({{question}}, !!!recode.vec)
}
sample_df %>%
mutate_at(vars(matches("q")), list(~fix_question(., c(A=NA_character_, B="Was B"))))
q1 q2 1 <NA> Was B 2 Was B Was B 3 C <NA>
或重新编码单个列:
sample_df %>%
mutate(q1 = fix_question(q1, c(A=NA_character_, B="Was B")))