删除任何列包含 NA 的所有数据框组

问题描述 投票:0回答:5

样本数据,

df <- data.frame(ID=c(1,1,1,2,2,2,3,3,3), v1=c(NA,2,3,1,2,3,1,2,3), v2=c(4,5,6,4,5,6,4,NA,6))

看起来,

  ID v1 v2
1  1 NA  4
2  1  2  5
3  1  3  6
4  2  1  4
5  2  2  5
6  2  3  6
7  3  1  4
8  3  2 NA
9  3  3  6

我想获得,

  ID v1 v2
4  2  1  4
5  2  2  5
6  2  3  6

我试过了,

library(dplyr)

df2 <- df %>%
  group_by(ID) %>%
  filter(!anyNA(c(v1,v2)))

df2

这会产生预期的结果,但是:

  • 我有70多个专栏,一定有比写c(v1,v2,v3,v4,v5...)更好的方法
  • 创建第二个数据框(df2 <- df) feels messy, is there a way to directly change my initial dataframe?
r dataframe dplyr
5个回答
4
投票

您可以将

rowSums
用于
is.na(df[-1])
,将
ave
any
一起使用并按
df$ID
分组,并使用
FALSE
取行。你可以覆盖
df
.

df <- df[!ave(rowSums(is.na(df[-1])) > 0L, df$ID, FUN=any), ]

df
#  ID v1 v2
#4  2  1  4
#5  2  2  5
#6  2  3  6

基准

library(tidyverse)
library(data.table)

df <- data.frame(ID=c(1,1,1,2,2,2,3,3,3), v1=c(NA,2,3,1,2,3,1,2,3), v2=c(4,5,6,4,5,6,4,NA,6))
dt <- copy(df)

bench::mark(check = FALSE,
ave = df[!ave(rowSums(is.na(df[-1])) > 0L, df$ID, FUN=any), ],
"Chris Ruehlemann" = {df %>%
  group_by(ID) %>%
    filter(if_all(starts_with("v"), ~!anyNA(.)))},
"TarJae1" = {df %>% 
  group_by(ID) %>% 
  mutate(row=row_number()) %>% 
  pivot_longer(-c(ID, row)) %>%
  filter(!any(is.na(value))) %>% 
  pivot_wider(names_from=name, values_from = value) %>% 
    select(-row)},
"TarJae2" = {df %>% 
  anti_join(df %>% 
              group_by(ID) %>% 
            filter(if_any(everything(), ~is.na(.))) , by = "ID")},
"TarJae3" = {df %>% 
  filter(!if_any(everything(), ~anyNA(.)), .by=ID) },
"ThomasIsCoding" = setDT(dt)[ID == dt[, !anyNA(.SD), ID][V1 == TRUE, ID]]
)

结果

  expression            min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc
  <bch:expr>       <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>
1 ave              114.16µs 120.22µs    8185.   105.02KB     44.8  3653    20
2 Chris Ruehlemann      3ms   3.06ms     319.     3.36MB     36.8   130    15
3 TarJae1           13.06ms  13.21ms      74.5    1.26MB     58.8    19    15
4 TarJae2            3.56ms   3.62ms     271.   413.95KB     38.0   107    15
5 TarJae3            3.39ms   3.46ms     284.    60.04KB     40.5   112    16
6 ThomasIsCoding     1.41ms   1.53ms     650.     2.83MB     15.1   258     6

在这种情况下

ave
比第二个快大约12倍并且
TarJae3
分配最低的额外内存。


3
投票

这是一个

tidyverse
解决方案:

df %>%
  group_by(ID) %>%
  filter(if_all(starts_with("v"), ~!anyNA(.)))

在这里,我们使用

starts_with
通过它们都以“v”开头的字母来处理有问题的列。有多个
dplyr
函数可以在不对名称进行硬编码的情况下对列进行寻址;查看
ends_with
contains
和(我最喜欢的)
matches
,它对正则表达式敏感


2
投票

更新2

受@Chris Ruehlemann 使用

anyNA
的启发,我找到了这个最终版本:

library(dplyr)

df %>% 
  filter(!if_any(everything(), ~anyNA(.)), .by=ID) 

  ID v1 v2
1  2  1  4
2  2  2  5
3  2  3  6

Update1改进代码: 一个

dplyr
唯一的解决方案:

df %>% 
  anti_join(df %>% 
              group_by(ID) %>% 
              filter(if_any(everything(), ~is.na(.))) , by = "ID")

  ID v1 v2
1  2  1  4
2  2  2  5
3  2  3  6

第一个答案: 这是

tidyverse
方法:

library(dplyr)
library(tidyr)

df %>% 
  group_by(ID) %>% 
  mutate(row=row_number()) %>% 
  pivot_longer(-c(ID, row)) %>%
  filter(!any(is.na(value))) %>% 
  pivot_wider(names_from=name, values_from = value) %>% 
  select(-row)
     ID    v1    v2
  <dbl> <dbl> <dbl>
1     2     1     4
2     2     2     5
3     2     3     6

2
投票

这里有一些

data.table
选项:

> setDT(df)[ID == df[, !anyNA(.SD), ID][, ID[V1]]
   ID v1 v2
1:  2  1  4
2:  2  2  5
3:  2  3  6

> rbindlist(Filter(Negate(anyNA), split(df, ~ID)))
   ID v1 v2
1:  2  1  4
2:  2  2  5
3:  2  3  6

0
投票

基本 R 选项

subset(df, ID %in% names(which(!sapply(split(df[-1], ~ ID), anyNA))))

-输出

  ID v1 v2
4  2  1  4
5  2  2  5
6  2  3  6
© www.soinside.com 2019 - 2024. All rights reserved.