了解何时在函数中使用ensym,sym与enquo
大多数dplyr参数不是透明。这意味着你不能用一个看似等价的对象代替一个在别处定义的值
df <- tibble(x = 1:3, y = 3:1)
filter(df, x == 1)
## # A tibble: 1 x 2
## x y
## <int> <int>
## 1 1 3
不等价下面这个代码:
my_var <- x
filter(df, my_var == 1)
# Error: object 'x' not found
或是这个代码
my_var <- "x"
filter(df, my_var == 1)
## # A tibble: 0 x 2
## # ... with 2 variables: x <int>, y <int>
这使得你用dplyr创建函数,你会发现它总是以一种无法被理解的形式报错
dplyr代码不明确,取决于在哪里定义了哪些变量, filter(df, x == y)可以等价于下面任意一个
df[df$x == df$y, ]
df[df$x == y, ]
df[x == df$y, ]
df[x == y, ]
目标
演示如何使用dplyr的pronouns和quasiquotation编写可靠的函数,以减少数据分析代码中的重复。
- 教你基本理论,包括quosures——一个存储表达式和环境的数据结构,以及tidyeval——底层工具包。
初入
在使用字符串构建函数的时候,你可能碰到过这样的问题
这是因为引号”把输入括起来了:它没有对你输入的东西进行解释,它仅仅将输入作为一个字符串进行存储。一种解决的办法是使用paste()函数将字符串粘连起来greet <- function(name) {
"How do you do, name?"
}
greet("jingjing")
## [1] "How do you do, name?"
另一个办法是使用glue包,它可以“unquote”一个字符串,用R表达式的值替换字符串greet <- function(name) {
paste0("How do you do, ", name, "?")
}
greet("jingjing")
## [1] "How do you do, jingjing?"
greet <- function(name) {
glue::glue("How do you do, {name}?")
}
greet("jingjing")
## How do you do, jingjing?
深入
```r mutate_y <- function(df) { mutate(df, y = a + x) } df1 <- tibble(x = 1:3) a <- 10
不幸的是,这种简单的方法存在一个缺点:
它可能会失败——如果其中一个变量不存在于数据框中,但存在于全局环境。
mutate_y(df1)
# A tibble: 3 x 2
x y
1 1 11.
2 2 12.
3 3 13.
- 我们可以通过使用.data代词(pronoun)进行更明确地指定以修正这种不确定性。如果变量不存在,这会抛出一个信息错误。
```r
mutate_y <- function(df) {
mutate(df, y = .data$a + .data$x)
}
mutate_y(df1)
## Error in mutate_impl(.data, dots): Evaluation error: Column `a`: not found in data.
创建一个变化的分组用于数据汇总
my_summarise <- function(df, group_var) {
df %>%
group_by(group_var) %>%
summarise(a = mean(a))
}
my_summarise(df, g1)
## Error in grouped_df_impl(data, unname(vars), drop): Column `group_var` is unknown
my_summarise(df, "g2")
## Error in grouped_df_impl(data, unname(vars), drop): Column `group_var` is unknown
我们发现不论是是使用字符串,或者是变量名都不能把参数传进行
那问题在哪里呢?
- group_by()函数像引号””一样工作:它不会评估(执行)它的输入,而是括起来。
- 这时候我们可以使用{{}},把变量括起来
- 作为函数参数时的间接使用,正常是环境变量,要想作为数据变量使用,则需要用两个大括号括起来{{var}} ```r my_summarise <- function(df, cyl) { df %>% group_by({{cyl}}) %>% summarise(mpg = mean(mpg)) }
my_summarise(mtcars,gear)
A tibble: 3 x 2
gear mpg
1 3 16.1
2 4 24.5
3 5 21.4
<a name="ruB0G"></a>
### 不同的输入变量
现在让我们来处理一些更复杂的问题。下面代码显示了重复的summarise()语句,计算三个汇总量,根据输入变量相应改变。
```r
summarise(df, mean = mean(a), sum = sum(a), n = n())
## # A tibble: 1 x 3
## mean sum n
## <dbl> <int> <int>
## 1 3. 15 5
summarise(df, mean = mean(a * b), sum = sum(a * b), n = n())
## # A tibble: 1 x 3
## mean sum n
## <dbl> <int> <int>
## 1 9.60 48 5
将其转化为一个函数
my_summarise2 <- function(df, expr) {
summarise(df,
mean = mean({{expr}}),
sum = sum({{expr}}),
n = n())
}
> my_summarise2(mtcars, mpg)
mean sum n
#1 20.09062 642.9 32
> my_summarise2(mtcars, mpg*cyl)
mean sum n
#1 115.425 3693.6 32
挑战变化输出变量名字
可能遇到的问题:!! mean_name = mean(!! expr) 不是合法的R代码,我们要使用由rlang提供的:=帮助函数
mutate(df, mean_a = mean(a), sum_a = sum(a))
## # A tibble: 5 x 6
## g1 g2 a b mean_a sum_a
## <dbl> <dbl> <int> <int> <dbl> <int>
## 1 1. 1. 1 3 3. 15
## 2 1. 2. 4 2 3. 15
## 3 2. 1. 2 1 3. 15
## 4 2. 2. 5 4 3. 15
## # ... with 1 more row
mutate(df, mean_b = mean(b), sum_b = sum(b))
## # A tibble: 5 x 6
## g1 g2 a b mean_b sum_b
## <dbl> <dbl> <int> <int> <dbl> <int>
## 1 1. 1. 1 3 3. 15
## 2 1. 2. 4 2 3. 15
## 3 2. 1. 2 1 3. 15
## 4 2. 2. 5 4 3. 15
## # ... with 1 more row
讲上面的表达式变为函数
my_mutate <- function(df, expr) {
mutate(df,
"mean_{{expr}}" := mean({{expr}}),
"sum_{{expr}}" := sum({{expr}})
)
}
my_mutate(mtcars[1:5,c(1:3)],mpg)
mpg cyl disp mean_mpg sum_mpg
# Mazda RX4 21.0 6 160 20.98 104.9
# Mazda RX4 Wag 21.0 6 160 20.98 104.9
# Datsun 710 22.8 4 108 20.98 104.9
# Hornet 4 Drive 21.4 6 258 20.98 104.9
# Hornet Sportabout 18.7 8 360 20.98 104.9
捕捉多个变量
将my_summarise()扩展可以接收任何数目的分组变量就好了。我们需要3个改变:
在函数定义中使用…以便于我们的函数能够接收任意数目的参数。
- 将其他函数参数都增加. 前缀,可以降低与… 参数的冲突风险。
- 使用quos()去捕获所有的…作为公式列表。
- 使用!!!替换!!将参数一个个切进group_by()。 ```r grouped_mean = function(.data, .summary_var, …) { summary_var = enquo(.summary_var) .data %>% group_by(…) %>% summarise(mean = mean(!!summary_var)) } grouped_mean(mtcars, disp, cyl, am)
grouped_mean = function(.data,.summary_var,…){ summary_var = enquo(.summary_var) .data %>% group_by(…) %>% summarise(mean=mean(!!summary_var) }
grouped_mean(mtcars, disp, cyl, am)
- ... 参数不需要做引用和反引用就能正确工作,但若要修改结果列名就不行了,仍需要借助引用和反引用,但是要改用enques() 和!!!
```r
my_summarise <- function(df, ...) {
group_var <- quos(...)
df %>%
group_by(!!! group_var) %>%
summarise(a = mean(a))
}
my_summarise(mtcars,cyl,am)
my_summarise <- function(df, ...) {
group_var <- enquos(...)
df %>%
group_by(!!! group_var) %>%
summarise(mpg = mean(mpg))
}
my_summarise(mtcars,cyl,am)
上面两个等价