purrr :: pmap与rlang的混淆行为; “引用”或不引用Q的论点

时间:2018-05-22 02:07:13

标签: r dplyr purrr rlang tidyeval

我有一个自定义函数,我正在使用rlang从数据框中读取输入的变量。无论输入的参数是引用还是不引用,此函数都可以正常 。但是,奇怪的是,当此函数与purrr::pmap一起使用时,仅在引用参数时才有效。

所以我有两个问题:

  1. 为什么函数行为这样?

  2. 如何使用rlang创建函数,即使在purrr::pmap中使用,我也不必引用参数?

  3. 这是一个使用简单函数突出显示此问题的最小代表:

    # loading the needed libraries
    library(rlang)
    library(dplyr)
    library(purrr)
    
    
    # defining the function
    tryfn <- function(data, x, y) {
      data <-
        dplyr::select(
          .data = data,
          x = !!rlang::enquo(x),
          y = !!rlang::enquo(y)
        )
    
      # creating a dataframe of means
      result_df <- data.frame(mean.x = mean(data$x), mean.y = mean(data$y))
    
      # return the dataframe
      return(result_df)
    }
    
    # without quotes (works!)
    tryfn(iris, Sepal.Length, Sepal.Width)
    #>     mean.x   mean.y
    #> 1 5.843333 3.057333
    
    # with quotes (works!)
    tryfn(iris, "Sepal.Length", "Sepal.Width")
    #>     mean.x   mean.y
    #> 1 5.843333 3.057333
    
    # pmap without quotes (doesn't work)
    purrr::pmap(.l = list(
      data = list(iris, mtcars, ToothGrowth),
      x = list(Sepal.Length, wt, len),
      y = list(Sepal.Width, mpg, dose)
    ),
    .f = tryfn)
    #> Error in is.data.frame(.l): object 'Sepal.Length' not found
    
    # pmap with quotes (works!)
    purrr::pmap(.l = list(
      data = list(iris, mtcars, ToothGrowth),
      x = list("Sepal.Length", "wt", "len"),
      y = list("Sepal.Width", "mpg", "dose")
    ),
    .f = tryfn)
    #> [[1]]
    #>     mean.x   mean.y
    #> 1 5.843333 3.057333
    #> 
    #> [[2]]
    #>    mean.x   mean.y
    #> 1 3.21725 20.09062
    #> 
    #> [[3]]
    #>     mean.x   mean.y
    #> 1 18.81333 1.166667
    

    reprex package(v0.2.0)创建于2018-05-21。

2 个答案:

答案 0 :(得分:3)

问题是:R看到Sepal.Length, wt, len符号,因此它试图查看当前环境并对其进行评估。当然,由于它们是数据框的列,因此导致错误。当你引用它们时,R没有尝试评估并返回值,因为它将它们视为字符串。

如果您将list替换为base::alistdplyr::varsrlang::exprs,则应该有效

注意:由于我们已经引用了输入,因此我们不再需要在rlang::enquo内使用tryfn了。

# loading the needed libraries
library(rlang)
library(tidyverse)

# defining the function
tryfn <- function(data, x, y) {
  data <-
    dplyr::select(
      .data = data,
      x = !! x,
      y = !! y
    )

  # creating a data frame of means
  result_df <- data.frame(mean.x = mean(data$x), mean.y = mean(data$y))

  # return the data frame
  return(result_df)
}

# alist handles its arguments as if they described function arguments. 
# So the values are not evaluated, and tagged arguments with no value are 
# allowed whereas list simply ignores them. 

purrr::pmap(.l = list(
  data = list(iris, mtcars, ToothGrowth),
  x    = alist(Sepal.Length, wt, len),
  y    = alist(Sepal.Width, mpg, dose)
),
.f = tryfn)

#> [[1]]
#>     mean.x   mean.y
#> 1 5.843333 3.057333
#> 
#> [[2]]
#>    mean.x   mean.y
#> 1 3.21725 20.09062
#> 
#> [[3]]
#>     mean.x   mean.y
#> 1 18.81333 1.166667


purrr::pmap(.l = list(
  data = list(iris, mtcars, ToothGrowth),
  x    = dplyr::vars(Sepal.Length, wt, len),
  y    = dplyr::vars(Sepal.Width, mpg, dose)
),
.f = tryfn)

#> [[1]]
#>     mean.x   mean.y
#> 1 5.843333 3.057333
#> 
#> [[2]]
#>    mean.x   mean.y
#> 1 3.21725 20.09062
#> 
#> [[3]]
#>     mean.x   mean.y
#> 1 18.81333 1.166667

purrr::pmap(.l = list(
  data = list(iris, mtcars, ToothGrowth),
  x    = rlang::exprs(Sepal.Length, wt, len),
  y    = rlang::exprs(Sepal.Width, mpg, dose)
),
.f = tryfn)

#> [[1]]
#>     mean.x   mean.y
#> 1 5.843333 3.057333
#> 
#> [[2]]
#>    mean.x   mean.y
#> 1 3.21725 20.09062
#> 
#> [[3]]
#>     mean.x   mean.y
#> 1 18.81333 1.166667

reprex package(v0.2.0)创建于2018-05-21。

答案 1 :(得分:1)

问题不在于purrr,真的。可以通过以下方式观察到相同的行为:

list(Sepal.Length) # Error: object 'Sepal.Length' not found

根据我的理解,当你将参数传递给你创建的函数时,!!enquo等所有魔法都可用。 >。这就是为什么它可以直接将未加引号的字段名称传递给tryfn()

但是使用pmap(),您需要在Sepal.Width定义中添加字段名称(wtlist等),list不是那样的 - 所以pmap从来没有机会将事情传递给tryfn,因为你的list barf定义了。

将字段名称作为字符串传递正常,因为list可以容纳该数据类型,然后pmap有机会将它们映射到tryfn()

哈德利对quasiquotation with dplyr的评论可能对您有用。

回答你的第二个问题:

  

如何使用rlang创建函数,即使在purrr :: pmap中使用,我也不必引用参数?

您可以使用quo()打包您的字段名称,以避免字面上引用它们作为字符串,但我不确定这是一个很大的改进:

purrr::pmap(.l = list(
  data = list(iris, mtcars, ToothGrowth),
  x = list(quo(Sepal.Length), quo(wt), quo(len)),
  y = list(quo(Sepal.Width), quo(mpg), quo(dose))
),
.f = tryfn) %>% 
  bind_rows(., .id="dataset")

  dataset    mean.x    mean.y
1       1  5.843333  3.057333
2       2  3.217250 20.090625
3       3 18.813333  1.166667