dplyr中的filter()如何评估自定义函数中()的内容?

问题描述 投票:0回答:2

我正在尝试编写一个函数,该函数采用两个列名以及每个列名的上下边界,因此我可以使用自己选择的列名和边界对数据进行子集化。以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)

如果我猜不是正在发生的事情,请也帮助我。

r dplyr lazy-evaluation tidyeval non-standard-evaluation
2个回答
0
投票

通常,我强烈建议您不要使用所有引用和评估方法。简洁的评估框架提供了更易于使用的替代工具。

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

典型的包装器功能如下:

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

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

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()
  • 将该表达式与6进行比较
  • [不幸的是,R表达式是可比较的,即使它没有任何意义。在理想情况下,这将是一个错误:

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

    可能您想做的是先计算表达式中描述的列子设置,然后与<=比较:

    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返回其第一个参数,以便可以对其求值。

  • R看到eval()的参数是<-调用。然后,它开始对其进行评估。

  • RHS是描述如何对mtcars进行子集化的一种混淆表达。该RHS分配给LHS filter1

  • <-不可见地返回RHS。这就是eval()作为参数。

  • [eval()继续计算mtcars子设置。

  • @@ Lionel Henry谢谢!我对您的示例确实有后续问题。

    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语句的原因。


    0
    投票

    @@ Lionel Henry谢谢!我对您的示例确实有后续问题。

    © www.soinside.com 2019 - 2024. All rights reserved.