Tidy evaluation
“dplyr是数据处理,特别是操作data.frame最常用的工具,提供了一系列函数来实现常见的数据框的处理,如mutate()
,select()
,为了方便我们在交互环境下处理数据,编写更简单的代码,这些函数利用了非标准求值方法实现(NSE, non-standard evaluation).”
background,
NSE
NSE, 仅当参数是一个表达式(不能直接eval 的参数,比如 quote 的参数,或者在函数内部使用 substitute 使参数为 quoted). 当函数调用的时候,参数会被存储为 promise:参数的表达式及其在环境中的值,直到在函数内部实际使用到参数就会eval.substitute()
捕捉表达式,变量值被替换,而没有用到参数的值,所以不会 eval.
data environment is evaluated before the enclosed environment, we say the data environment does overscope.
formula
expression的定义和 expression 类的区别
rlang
expr()
, 等价于bquote()
enexpr()
, 等价于substitute()
- quosure: quote and closure, formula 的子类,单边公式
dplyr package
dplyr是数据处理,特别是操作 data.frame 最常用的工具,提供了一系列函数来实现常见的数据框 df 的处理,如filter()
,select()
,为了方便我们在交互环境下处理数据,编写更简单的代码,这些函数利用了非标准求值方法实现(NSE, non-standard evaluation)1.
library(dplyr)
df <- data.frame(x = 1:3, y = 3:1)
# 等价于 df[df$x == 1 & df$y == 3, ]
filter(df, x == 1, y == 3)
## x y
## 1 1 3
但是 NSE 方法构建的函数,在交互式环境下可以正常使用,在函数内部调用用变量传递参数可能得到奇怪的结果或者错误.
# 用 var 传递其参数,结果异常
var <- "x"
filter(df, var == 1)
## [1] x y
## <0 rows> (or 0-length row.names)
my_filter <- function(df, var) {
filter(df, var == 1)
}
my_filter(df = df, var = x)
## Error in filter_impl(.data, quo): Evaluation error: object 'x' not found.
因此,dplyr中对于这些函数提供了相应的标准求值版本,形如*_()
,以便在自定义函数或者开发包的时候使用这类函数.
filter_(df, "x == 1")
## x y
## 1 1 3
自0.7版本后,dplyr 采用了一种新的 NSE方法 tidyeval 的策略,上述函数的参数不仅可以是
tidy eval
quote
quote(), substitute():
quote()
,literal quote,not quote the value ;而substitute(arg, env)
quote the value of the arg, substituting any variables bound in env.- quo(), enquo()
quo_name(), :=
unquote
- !!,UQ, unquote
# quo, 捕捉环境极其参数表达式, R general quasure, quosureish
# enquo, 作用于函数的参数,返回一个 quosure,成为 tidy eval quosure
q <- quo(quote)
# 输入 general quasure,返回 quasure
UQ(q)
# unquote, evaluate
"!!"(q)
- !!!, UQS, unquoting splicing
- UQE
创建函数
第一个参数是数据 .data (tbl 或 data.frame),后续参数是表达式 …
数据作为参数
dplyr 中基本的数据操作函数对第一个参数.data者采用SE,所以直接构建函数即可:
mutate(df1, y = a + x)
mutate(df2, y = a + x)
mutate(df2, y = a + x)
my_mutate <- function(df) {
mutate(df, y = a + x)
}
但是,针对 .data 不存在而全局环境中存在的变量,函数可能返回错误的结果,并且错误很难发现.
df1 <- tibble(x = 1:3)
## Warning: `list_len()` is soft-deprecated as of rlang 0.2.0.
## Please use `new_list()` instead
## This warning is displayed once per session.
a <- 10
my_mutate(df1)
## # A tibble: 3 x 2
## x y
## <int> <dbl>
## 1 1 11
## 2 2 12
## 3 3 13
为此, dplyr定义了.data
变量,用于表示输入的数据,也就是第一个参数,当对数据内不存在的变量操作时不会再向父环境中寻值,直接抛出错误2.
my_mutate <- function(df) {
mutate(df, y = .data$a + .data$x)
}
my_mutate(df1)
## Error in mutate_impl(.data, dots): Evaluation error: Column `a` not found in `.data`.
建议在构建自己的函数的时候,使用.data$
访问数据的变量,以免返回错误的结果.
表达式作为参数
后续的参数是非引用(unquoted)的表达式,dplyr 会自动的引用(quote)然后计算这些表达式的值,所以这些参数中不能含有其他变量, 因为它不会在计算的时候用我们定义的变量的值来替换该变量.
df <- tibble(
g1 = c(1, 1, 2, 2, 2),
g2 = c(1, 2, 1, 2, 1),
a = sample(5),
b = sample(5)
)
df %>%
group_by(g1) %>%
summarise(a = mean(a))
## # A tibble: 2 x 2
## g1 a
## <dbl> <dbl>
## 1 1 3
## 2 2 3
df %>%
group_by(g2) %>%
summarise(a = mean(a))
## # A tibble: 2 x 2
## g2 a
## <dbl> <dbl>
## 1 1 3.33
## 2 2 2.5
构建由表达式作为参数的函数时会抛出错误.
my_summarise <- function(df, group_var) {
df %>%
group_by(group_var) %>%
summarise(a = mean(a))
}
# 不会用g1来替换 group_var,只是 quote(group_var)
my_summarise(df, g1)
## Error in grouped_df_impl(data, unname(vars), drop): Column `group_var` is unknown
解决办法是首先 quote参数(函数 quo()
,确保参数 group_var
可以是变量),然后调用group_by()
时unquote (!!
)该参数求值.
my_summarise2 <- function(df, group_var) {
df %>%
group_by(!!group_var) %>%
summarise(a = mean(a))
}
my_summarise2(df, quo(g1))
## # A tibble: 2 x 2
## g1 a
## <dbl> <dbl>
## 1 1 3
## 2 2 3
利用 enquo()
3, 构建 dplyr 类似的函数,`my_summarise(df, group_var)
my_summarise3 <- function(df, group_var) {
group_var <- enquo(group_var)
df %>%
group_by(!!group_var) %>%
summarise(a = mean(a))
}
my_summarise3(df, g1)
## # A tibble: 2 x 2
## g1 a
## <dbl> <dbl>
## 1 1 3
## 2 2 3
多个输入变量
diff_input_summarise <- function(df, expr) {
expr <- enquo(expr)
summarise(df,
mean = mean(!!expr),
sum = sum(!!expr),
n = n()
)
}
diff_input_summarise(df, g1)
## # A tibble: 1 x 3
## mean sum n
## <dbl> <dbl> <int>
## 1 1.6 8 5
diff_input_summarise(df, a * b)
## # A tibble: 1 x 3
## mean sum n
## <dbl> <int> <int>
## 1 10.4 52 5
多个输入和输出变量
形如mutate(df, mean_a = mean(a), sum_a = sum(a))
,根据输入变量创建输出变量,解决方法是利用quo_name()
创建输出变量的名称.4
my_mutate <- function(df, expr) {
expr <- enquo(expr)
mean_name <- paste0("mean_", quo_name(expr))
sum_name <- paste0("sum_", quo_name(expr))
mutate(df,
!!mean_name := mean(!!expr),
!!sum_name := sum(!!expr)
)
}
my_mutate(df, a)
## # A tibble: 5 x 6
## g1 g2 a b mean_a sum_a
## <dbl> <dbl> <int> <int> <dbl> <int>
## 1 1 1 5 5 3 15
## 2 1 2 1 2 3 15
## 3 2 1 3 1 3 15
## 4 2 2 4 4 3 15
## 5 2 1 2 3 3 15
任意多参数
- 多个参数用
...
表示 - 使用
quos()
来quote...
,构成一个 quosure 列表 !!!
代替!!
分割quos
的参数,把列表分割为多参数
args <- list(na.rm = TRUE, trim = 0.25)
quo(mean(x, !!! args))
## <quosure>
## expr: ^mean(x, na.rm = TRUE, trim = 0.25)
## env: global
multiple_arg_summarise <- function(df, ...) {
group_by <- quos(...)
df %>%
group_by(!!!group_by) %>%
summarise(a = mean(a))
}
multiple_arg_summarise(df, g1, g2)
## # A tibble: 4 x 3
## # Groups: g1 [?]
## g1 g2 a
## <dbl> <dbl> <dbl>
## 1 1 1 5
## 2 1 2 1
## 3 2 1 2.5
## 4 2 2 4
引用 Quoting
引用就是不计算其值而直接捕捉表达式,函数中表达式组成的参数调用时首先都是引用这些参数. 最常见的是公式~
y ~ a + b
## y ~ a + b
另外,quote(expr)
直接返回其参数表达式
# 计算表达式
toupper(letters[1:5])
## [1] "A" "B" "C" "D" "E"
# 捕捉表达式
quote(toupper(letters[1:5]))
## toupper(letters[1:5])
其他
- sym: character to name,可以构建函数输入参数为字符,而非表达式, name can also be quoted
NSE 和 SE 的区别:NSE,函数的参数传递代码,SE,函数的参数直接传递其值,具体可参考 https://adv-r.hadley.nz/nse,后面准备专门写一篇详细介绍 NSE 的文章.↩
在开发包时,使用 dplyr 中函数,时使用
.data
访问变量值可以避在R CMD check
产生 undefined global variables 的NOTE
(@importFrom rlang .data
).↩quo(expression)
返回一个quosure,相当于单边公式,表达式及其环境(https://adv-r.hadley.nz/nse),可以在任何表达式中使用.enquo(arg)
,捕捉函数的调用环境,并对 arg 中的变量进行求值,返回最终的表达式, 只适用于函数内部.quo()
和enquo()
类似于 R 中的quote()
和substitute()
.↩!!mean_name = mean(!!expr)
是非法的 R 表达式,需要使用 rlang 中:=
的来创建.↩