使用dplyr和group_by编写自己的函数 - 如何继续更改列名

时间:2017-07-31 04:07:50

标签: r dplyr rlang tidyeval

我想制作出版物的表格,这些表格给出了两个变量分组的观察数量。这个代码工作得很好。但是,在尝试将其转换为函数时遇到了问题。

我正在使用dplyr_0.7.2

使用mtcars的示例:

功能之外的表格代码:此作品

library(tidyverse) 

tab1 <- mtcars %>% count(cyl) %>% rename(Total = n) 

tab2 <- mtcars %>%
  group_by(cyl, gear) %>% count %>% 
  spread(gear, n)

tab <- full_join(tab1, tab2, by = "cyl")
tab


# This is the output (which is what I want)

A tibble: 3 x 5
cyl Total   `3`   `4`   `5`
<dbl> <int> <int> <int> <int>
1     4    11     1     8     2
2     6     7     2     4     1
3     8    14    12    NA     2

尝试将其置于函数

tab1的功能:这可行

count_by_two_groups_A <- function(df, var1){
  var1 <- enquo(var1)
  tab1 <- df %>% count(!!var1) %>% rename(Total = n)
  tab1
} 

count_by_two_groups_A(mtcars, cyl) 

A tibble: 3 x 2
cyl Total
<dbl> <int>
1     4    11
2     6     7
3     8    14

tab2的第一部分的功能:它到目前为止,但是......

count_by_two_groups_B <- function(df, var1, var2){

  var1 <- enquo(var1)
  var2 <- enquo(var2)

  tab2 <- df %>% group_by((!!var1), (!!var2)) %>% count
  tab2
} 

count_by_two_groups_B(mtcars, cyl, gear)

A tibble: 8 x 3
Groups:   (cyl), (gear) [8]
 `(cyl)` `(gear)`     n
 <dbl>    <dbl> <int>
 1       4        3     1
 2       4        4     8
 3       4        5     2
 4       6        3     2
 5       6        4     4
 6       6        5     1
 7       8        3    12
 8       8        5     2

列名已更改为(cyl)和(gear)。由于列名已更改,我似乎无法弄清楚如何继续使用spread()和full_join()(或使用新列名称的任何其他内容)。即我无法弄清楚如何以tidyeval方式指定新列名,以便能够继续。我尝试了各种各样的事情,没有成功。

3 个答案:

答案 0 :(得分:2)

我可以使用NSE(非标准评估)。无法用tidyverse来做,因为我没有安装并且没有安装。

这是一个有效的代码:

library(dplyr)
library(tidyr)

count_by_two_groups_B <- function(df, var1, var2){

 # var1 <- enquo(var1)
 # var2 <- enquo(var2)

  tab2 <- df %>% group_by_(var1, var2) %>% summarise(n = n() )  %>%spread(gear, n)

  tab2
} 

count_by_two_groups_B(mtcars, 'cyl', 'gear')

结果:

# A tibble: 3 x 4
# Groups:   cyl [3]
    cyl   `3`   `4`   `5`
* <dbl> <int> <int> <int>
1     4     1     8     2
2     6     2     4     1
3     8    12    NA     2

答案 1 :(得分:2)

在tidyeval上下文中设置名称的常用方法是使用定义运算符:=。它看起来像这样:

df %>%
  group_by(
    !! nm1 := !! var1,
    !! nm2 := !! var2
  ) %>%
  count()

为此,您需要从nm1中提取var1。不幸的是,我还没有简单的方法来删除括号中的括号。我认为在即将发布的函数ensym()中执行此操作是有意义的(它会捕获符号而不是quosures,并在您提供调用时发出错误)。我在这里提交了一张票:https://github.com/tidyverse/rlang/issues/223

幸运的是,我们在这里有两个简单的解决方案。首先请注意,您不需要括号括起来。仅当其他运算符涉及捕获的表达式时才需要它们。例如。在这些情况下:

(!! var) / avg
(!! var) < value

在这种情况下,如果省略括号,!!将尝试取消引用整个表达式而不是仅仅一个符号。另一方面,在你的函数中没有运算符,所以你可以安全地取消引用而不用括起来:

count_by_two_groups_B <- function(df, var1, var2) {
  var1 <- enquo(var1)
  var2 <- enquo(var2)

  df %>%
    group_by(!! var1, !! var2) %>%
    count()
}

最后,通过允许可变数量的参数,您可以使您的函数更通用。这更容易实现,因为点转发所以不需要捕获和取消引用。只需将它们传递给group_by()

count_by <- function(df, ...) {
  df %>%
    group_by(...) %>%
    count()
}

答案 2 :(得分:0)

这是达到dplyr或tidyverse似乎过度的情况之一。有基本功能可以执行此操作... table并以长整数格式生成结果as.dataframe

as.data.frame( with(mtcars, table(cyl,gear)) , responseName="Total")
#--------
  cyl gear Total
1   4    3     1
2   6    3     2
3   8    3    12
4   4    4     8
5   6    4     4
6   8    4     0
7   4    5     2
8   6    5     1
9   8    5     2

这将是一个dplyr方法:

mtcars %>% group_by(cyl,gear) %>% summarise(Total=n())
#----
# A tibble: 8 x 3
# Groups:   cyl [?]
    cyl  gear Total
  <dbl> <dbl> <int>
1     4     3     1
2     4     4     8
3     4     5     2
4     6     3     2
5     6     4     4
6     6     5     1
7     8     3    12
8     8     5     2

如果问题是如何将其作为表格对象(认为可能是spread的目标,那么只需:

with(mtcars, table(cyl,gear))