在 R 中使用 dplyr 在 group_by 之后应用自定义函数

Posted

技术标签:

【中文标题】在 R 中使用 dplyr 在 group_by 之后应用自定义函数【英文标题】:Apply a custom function after group_by using dplyr in R 【发布时间】:2019-12-06 09:39:13 【问题描述】:

如何在 group_by 之后使用 dplyr 应用函数来删除具有 2 个或更多连续 NA 的组?我编写了一个函数,无论数据框中的列是否有 2 个或更多 NA,它都会输出 True 或 False:

# function for determining if ts contains consecutive NAs 
is.na.contiguous <- function(df, consecutive) 
  na.rle <- rle(is.na(df$b))
  na.rle$values <- na.rle$values & na.rle$lengths >= consecutive
  any(na.rle$values)


# example df
d = structure(list(a = c(1, 2, 3, 4, 5, 6, 7, 8), b = c(1, 2, 2, 
+ NA, NA, 2, NA, 2), c = c(1, 1, 1, 2, 2, 2, 3, 3)), class = "data.frame", row.names = c(NA, 
+ -8L))

head(d)
  a  b c
1 1  1 1
2 2  2 1
3 3  2 1
4 4 NA 2
5 5 NA 2
6 6  2 2
7 7 NA 3
8 8  2 3

# test function
is.na.contiguous(d,2)
TRUE # column b has 2 consecutive NAs
is.na.contiguous(d,3)
FALSE # column b does not have 3 consecutive NAs

现在如何将此函数应用于数据框中的每个组?以下是我尝试过的:

d %>% group_by(c) %>% mutate(consecNA = is.na.contiguous(.,2)) %>% as.data.frame()

  a  b c consecNA
1 1  1 1     TRUE
2 2  2 1     TRUE
3 3  2 1     TRUE
4 4 NA 2     TRUE
5 5 NA 2     TRUE
6 6  2 2     TRUE
7 7 NA 3     TRUE
8 8  2 3     TRUE

我做错了什么?

【问题讨论】:

添加一列,d %&gt;% group_by(c) %&gt;% mutate(consecNA = any(is.na(b) &amp; lag(is.na(b), default = FALSE)));删除组,d %&gt;% group_by(c) %&gt;% filter(!any(is.na(b) &amp; lag(is.na(b), default = FALSE))) @TYL 你能显示预期的输出吗?是一列逻辑向量还是你要过滤 我的最终目标是过滤,但我似乎找不到方法。所以我的想法是改变一列逻辑向量,然后根据该列进行过滤。 【参考方案1】:

一个选项是在逻辑向量 (is.na(b)) 上使用来自 data.tablerleid,并使用它来对行数大于或等于 2 的组进行子集化,如果 all 元素是NA

library(data.table)
i1 <- setDT(d)[, .I[!(.N >=2 & all(is.na(b)))], rleid(is.na(b))]$V1
d[i1]
#.  a  b c
#1: 1  1 1
#2: 2  2 1
#3: 3  2 1
#4: 6  2 2
#5: 7 NA 3
#6: 8  2 3

或者如果我们还需要按 'c' 分组

setDT(d)[d[, .I[sum(is.na(b)) <2], .(grp = rleid(is.na(b)), c)]$V1]

tidyverse

library(dplyr)
d %>%
   group_by(grp = rleid(is.na(b))) %>%
   filter(!(n() >=2 & all(is.na(b))))
# A tibble: 6 x 4
# Groups:   grp [4]
#      a     b     c   grp
#  <dbl> <dbl> <dbl> <int>
#1     1     1     1     1
#2     2     2     1     1
#3     3     2     1     1
#4     6     2     2     3
#5     7    NA     3     4
#6     8     2     3     5

或者另一种选择是获取逻辑向量的sum并检查它是否小于2

d %>%
    group_by(c, grp = rleid(is.na(b))) %>%
    filter(sum(is.na(b))<2)

如果我们使用 OP 中的函数

is.na.contiguous <- function(x, consecutive) 
     na.rle <- rle(is.na(x))
      with(na.rle, any(values & na.rle$lengths >= consecutive))

      

d %>%
   group_by(c) %>%
   mutate(consecNA = is.na.contiguous(b, 2))
# A tibble: 8 x 4
# Groups:   c [3]
#      a     b     c consecNA
#  <dbl> <dbl> <dbl> <lgl>   
#1     1     1     1 FALSE   
#2     2     2     1 FALSE   
#3     3     2     1 FALSE   
#4     4    NA     2 TRUE    
#5     5    NA     2 TRUE    
#6     6     2     2 TRUE    
#7     7    NA     3 FALSE   
#8     8     2     3 FALSE   

【讨论】:

【参考方案2】:

与其将整个数据框传递给is.na.contiguous,不如只传递列值,然后通过组应用它会很简单,如果您想对某些不同的列执行相同的操作,它也会变得灵活。

is.na.contiguous <- function(x, consecutive) 
   na.rle <- rle(is.na(x))
   na.rle$values <- na.rle$values & na.rle$lengths >= consecutive
   any(na.rle$values)


library(dplyr)
d %>%
  group_by(c) %>%
  filter(!is.na.contiguous(b, 2))

#      a     b     c
#  <dbl> <dbl> <dbl>
#1     1     1     1
#2     2     2     1
#3     3     2     1
#4     7    NA     3
#5     8     2     3

【讨论】:

以上是关于在 R 中使用 dplyr 在 group_by 之后应用自定义函数的主要内容,如果未能解决你的问题,请参考以下文章

R语言dplyr包使用dplyr函数使用group_by函数summarise函数和mutate函数计算分组占比实战

如何使用 R 和 dplyr 中连续的元素执行 group_by

R语言dplyr包使用group_by函数和summarise函数构建频率表实战

R使用dplyr group_by / sum for循环,作为连接列表输出

R语言dplyr包使用group_by函数arrange函数和filter函数获取每个分组的第一个第N个最后一个记录实战

使用 dplyr group_by 模拟 split():返回数据帧列表