【问题标题】:Want to remove duplicated rows unless NA value exists in columns要删除重复的行,除非列中存在 NA 值
【发布时间】:2019-09-16 03:22:06
【问题描述】:

我有一个包含 4 列的数据表:ID、名称、Rate1、Rate2。

我想删除 ID、Rate1 和 Rate 2 相同的重复项,如果它们都是 NA,我想保留这两行。

基本上,我想有条件地删除重复项,但前提是条件!= NA。

例如,我想要这样:

ID   Name   Rate1    Rate2
1    Xyz    1        2
1    Abc    1        2
2    Def    NA       NA
2    Lmn    NA       NA
3    Hij    3        5
3    Qrs    3        7

变成这样:

ID   Name   Rate1    Rate2
1    Xyz    1        2
2    Def    NA       NA
2    Lmn    NA       NA
3    Hij    3        5
3    Qrs    3        7

提前致谢!

编辑:我知道可以只获取 Rates 为 NA 的数据表的一个子集,然后删除剩余部分的重复项,然后重新添加 NA 行 - 但是,我宁愿避免这种策略。这是因为实际上我想连续执行相当多的速率对。

EDIT2:为清楚起见,在示例中添加了更多行。

【问题讨论】:

  • 如何分两步处理:首先找到 rate1 和 rate2 不为 NA 的唯一值,然后添加它们都为 NA 的所有行

标签: r duplicates data.table conditional-statements distinct


【解决方案1】:

base R 选项是在没有“名称”列即列索引 2 的数据集子集上使用 duplicated 来创建逻辑向量,取反(! - TRUE 变为 FALSE,反之亦然),这样TRUE 将是非重复行。除此之外,在逻辑矩阵(is.na(df1[3:4]) - Rate 列)上创建另一个条件 rowSums 以获得所有 NA 的行 - 这里我们将其与 2 进行比较 - 即数据集中的 Rate 列数)。这两个条件都由| 连接以创建预期的逻辑索引

i1 <- !duplicated(df1[-2])| rowSums(is.na(df1[3:4])) == 2
df1[i1,]
#    ID Name Rate1 Rate2
#1  1  Xyz     1     2
#3  2  Def    NA    NA
#4  2  Lmn    NA    NA

或与Reduce 来自base R

df1[Reduce(`&`, lapply(df1[3:4], is.na)) | !duplicated(df1[-2]), ]

将其包装在一个函数中

f1 <- function(dat, i, method ) {     
        
         nm1 <- grep("^Rate", colnames(dat), value = TRUE)    
         i1 <- !duplicated(dat[-i])  
         i2 <-  switch(method, 
           "rowSums" = rowSums(is.na(dat[nm1])) == length(nm1),
           "Reduce" = Reduce(`&`, lapply(dat[nm1], is.na))
         
         )   
         i3 <- i1|i2
         dat[i3,]
     }    

-测试

f1(df1, 2, "rowSums")
#  ID Name Rate1 Rate2
#1  1  Xyz     1     2
#3  2  Def    NA    NA
#4  2  Lmn    NA    NA

f1(df1, 2, "Reduce")
#  ID Name Rate1 Rate2
#1  1  Xyz     1     2
#3  2  Def    NA    NA
#4  2  Lmn    NA    NA

f1(df2, 2, "rowSums")
#  ID Name Rate1 Rate2
#1  1  Xyz     1     2
#3  2  Def    NA    NA
#4  2  Lmn    NA    NA
#5  3  Hij     3     5
#6  3  Qrs     3     7

f1(df2, 2, "Reduce")
#  ID Name Rate1 Rate2
#1  1  Xyz     1     2
#3  2  Def    NA    NA
#4  2  Lmn    NA    NA
#5  3  Hij     3     5
#6  3  Qrs     3     7

如果有多个 'Rate' 列(比如 100 或更多 - 第一个解决方案中唯一要更改的是 2 应更改为 'Rate' 列的数量)


或使用tidyverse

library(tidyvesrse)
df1 %>%
    group_by(ID) %>%
    filter_at(vars(Rate1, Rate2), any_vars(!duplicated(.)|is.na(.)))
# A tibble: 3 x 4
# Groups:   ID [2]
#     ID Name  Rate1 Rate2
#  <int> <chr> <int> <int>
#1     1 Xyz       1     2
#2     2 Def      NA    NA
#3     2 Lmn      NA    NA



df2 %>% 
     group_by(ID) %>%
     filter_at(vars(Rate1, Rate2), any_vars(!duplicated(.)|is.na(.)))
# A tibble: 5 x 4
# Groups:   ID [3]
#     ID Name  Rate1 Rate2
#  <int> <chr> <int> <int>
#1     1 Xyz       1     2
#2     2 Def      NA    NA
#3     2 Lmn      NA    NA
#4     3 Hij       3     5
#5     3 Qrs       3     7

正如 @Paul 在 cmets 中提到的,更新后的 tidyverse 语法截至 2021 年 11 月 4 日是

library(dplyr)
df2 %>% 
     group_by(ID) %>%
     filter(if_any(cRate1, Rate2), ~ !duplicated(.)|is.na(.)))

数据

df1 <- structure(list(ID = c(1L, 1L, 2L, 2L), Name = c("Xyz", "Abc", 
"Def", "Lmn"), Rate1 = c(1L, 1L, NA, NA), Rate2 = c(2L, 2L, NA, 
 NA)), class = "data.frame", row.names = c(NA, -4L))

df2 <- structure(list(ID = c(1L, 1L, 2L, 2L, 3L, 3L), Name = c("Xyz", 
 "Abc", "Def", "Lmn", "Hij", "Qrs"), Rate1 = c(1L, 1L, NA, NA, 
 3L, 3L), Rate2 = c(2L, 2L, NA, NA, 5L, 7L)), class = "data.frame", 
 row.names = c(NA, -6L))

【讨论】:

  • 太棒了,谢谢。我最喜欢 tidyverse 方法。
  • 由于 filter_at() 现在已被取代,我认为 tidyverse 语法将是 filter(across(c(Rate1, Rate2), ~ !duplicated(.) | is.na(.)))
猜你喜欢
  • 2018-10-21
  • 2018-07-27
  • 2014-11-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-10
  • 2016-07-10
  • 2021-05-28
相关资源
最近更新 更多