0

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

以 mtcars 为例,如果我想通过说我只想要具有cyl > 4和的行来对数据进行子集化mpg > 15,在这种情况下,我的函数将接受两个列名cylmpg,每个列名还有两个下边界,即 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 中得到了 avgcrl 的 0 个计数和 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))

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

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

不是这个:

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

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

4

3 回答 3

3

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

以 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()
  2. 将该表达式与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 返回它的第一个参数,所以它可以评估它。

  2. R 看到参数 toeval()是一个<-调用。然后它开始评估它。

  3. RHS 是描述如何对mtcars. 此 RHS 分配给 LHS filter1

  4. <-无形地返回 RHS。这就是eval()作为论据的内容。

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

于 2019-11-09T11:09:21.247 回答
1

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)
于 2019-11-11T09:22:14.430 回答
0

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

于 2019-11-10T22:11:50.303 回答