【问题标题】:Remove *all* duplicate rows, unless there's a "similar" row删除 *all* 重复行,除非有“相似”行
【发布时间】:2019-10-10 11:42:13
【问题描述】:

我有以下data.table

library(data.table)
dt = data.table(c(1, 1, 1, 2, 2, 2, 2, 3, 4),
                c(4, 4, 4, 5, 5, 6, 7, 4, 5))
   V1 V2
1:  1  4
2:  1  4
3:  1  4
4:  2  5
5:  2  5
6:  2  6
7:  2  7
8:  3  4
9:  4  5

我想研究给定V1V2 的不同值。但是,如果给定 V1 的所有 V2 值都相同,那我就不感兴趣了,所以我想删除这些行。

看上面的例子,前三行完全相同(V1=1V2=4),所以我想删除它们。

但是,接下来的四行包括两个相同的行和其他带有不同 V2 的行。在这种情况下,我想显示给定V1 = 2V2 的三个可能值:(2, 5)(2, 6)(2, 7)

最后两行有唯一的V1: 属于“所有行完全相同”的类别,因此也应删除。

我能想到的最好的显示在this answer

dt[!duplicated(dt) & !duplicated(dt, fromLast = TRUE), ]
   V1 V2
1:  2  6
2:  2  7
3:  3  4
4:  4  5

这显然不令人满意:它删除了 (2,5) 对,因为它是重复的,它保留了 (3,4)(4,5) 对,因为它们是唯一的,因此不会被 duplicated() 标记标记.

另一种选择是简单地调用

unique(dt)
   V1 V2
1:  1  4
2:  2  5
3:  2  6
4:  2  7
5:  3  4
6:  4  5

但它保留了我想要删除的 (1,4)(3,4)(4,5) 对。

最后,我要找的结果是:

   V1 V2
1:  2  5
2:  2  6
3:  2  7

尽管任何其他格式也可以接受,例如:

   V1 V2.1 V2.2 V2.3
1:  2    5    6    7

(显示每个“有趣”V1V2 的可能值)

我不知道如何区分 (1,4) 案例(所有行都相同)和 (2,5) 案例(有一些重复,但还有其他行具有相同的 V1,所以我们必须删除重复的(2,5),但保留一份)。

至于唯一行,我写了一个非常丑陋的调用,但它只有在只有一个唯一行时才有效。如果有两个,如上例,则失败。

【问题讨论】:

  • 你的预期输出是什么
  • @akrun 预期的输出在最后两个代码块中给出。

标签: r data.table


【解决方案1】:

一个选项是按“V1”分组,获取唯一元素长度大于 1 的组的索引,然后采用 unique

unique(dt[dt[, .(i1 = .I[uniqueN(V2) > 1]), V1]$i1])
#   V1 V2
#1:  2  5
#2:  2  6
#3:  2  7

或者正如@r2evans 提到的那样

unique(dt[, .SD[(uniqueN(V2) > 1)], by = "V1"])

注意:OP 的数据集是 data.tabledata.table 方法是这样做的自然方式


如果我们需要一个tidyverse 选项,与上述data.table 选项相当的选项是

library(dplyr)
dt %>%
   group_by(V1) %>% 
   filter(n_distinct(V2) > 1) %>% 
   distinct()

【讨论】:

  • 我正要发帖unique(dt[,.SD[ (length(unique(V2)) > 1), ], by = "V1" ]) ...现在才知道uniqueN,谢谢:-)
  • 也许更清楚? unique(dt[, .SD[ (uniqueN(V2) > 1), ], by = "V1"])
  • @r2evans 这很干净,但我认为 .I 在我之前的基准实验中会更快
  • @r2evans 在this question 的一个相对较大的表上有一个基准测试。另一种选择(不需要更多)是unique(dt[, if(uniqueN(V2) > 1) .SD, by = "V1"])
  • @Wasabi 它的作用是 uniqueN(V2) > 1 为每组“V1”获取长度为 1 的逻辑向量 通过用 .I 包装,它给出了该 V1 的所有行的行索引.提取该行索引列$i1 并在i 中使用它来对行进行子集化,并使用unique 进行换行
【解决方案2】:

还有一个dplyr可能:

dt %>%
 group_by(V1) %>%
 filter(n_distinct(V2) != 1 & !duplicated(V2))

     V1    V2
  <dbl> <dbl>
1     2     5
2     2     6
3     2     7

或者:

dt %>%
 group_by(V1) %>%
 filter(n_distinct(V2) != 1) %>%
 group_by(V1, V2) %>%
 slice(1)

【讨论】:

    【解决方案3】:

    你的情况是R

    dt[ave(dt$V2,dt$V1,FUN=function(x) length(unique(x)))>1&!duplicated(dt)]
       V1 V2
    1:  2  5
    2:  2  6
    3:  2  7
    

    【讨论】:

    • 一个更清晰的小改动可以是dt[with(dt,ave(V2,V1,FUN=function(x) length(unique(x)))&gt;1&amp;!duplicated(dt))]
    • 我想知道的是:随着所有 base-R 代码高尔夫的进行,您在中间保留一个空格? ;-)
    • 他们必须为所有强制缩进保存额外的空格? (完全披露:我也使用 python,只是不那么熟练/发声。)
    • @WeNYoBen,我对这个的熊猫版本印象深刻:-P
    • @r2evans 保留空间是一种很好的编码行为,我应该加强它:-) , df[df.groupby('V1').V2.transform('nunique').gt(1)].drop_duplicates()
    【解决方案4】:

    使用 if 语句可以更简洁,并且可以说更 data.table'ly:

    dt[, if (uniqueN(V2) > 1) unique(V2), by = V1]
    
    #    V1 V1
    # 1:  2  5
    # 2:  2  6
    # 3:  2  7
    

    但无法正确获取列名...

    不太简洁的解决方案:

    dt[, .(V2 = if (uniqueN(V2) > 1) unique(V2) else numeric(0)), by = V1]
    
    dt[, .SD[if (uniqueN(V2) > 1) !duplicated(V2)], by = V1]
    
    #    V1 V2
    # 1:  2  5
    # 2:  2  6
    # 3:  2  7
    

    【讨论】:

      猜你喜欢
      • 2018-04-07
      • 1970-01-01
      • 2019-09-06
      • 2014-01-27
      • 1970-01-01
      • 1970-01-01
      • 2017-03-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多