【问题标题】:How does filter() in dplyr evaluate what's inside the () in a customized function?dplyr 中的 filter() 如何评估自定义函数中 () 中的内容?
【发布时间】:2019-11-08 22:35:34
【问题描述】:

我正在尝试编写一个函数,该函数采用两个列名以及每个列名的上限和/或下限,这样我就可以使用我选择的列名和边界对数据进行子集化。

以 mtcars 为例,如果我想通过说我只想要具有 cyl > 4 和 mpg > 15 的行来对数据进行子集化,在这种情况下,我的函数将采用两个列名 cyl 和 mpg ,每个列名也有两个下边界,分别是 4 和 15。当然,在函数中,我可以选择为其分配一个上边界,以将列名(变量)保持在一定范围内。

所以我想出了类似下面的东西,一个函数,它接受您选择的两个变量名称以及每个变量的上限和/或下限。

如果我只给这个变量一个上限或下限,那么它会给我小于或大于这个边界的任何东西,如果我给函数同时给出上限和下限,它会给我返回范围内的行.

comb_function<-function(df,var1,var2,var1_lower=NULL,var1_upper=NULL,var2_upper=NULL,var2_lower=NULL){
   var1<-enexpr(var1)
   var2<-enexpr(var2)
 #####for var2,if upper boundary are given by user,do this#####{
    filter1<-expr(`$`(df,!!var2))<=var2_upper
    #for var1, if upper boundary are given by user,do this# {
      filter2<-expr(`$`(df,!!var1))<=var1_upper}
    #for var 1,if lower boundary are given by user, do this#{
      filter2<-expr(`$`(df,!!var1))>=var1_lower}
    #for var1, if both are given by user, do this#{
      filter2<-expr(`$`(df,!!var1))>=var1_lower&expr(`$`(df,!!var1))<=var1_upper}
  }
  #####for var2,if lower boundary are given by user,do this#####{
    filter1<-expr(`$`(df,!!var2))>=var2_lower 
    #for var1,if upper boundary are given by user,do this#{
      filter2<-expr(`$`(df,!!var1))<=var1_upper}
    #for var1,if lower boundary are given by user,do this#{
      filter2<-expr(`$`(df,!!var1))>=var1_lower}
    #if both are given by the user,do this{
      filter2<-expr(`$`(df,!!var1))>=var1_lower&expr(`$`(df,!!var1))<=var1_upper}
  }
  #####for var2,if both are given by user,do this#####{
    filter1<-expr(`$`(df,!!var2))<=var2_upper&expr(`$`(df,!!var2))>=var2_lower
    #for var1,if upper boundary are given by user,do this#{
      filter2<-expr(`$`(df,!!var1))<=var1_upper}
    #for var1,if lower boundary are given by user,do this#{
      filter2<-expr(`$`(df,!!var1))>=var1_lower}
    #if both are given by user, do this#{
      filter2<-expr(`$`(df,!!var1))>=var1_lower&expr(`$`(df,!!var1))<=var1_upper}
  }
   output<-df%>%filter(filter1,filter2)%>%summarise(count=n(),avgcyl=mean(cyl,na.rm=TRUE))
    return(output)
}

当我以 mtcars 为例调用这个函数时

final1<-comb_function(df=mtcars,var1=mpg,var2=cyl,var1_lower =15,var2_lower=4,var2_upper=6)

我在 final1 中得到 0 个计数和 avgcrl 的 NaN。因此,当filter() 评估() 中的内容时,它只会得到 FALSE,而不是 TRUE,我认为这就是没有返回行的原因。

我对为什么会发生这种情况有一个理论。如果我这样做:

x<-expr(cyl);eval(expr(expr(`$`(mtcars,!!x))<=6))

返回:

[1]FALSE

这显然不是我所期望的。如果我这样做:

eval(expr(`$`(mtcars,!!x)))<=6

返回

[1]  TRUE  TRUE  TRUE  TRUE FALSE  TRUE FALSE  TRUE  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE  TRUE  TRUE FALSE
[23] FALSE FALSE FALSE  TRUE  TRUE  TRUE FALSE  TRUE FALSE  TRUE

这就是我想要的 filter() 函数中的函数。所以我猜当filter() 评估() 里面的内容时,它会自动将括号括在整个表达式中,就像

eval(expr(expr(`$`(mtcars,!!x))<=6))

did,它只返回一个 FALSE。因此,如果这真的是我所期望的原因,我该如何让filter() 知道我真正想要的是让它像这样评估:

eval(filter1<-expr(`$`(df,!!var2)))<=var2_upper

不是这个:

eval(filter1<-expr(`$`(df,!!var2))<=var2_upper)

如果我猜的不是怎么回事,也请帮助我。

【问题讨论】:

  • 我建议尽可能简化您的问题。尤其是第一个 sn-p 代码非常复杂且难以阅读。

标签: r dplyr lazy-evaluation tidyeval non-standard-evaluation


【解决方案1】:

总的来说,我强烈建议您远离所有这些引用和评估。 tidy eval 框架提供了更容易使用的替代工具。

以 mtcars 为例,如果我想通过说我只想要具有 cyl &gt; 4 和 mpg &gt; 15 的行来对数据进行子集化

典型的包装函数如下所示:

filter2 <- function(data, var1, var2, lower1, lower2) {
  data %>%
    filter(
      {{ var1 }} > .env$lower1,
      {{ var2 }} > .env$lower2
    )
}
  • 使用{{ 运算符,我们在数据上下文中插入输入表达式。这意味着您可以提供直接引用列名的 R 代码。

  • 对于.env$,我们要求函数环境中的lower 变量。这意味着如果数据框包含lower1 和lower2 列,这些不会干扰。在环境中强制评估的另一种方法是使用!!。

mtcars %>% filter2(cyl, mpg, 4, 15) %>% head()
#>   mpg cyl disp  hp drat  wt qsec vs am gear carb
#> 1  21   6  160 110  3.9 2.6   16  0  1    4    4
#> 2  21   6  160 110  3.9 2.9   17  0  1    4    4
#> 3  21   6  258 110  3.1 3.2   19  1  0    3    1
#> 4  19   8  360 175  3.1 3.4   17  0  0    3    2
#> 5  18   6  225 105  2.8 3.5   20  1  0    3    1
#> 6  19   6  168 123  3.9 3.4   18  1  0    4    4

=================================

此答案的其余部分试图解开您提出的一些难题。这可能有助于更好地了解 R 中的评估模型,但同样,您最好还是找到更简单的方法来解决您的问题。

让我们来:

x<-expr(cyl);eval(expr(expr(`$`(mtcars,!!x))<=6))
#> [1] FALSE

重新格式化一下:

x <- expr(cyl)
eval(expr(expr(`$`(mtcars,!!x)) <= 6))

去除不必要的复杂性:

eval(expr(expr(mtcars$cyl) <= 6))

我们看一下中间结果:

expr(expr(mtcars$cyl) <= 6)
#> expr(mtcars$cyl) <= 6

外部expr()返回一个表达式,指示R:

  1. 创建一个新表达式(使用内部expr())
  2. 将该表达式与6 进行比较

不幸的是,尽管 R 表达式没有任何意义,但它具有可比性。在理想世界中,这将是一个错误:

quote(foo) < 10
#> [1] FALSE

您可能想做的是首先计算表达式中描述的列子集,然后与&lt;=进行比较:

eval(expr(mtcars$cyl)) <= 6
#>  [1]  TRUE  TRUE  TRUE  TRUE FALSE  TRUE FALSE  TRUE  TRUE  TRUE
#> [11]  TRUE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE  TRUE
#> [21]  TRUE FALSE FALSE FALSE FALSE  TRUE  TRUE  TRUE FALSE  TRUE
#> [31] FALSE  TRUE

另一个注释。你写:

eval(filter1<-expr(`$`(df,!!var2)))

重新格式化和简化:

eval(filter1 <- expr(mtcars$cyl)))

当您对此进行评估时,会发生以下情况:

  1. eval() 要求 R 返回它的第一个参数,以便它可以评估它。

  2. R 发现eval() 的参数是&lt;- 调用。然后它开始评估它。

  3. RHS 是一个描述如何对mtcars 进行子集化的表达式。此 RHS 分配给 LHS filter1。

  4. &lt;- 不可见地返回 RHS。这就是 eval() 得到的参数。

  5. eval() 继续计算 mtcars 子集。

【讨论】:

  • 谢谢!它们非常有用。我还有一个后续问题要问你,我必须通过回答我自己的问题来发布。我在答案中@你的用户名,所以你知道这是对你的答案的回应。再次感谢!!
【解决方案2】:

在https://stackoverflow.com/a/58793418/1725177 xiahfyj 中询问如何在与filter() 不同的步骤中计算过滤器。通常可以使用transmute() 执行单独的计算。此函数接受输入并返回每个输入包含一列的数据框。输入是在数据帧内计算的,如果有的话,也是在组内计算的。

filter3 <- function(data, var1, var2, lower1, lower2) {
  filters <- data %>% transmute(
    filter_a = {{ var1 }} > .env$lower1,
    filter_b = {{ var2 }} > .env$lower2
  )

  data %>%
    filter(!!!unname(filters))
}

然后可以将评估过滤器列的数据框拼接成filter()。强制拼接运算符!!! 将其参数转换为周围调用中的多个输入(这里是对filter() 的调用)。

在filter() 的情况下,输入的数据框必须是未命名的,因为在filter() 中有一个特殊检查以对命名输入抛出错误,以便在写入a = foo 时捕获常见的拼写错误a == foo.

我们计划在 dplyr 的下一个主要版本中支持数据框输入,并自动拼接它们。在这种情况下,最后一步将变得如此简单:

  data %>%
    filter(filters)

【讨论】:

    【解决方案3】:

    @莱昂内尔·亨利 谢谢!我确实对您的示例有后续问题。

    filter2 <- function(data, var1, var2, lower1, lower2) {
      data %>%
        filter(
          {{ var1 }} > .env$lower1,
          {{ var2 }} > .env$lower2
        )
    }
    

    如果我想要类似下面的块怎么办?基本上我想把你在 filter() 里面的东西拿出来,并事先将它们存储在一些变量中。我知道下面的功能不起作用。我想知道我应该如何编辑它以使其工作。

    filter2 <- function(data, var1, var2, lower1, lower2) {
    filter_a<-{{ var1 }} > .env$lower1
    filter_b<-{{ var2 }} > .env$lower2
      data %>%
        filter(filter_a,filter_b)
    }
    

    我想要这个的原因是因为我的函数的目的是 filter() 里面的东西是动态的。例如,我需要这样的东西:

    ###if both lower and upper boundary for var1 are given by the user,do below:
       filter_a<-{{ var1 }} > .env$lower1&{{ var1 }} < .env$upper1
    ###if only upper are given.do below:
       filter_a<-{{ var1 }} < .env$upper1
    ###if only lower are given, do below:
       filter_a<-{{ var1 }} > .env$lower1
    

    这也是为什么我在原来的长而“难以阅读”的问题中有这么多 if 语句的原因。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-06-30
      • 2019-03-23
      • 2018-05-31
      • 1970-01-01
      • 1970-01-01
      • 2019-09-23
      • 1970-01-01
      • 2011-12-25
      相关资源
      最近更新 更多