dplyr将NULL传递给group_by

3

这个问题可能已经有人回答过了,但我找不到答案...您可以将其标记为重复并进行踩,但请有人帮助我 :)

简短的问题

如何在函数内将NULL传递给dplyr::group_by

library(dplyr)

dt <- data.frame(a = sample(LETTERS[1:2], 100, replace = TRUE), b = sample(LETTERS[3:4], 100, replace = TRUE), value = rnorm(100,5,1))

f1 <- function(dt, a, b, c) {
  dt %>% group_by(a, b, c) %>% summarise(mean = mean(value))
}

f1(dt, a = "a", b = "b", c = NULL)

# Error in grouped_df_impl(data, unname(vars), drop) : 
#  Column `c` is unknown 

长说明

我正在编写一个函数,其中“b”列可以被指定为 NULL ,这意味着该函数应忽略此列。如果将“b”列指定为字符,则函数应使用该列来汇总数据。就像这样:

f2 <- function(dt, a, b) {
  if(is.null(b)) {
    dt %>% group_by(a) %>% summarize(mean = mean(value))
  } else {
    dt %>% group_by(a, b) %>% summarize(mean = mean(value))
  }
}

实际函数相当复杂冗长,使用dplyr管道使所有汇总代码更短。我有多个条件导致不同的输出和汇总选择,因此我通过先进行分组再在单独的步骤中进行汇总来缩短if else语句。
f3 <- function(dt, a, b, type = "mean") {
  if(is.null(b)) {
    tmp <- dt %>% group_by(a) 
  } else {
    tmp <- dt %>% group_by(a, b)
  }

  if(type == "mean") {
    tmp %>% summarize(mean = mean(value))
  } else {
    tmp %>% summarise(sum = sum(value))
  }
}

如果可以将NULL传递给group_by函数,我可以大大缩短代码(因为NULL本来就是空的,像reshape2::melt这样的许多函数都可以使用此方法)。

如果您的数据中没有出现字符串“NULL”,那么您是否可以将“NULL”替换为其字符串等效形式? - Tim Biegeleisen
C总是NULL吗? - Randall Helms
@RandallHelms 不总是,但与长说明中的想法相同。 - Mikko
我想我的例子在翻译中有点混淆。原则是正确的,但它们缺乏合理性。我首先在我的函数内创建了一个自定义类的对象,对其进行了大量修改,然后将其中一个数据框传递给这些条件dplyr管道。NULL实际上是控制是否应该绘制某些元素的参数。 - Mikko
1
这是一篇关于使用NULLNA的好文章:https://www.r-bloggers.com/r-na-vs-null/。在列表中,`c`不应被列为`NULL`,而应该是`NA`。尝试以下代码并查看哪个有效:`group_by(mtcars, cyl, mpg, NA)group_by(mtcars, cyl, mpg, NULL)`。 - Mike
显示剩余2条评论
2个回答

2
我不确定是否涵盖了您所有的用例,但是使用整洁评估的函数(请参见《使用dplyr进行编程》文献)将更加灵活,因为您不必担心有多少分组变量,而且可以通过传递任意向量的函数来进行总结。希望这样可以避免跟踪NULL列或使用ifelse选择摘要函数的需要。
例如,在下面的代码中,...是任意数量的分组列,包括没有分组列。 type参数允许您按一个或多个任意函数进行汇总:
library(tidyverse)
library(rlang)

set.seed(2)
dt <- data.frame(a = sample(LETTERS[1:2], 100, replace = TRUE), 
                 b = sample(LETTERS[3:4], 100, replace = TRUE), 
                 value = rnorm(100,5,1))

f1 = function(data, value.var, ...,  type="mean") {

  groups = enquos(...)
  value.var = enquo(value.var)

  names(type) = paste0(type, "_", quo_text(value.var))
  type = syms(type)

  data %>% group_by(!!!groups) %>% 
    summarise_at(vars(!!value.var), funs(!!!type))
}

f1(dt, value, a, b)
  a     b     mean_value
  <fct> <fct>      <dbl>
1 A     C           5.01
2 A     D           5.05
3 B     C           4.95
4 B     D           5.13
f1(dt, value)
  mean_value
       <dbl>
1       5.03
weird_func = function(x) {
  paste(round(cos(x),1)[1:3], collapse="/")
}

f1(dt, value, a, b, type=c("mean", "min", "median", "max", "weird_func"))
  a     b     mean_value min_value median_value max_value weird_func_value
  <fct> <fct>      <dbl>     <dbl>        <dbl>     <dbl> <chr>           
1 A     C           5.01      3.26         5.07      7.08 1/-0.1/1        
2 A     D           5.05      2.90         5.33      6.36 -0.4/0.9/0      
3 B     C           4.95      3.66         4.73      7.11 0.5/-0.5/0.7    
4 B     D           5.13      2.98         5.46      7.05 0/0.7/0.7
f1(mtcars, mpg, cyl, type=c("mean", "median"))
    cyl mean_mpg median_mpg
  <dbl>    <dbl>      <dbl>
1     4     26.7       26  
2     6     19.7       19.7
3     8     15.1       15.2

1
我认为你需要先将它从NULL转换为NA,像这样(根据你的回答,你只需要将该值传递而不涉及计算)。
library(dplyr)

dt <- data.frame(a = sample(LETTERS[1:2], 100, replace = TRUE), b = sample(LETTERS[3:4], 100, replace = TRUE), value = rnorm(100,5,1))

f1 <- function(dt, a, b, c) {
  dt %>% 
    mutate(c = ifelse(is_empty(c)==TRUE,NA,c)) %>% 
    group_by(a, b,c) %>% 
    summarise(mean = mean(value))
}

f1(dt, a = "a", b = "b",c=NULL)

结果:

# A tibble: 4 x 4
# Groups:   a, b [?]
  a     b     c      mean
  <fct> <fct> <lgl> <dbl>
1 A     C     NA     5.27
2 A     D     NA     5.18
3 B     C     NA     5.27
4 B     D     NA     5.49

不错的技巧!有人可能会称之为笨拙的编程(我的错),但它做到了我要求的(或者更准确地说:如果你添加一个语句来删除只包含NA的列,那么它就能做到,但使用filter很容易实现)。我学到了新东西。谢谢! - Mikko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接