如果我想使类似 dplyr 的函数能够与 NSE 和 SE 一起工作,如何解决 {{}} 和 all_off() 的问题?
How to fix the issue with {{}} and all_off() if when I want to make a dplyr-like function able to work both with NSE and SE?
我想编写一个函数,它将列的符号名称和作为变量(字符串)传递的名称。
举个例子:
数据:
> ( d <- data.frame(A=1:3, B=3:1) )
A B
1 1 3
2 2 2
3 3 1
现在我的函数:
fn <- function(data, cols) {
return(data %>% mutate(across({{cols}}, ~. * 2)))
}
适用于:
A) 符号名
> d %>% fn(cols = A)
A B
1 2 3
2 4 2
3 6 1
> d %>% fn(cols = B)
A B
1 1 6
2 2 4
3 3 2
> d %>% fn(cols = c(A, B))
A B
1 2 6
2 4 4
3 6 2
B) 名称作为字符串传递
> column <- "A"
> d %>% fn(cols = column)
A B
1 2 3
2 4 2
3 6 1
> d %>% fn(cols = c("A", "B"))
A B
1 2 6
2 4 4
3 6 2
到目前为止,一切顺利!
现在,当我提供大于 1 列的外部矢量时,它会发出警告。
> d %>% fn(cols = columns)
Note: Using an external vector in selections is ambiguous.
i Use `all_of(columns)` instead of `columns` to silence this message.
i See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
This message is displayed once per session.
A B
1 2 6
2 4 4
3 6 2
所以我添加了 all_of 函数,它适用于字符串:
fn <- function(data, cols) {
return(data %>% mutate(across(all_of({{cols}}), ~. * 2)))
}
> d %>% fn(cols = columns)
A B
1 2 6
2 4 4
3 6 2
但是当我传递符号名称时抛出错误:
> d %>% fn(cols = A)
Error: Problem with `mutate()` input `..1`.
x object 'A' not found
i Input `..1` is `across(all_of(A), ~. * 2)`.
Run `rlang::last_error()` to see where the error occurred. > d %>% fn(cols = B)
> d %>% fn(cols = c(A, B))
Error: Problem with `mutate()` input `..1`.
x object 'A' not found
i Input `..1` is `across(all_of(c(A, B)), ~. * 2)`.
Run `rlang::last_error()` to see where the error occurred.
如何解决这个问题,启用这两种方法并避免警告?
我的建议是保留您的原始实现及其附带的警告,因为情况确实 不明确。考虑:
d <- data.frame(A=1:3, B=3:1, columns=4:6) # Note the new column named columns
columns <- c("A","B")
d %>% fn(cols = columns) # Which `columns` should this use?
您的函数的用户随后可以使用 all_of()
自己来解决歧义,您可以在函数的帮助页面中记录这一点。
d %>% fn(cols = all_of(columns)) # works without a warning
编辑: 虽然我推荐上述方法,但另一种方法是检查调用环境中是否存在变量。如果该变量存在,则假定它包含列名并在all_of()
中使用它;否则,假定列名按原样提供:
fn <- function(data, cols) {
varExist <- rlang::enexpr(cols) %>%
rlang::expr_deparse() %>%
exists(envir=rlang::caller_env())
if(varExist)
data %>% mutate( across(all_of(cols), ~. *2) )
else
data %>% mutate( across({{cols}}, ~. * 2) )
}
rm(A) # Ensure there is no variable called A
d %>% fn(cols=A) # Mutate will operate on column A only
A <- c("A","B") # A now contains column names
d %>% fn(cols=A) # Mutate will operate on A and B
我想编写一个函数,它将列的符号名称和作为变量(字符串)传递的名称。
举个例子:
数据:
> ( d <- data.frame(A=1:3, B=3:1) )
A B
1 1 3
2 2 2
3 3 1
现在我的函数:
fn <- function(data, cols) {
return(data %>% mutate(across({{cols}}, ~. * 2)))
}
适用于:
A) 符号名
> d %>% fn(cols = A)
A B
1 2 3
2 4 2
3 6 1
> d %>% fn(cols = B)
A B
1 1 6
2 2 4
3 3 2
> d %>% fn(cols = c(A, B))
A B
1 2 6
2 4 4
3 6 2
B) 名称作为字符串传递
> column <- "A"
> d %>% fn(cols = column)
A B
1 2 3
2 4 2
3 6 1
> d %>% fn(cols = c("A", "B"))
A B
1 2 6
2 4 4
3 6 2
到目前为止,一切顺利!
现在,当我提供大于 1 列的外部矢量时,它会发出警告。
> d %>% fn(cols = columns)
Note: Using an external vector in selections is ambiguous.
i Use `all_of(columns)` instead of `columns` to silence this message.
i See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
This message is displayed once per session.
A B
1 2 6
2 4 4
3 6 2
所以我添加了 all_of 函数,它适用于字符串:
fn <- function(data, cols) {
return(data %>% mutate(across(all_of({{cols}}), ~. * 2)))
}
> d %>% fn(cols = columns)
A B
1 2 6
2 4 4
3 6 2
但是当我传递符号名称时抛出错误:
> d %>% fn(cols = A)
Error: Problem with `mutate()` input `..1`.
x object 'A' not found
i Input `..1` is `across(all_of(A), ~. * 2)`.
Run `rlang::last_error()` to see where the error occurred. > d %>% fn(cols = B)
> d %>% fn(cols = c(A, B))
Error: Problem with `mutate()` input `..1`.
x object 'A' not found
i Input `..1` is `across(all_of(c(A, B)), ~. * 2)`.
Run `rlang::last_error()` to see where the error occurred.
如何解决这个问题,启用这两种方法并避免警告?
我的建议是保留您的原始实现及其附带的警告,因为情况确实 不明确。考虑:
d <- data.frame(A=1:3, B=3:1, columns=4:6) # Note the new column named columns
columns <- c("A","B")
d %>% fn(cols = columns) # Which `columns` should this use?
您的函数的用户随后可以使用 all_of()
自己来解决歧义,您可以在函数的帮助页面中记录这一点。
d %>% fn(cols = all_of(columns)) # works without a warning
编辑: 虽然我推荐上述方法,但另一种方法是检查调用环境中是否存在变量。如果该变量存在,则假定它包含列名并在all_of()
中使用它;否则,假定列名按原样提供:
fn <- function(data, cols) {
varExist <- rlang::enexpr(cols) %>%
rlang::expr_deparse() %>%
exists(envir=rlang::caller_env())
if(varExist)
data %>% mutate( across(all_of(cols), ~. *2) )
else
data %>% mutate( across({{cols}}, ~. * 2) )
}
rm(A) # Ensure there is no variable called A
d %>% fn(cols=A) # Mutate will operate on column A only
A <- c("A","B") # A now contains column names
d %>% fn(cols=A) # Mutate will operate on A and B