首页 > 解决方案 > 管理 dplyr group_by 函数以在与 group_modify 结合使用时保留分组变量

问题描述

我正在尝试使用该功能(我在这里group_modify了解到)。

目标是取 a data.frame,将其拆分,group_by然后应用自制函数进行一些重组(即排序,选择“最佳行”,如果超过一个,则平均值)。我需要输出data.frame包含原始列的所有列。

这是一个可以让一切变得更清晰的 RE:

数据:

library(dplyr)
(dd <- data.frame(id = c("a", "a", "b", "b", "c", "c", "c"), cat = c("s2", "s1", "s1", "s1", "s3", "s2", "s2"), val = 1:7))
  id cat val
1  a  s2   1
2  a  s1   2
3  b  s1   3
4  b  s1   4
5  c  s3   5
6  c  s2   6
7  c  s2   7

我的功能(显示我的问题的基本功能,但不完全是我实际使用的功能):

simple_fun <- function(slice, key){
  big_out_to_show_error <<- slice

  temp1 <- arrange(slice, cat)
  
  temp2 <- temp1 %>% 
    filter(cat==temp1$cat[1])

  if(nrow(temp2)>1) {
    temp2 <- temp2 %>% 
      group_by(id, cat) %>% 
      summarise(val = mean(val))
  }
  
  return(data.frame(temp2))
  
}

我想要的输出(每个 ID 一行具有“最佳” cat,如果多于一行,则为原始变量的平均值val所有data.frame变量):

  id cat val
a  a  s1 2.0
b  b  s1 3.5
c  c  s2 6.5

我尝试使用dplyr::group_modify函数会引发错误:

dd %>% 
   group_by(id) %>%
   group_modify(simple_fun)
 Show Traceback
 
 Rerun with Debug
 Error: Column `id` is unknown 

这是因为slice使用的 不包括分组变量。从这个简单的代码可以看出这一点,它使用big_out_to_show_error <<- slicemain 函数中的行并限制为id=="a"

filter(dd, id=="a") %>% 
   group_by(id) %>%
   group_modify(simple_fun)
# A tibble: 1 x 3
# Groups:   id [1]
  id    cat     val
  <fct> <fct> <int>
1 a     s1        2

big_out_to_show_error
# A tibble: 2 x 2
  cat     val
  <fct> <int>
1 s2        1
2 s1        2

如何管理group_by函数以仍然在切片中抛出分组变量,以便我的函数可以使用group_modify

作为旁注,我真的在尝试理解和修复这种dplyr group_by 行为。我已经知道基本的 R 方法:

split(dd, dd$id) %>% 
  lapply(simple_fun) %>% 
  do.call("rbind", .)
  id cat val
a  a  s1 2.0
b  b  s1 3.5
c  c  s2 6.5

谢谢

标签: rgroup-bydplyr

解决方案


group_modify()为每个组创建两个对象 - 一个包含子集数据的小标题,以及一个包含组信息的单独的单行小标题。

因为返回数据时会自动恢复组信息group_modify(),所以一般不需要将这些信息保留在子集数据中,默认情况下是去掉的。但是,您可以使用.keep参数来保留它,但是如果在您的函数返回数据时存在组变量,这将导致错误。

因此,您可以通过使用参数来修复您的函数.keep,然后在返回数据之前删除分组变量:

simple_fun <- function(slice, key){

  temp1 <- arrange(slice, cat)
  
  temp2 <- temp1 %>% 
    filter(cat==temp1$cat[1])
  
  if(nrow(temp2)>1) {
    temp2 <- temp2 %>% 
      group_by(id, cat) %>% 
      summarise(val = mean(val), .groups = "drop")
  }   
  temp2 %>%
    select(-id)      
}

dd %>% 
  group_by(id) %>%
  group_modify(simple_fun, .keep = TRUE)

# A tibble: 3 x 3
# Groups:   id [3]
  id    cat     val
  <chr> <chr> <dbl>
1 a     s1      2  
2 b     s1      3.5
3 c     s2      6.5

您还可以简化函数以完全回避这个问题:

simple_fun2 <- function(slice, key){

slice %>% 
    slice_min(cat, 1) %>%
    summarise(cat = unique(cat),
              val = mean(val))
}

dd %>% 
  group_by(id) %>%
  group_modify(simple_fun2)

# A tibble: 3 x 3
# Groups:   id [3]
  id    cat     val
  <chr> <chr> <dbl>
1 a     s1      2  
2 b     s1      3.5
3 c     s2      6.5

推荐阅读