了解何时在函数中使用ensym,sym与enquo

  • 大多数dplyr参数不是透明。这意味着你不能用一个看似等价的对象代替一个在别处定义的值

    1. df <- tibble(x = 1:3, y = 3:1)
    2. filter(df, x == 1)
    3. ## # A tibble: 1 x 2
    4. ## x y
    5. ## <int> <int>
    6. ## 1 1 3
  • 不等价下面这个代码:

    1. my_var <- x
    2. filter(df, my_var == 1)
    3. # Error: object 'x' not found
  • 或是这个代码

    1. my_var <- "x"
    2. filter(df, my_var == 1)
    3. ## # A tibble: 0 x 2
    4. ## # ... with 2 variables: x <int>, y <int>

    这使得你用dplyr创建函数,你会发现它总是以一种无法被理解的形式报错

  • dplyr代码不明确,取决于在哪里定义了哪些变量, filter(df, x == y)可以等价于下面任意一个

    1. df[df$x == df$y, ]
    2. df[df$x == y, ]
    3. df[x == df$y, ]
    4. df[x == y, ]

    目标

  • 演示如何使用dplyr的pronounsquasiquotation编写可靠的函数,以减少数据分析代码中的重复。

  • 教你基本理论,包括quosures——一个存储表达式和环境的数据结构,以及tidyeval——底层工具包。

    初入

    在使用字符串构建函数的时候,你可能碰到过这样的问题
    1. greet <- function(name) {
    2. "How do you do, name?"
    3. }
    4. greet("jingjing")
    5. ## [1] "How do you do, name?"
    这是因为引号”把输入括起来了:它没有对你输入的东西进行解释,它仅仅将输入作为一个字符串进行存储。一种解决的办法是使用paste()函数将字符串粘连起来
    1. greet <- function(name) {
    2. paste0("How do you do, ", name, "?")
    3. }
    4. greet("jingjing")
    5. ## [1] "How do you do, jingjing?"
    另一个办法是使用glue包,它可以“unquote”一个字符串,用R表达式的值替换字符串
    1. greet <- function(name) {
    2. glue::glue("How do you do, {name}?")
    3. }
    4. greet("jingjing")
    5. ## 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.

  1. - 我们可以通过使用.data代词(pronoun)进行更明确地指定以修正这种不确定性。如果变量不存在,这会抛出一个信息错误。
  2. ```r
  3. mutate_y <- function(df) {
  4. mutate(df, y = .data$a + .data$x)
  5. }
  6. mutate_y(df1)
  7. ## Error in mutate_impl(.data, dots): Evaluation error: Column `a`: not found in data.

创建一个变化的分组用于数据汇总

  1. my_summarise <- function(df, group_var) {
  2. df %>%
  3. group_by(group_var) %>%
  4. summarise(a = mean(a))
  5. }
  6. my_summarise(df, g1)
  7. ## Error in grouped_df_impl(data, unname(vars), drop): Column `group_var` is unknown
  8. my_summarise(df, "g2")
  9. ## 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

  1. <a name="ruB0G"></a>
  2. ### 不同的输入变量
  3. 现在让我们来处理一些更复杂的问题。下面代码显示了重复的summarise()语句,计算三个汇总量,根据输入变量相应改变。
  4. ```r
  5. summarise(df, mean = mean(a), sum = sum(a), n = n())
  6. ## # A tibble: 1 x 3
  7. ## mean sum n
  8. ## <dbl> <int> <int>
  9. ## 1 3. 15 5
  10. summarise(df, mean = mean(a * b), sum = sum(a * b), n = n())
  11. ## # A tibble: 1 x 3
  12. ## mean sum n
  13. ## <dbl> <int> <int>
  14. ## 1 9.60 48 5

将其转化为一个函数

  1. my_summarise2 <- function(df, expr) {
  2. summarise(df,
  3. mean = mean({{expr}}),
  4. sum = sum({{expr}}),
  5. n = n())
  6. }
  7. > my_summarise2(mtcars, mpg)
  8. mean sum n
  9. #1 20.09062 642.9 32
  10. > my_summarise2(mtcars, mpg*cyl)
  11. mean sum n
  12. #1 115.425 3693.6 32

挑战变化输出变量名字

  • 可能遇到的问题:!! mean_name = mean(!! expr) 不是合法的R代码,我们要使用由rlang提供的:=帮助函数

    1. mutate(df, mean_a = mean(a), sum_a = sum(a))
    2. ## # A tibble: 5 x 6
    3. ## g1 g2 a b mean_a sum_a
    4. ## <dbl> <dbl> <int> <int> <dbl> <int>
    5. ## 1 1. 1. 1 3 3. 15
    6. ## 2 1. 2. 4 2 3. 15
    7. ## 3 2. 1. 2 1 3. 15
    8. ## 4 2. 2. 5 4 3. 15
    9. ## # ... with 1 more row
    10. mutate(df, mean_b = mean(b), sum_b = sum(b))
    11. ## # A tibble: 5 x 6
    12. ## g1 g2 a b mean_b sum_b
    13. ## <dbl> <dbl> <int> <int> <dbl> <int>
    14. ## 1 1. 1. 1 3 3. 15
    15. ## 2 1. 2. 4 2 3. 15
    16. ## 3 2. 1. 2 1 3. 15
    17. ## 4 2. 2. 5 4 3. 15
    18. ## # ... with 1 more row

    讲上面的表达式变为函数

    1. my_mutate <- function(df, expr) {
    2. mutate(df,
    3. "mean_{{expr}}" := mean({{expr}}),
    4. "sum_{{expr}}" := sum({{expr}})
    5. )
    6. }
    7. my_mutate(mtcars[1:5,c(1:3)],mpg)
    8. mpg cyl disp mean_mpg sum_mpg
    9. # Mazda RX4 21.0 6 160 20.98 104.9
    10. # Mazda RX4 Wag 21.0 6 160 20.98 104.9
    11. # Datsun 710 22.8 4 108 20.98 104.9
    12. # Hornet 4 Drive 21.4 6 258 20.98 104.9
    13. # 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)

  1. - ... 参数不需要做引用和反引用就能正确工作,但若要修改结果列名就不行了,仍需要借助引用和反引用,但是要改用enques() 和!!!
  2. ```r
  3. my_summarise <- function(df, ...) {
  4. group_var <- quos(...)
  5. df %>%
  6. group_by(!!! group_var) %>%
  7. summarise(a = mean(a))
  8. }
  9. my_summarise(mtcars,cyl,am)
  10. my_summarise <- function(df, ...) {
  11. group_var <- enquos(...)
  12. df %>%
  13. group_by(!!! group_var) %>%
  14. summarise(mpg = mean(mpg))
  15. }
  16. my_summarise(mtcars,cyl,am)

上面两个等价