选择除一列外所有重复的行

6

我希望在数据集中找到所有除一列外,其他所有列的值都匹配的行。经过一番折腾,试图让duplicated()返回所有重复行的实例(而不仅仅是第一个实例),我想出了一种方法(如下所示)。

例如,我想找到Iris数据集中除Petal.Width外所有相等的行。

require(tidyverse)
x = iris%>%select(-Petal.Width)
dups = x[x%>%duplicated(),]
answer =  iris%>%semi_join(dups)

> answer 
   Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
1           5.1         3.5          1.4         0.2    setosa
2           4.9         3.1          1.5         0.1    setosa
3           4.8         3.0          1.4         0.1    setosa
4           5.1         3.5          1.4         0.3    setosa
5           4.9         3.1          1.5         0.2    setosa
6           4.8         3.0          1.4         0.3    setosa
7           5.8         2.7          5.1         1.9 virginica
8           6.7         3.3          5.7         2.1 virginica
9           6.4         2.8          5.6         2.1 virginica
10          6.4         2.8          5.6         2.2 virginica
11          5.8         2.7          5.1         1.9 virginica
12          6.7         3.3          5.7         2.5 virginica

你看到了,这个方案可行,但我相信很多人都需要这个功能,而且我不知道是否有一种更简单的方法来实现这个功能,或者一种更加整洁的方式。有什么建议吗?

另一种方法是参考至少两个其他帖子的做法,应用于这种情况:

answer = iris[duplicated(iris[-4]) | duplicated(iris[-4], fromLast = TRUE),]

但这似乎只是另一种绕过单个函数的方法。这两种方法需要相同的时间(在我的系统上为0.08秒)。没有更简洁/更快的方法吗?

例如,像这样的内容: iris%>%duplicates(all = TRUE,ignore = Petal.Width)


1
请使用可重现的数据和期望的结果进行一个小例子。谢谢。 - Andre Elrico
3
基本上这是iris[duplicated(iris[-4]) | duplicated(iris[-4], fromLast = TRUE),] - David Arenburg
是的,我之前看过那篇帖子,它与David的答案非常相似,但它似乎不是一个优雅的解决方案,而我认为这应该是一个相对常见的任务...难道没有一个单一的函数可以返回除了一个列以外的所有重复项吗?或者甚至只返回所有重复的条目? - Paul Raftery
关于“_是的,我之前看过那篇帖子_”,你应该在你的帖子中包含这样的信息,并清楚地解释为什么之前的答案不符合你的需求。祝好! - Henrik
我添加了一个dplyr解决方案,需要写入更少的字符,可能比基本的R解决方案更容易阅读。但是,我没有进行微基准测试来测试速度。 - Lennyy
3个回答

5
iris[duplicated(iris[,-4]) | duplicated(iris[,-4], fromLast = TRUE),]

对于重复行(不考虑第四列),duplicated(iris[,-4]) 返回重复数据集的第二行,即18、35、46、133、143和145行,duplicated(iris[,-4], fromLast = TRUE) 返回每个重复集合的第一行,即1、10、13、102、125和129行。通过添加 |符号可以得到12个TRUE,因此它返回了预期的输出结果。

或者使用dplyr:基本上,您可以按照除Petal.Width外的所有变量进行分组,计算其出现次数,并筛选出出现次数大于一的变量。

library(dplyr)
iris %>% 
  group_by_at(vars(-Petal.Width)) %>% 
  filter(n() > 1)

   Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
          <dbl>       <dbl>        <dbl>       <dbl>    <fctr>
 1          5.1         3.5          1.4         0.2    setosa
 2          4.9         3.1          1.5         0.1    setosa
 3          4.8         3.0          1.4         0.1    setosa
 4          5.1         3.5          1.4         0.3    setosa
 5          4.9         3.1          1.5         0.2    setosa
 6          4.8         3.0          1.4         0.3    setosa
 7          5.8         2.7          5.1         1.9 virginica
 8          6.7         3.3          5.7         2.1 virginica
 9          6.4         2.8          5.6         2.1 virginica
10          6.4         2.8          5.6         2.2 virginica
11          5.8         2.7          5.1         1.9 virginica
12          6.7         3.3          5.7         2.5 virginica

请提供一些解释而不仅仅是代码。谢谢! - petezurich
1
我为我的基本R解决方案添加了一些说明,并且现在还添加了一个使用dplyr的方法。 - Lennyy
1
在我的数据集(约600K行,5列)中,仅使用duplicated()的第一种选项是最快的,速度为0.14秒。然而,第二种使用dplyr的“整洁”解决方案对我来说更直观。尽管比第一种选项慢(0.52秒),但它更加易于理解。不出所料,我最初提出的解决方案是最慢的(8.6秒)。谢谢! - Paul Raftery

1

我认为Janitor可以直接完成这项任务。

library(janitor)

get_dupes(iris, !Petal.Width)

# get_dupes(iris, !Petal.Width)[,names(iris)] # alternative: no count column

   Sepal.Length Sepal.Width Petal.Length   Species dupe_count Petal.Width
1           4.8         3.0          1.4    setosa          2         0.1
2           4.8         3.0          1.4    setosa          2         0.3
3           4.9         3.1          1.5    setosa          2         0.1
4           4.9         3.1          1.5    setosa          2         0.2
5           5.1         3.5          1.4    setosa          2         0.2
6           5.1         3.5          1.4    setosa          2         0.3
7           5.8         2.7          5.1 virginica          2         1.9
8           5.8         2.7          5.1 virginica          2         1.9
9           6.4         2.8          5.6 virginica          2         2.1
10          6.4         2.8          5.6 virginica          2         2.2
11          6.7         3.3          5.7 virginica          2         2.1
12          6.7         3.3          5.7 virginica          2         2.5


0

我研究了duplicated的源代码,但很想看看是否有人能找到更快的方法。这可能涉及到使用Rcpp或类似的东西。在我的机器上,基本方法是最快的,但你原来的方法实际上比最易读的dplyr方法更好。我认为为自己的目的包装这样一个函数应该足够了,因为你的运行时间似乎并不过长,如果这是主要问题,你可以简单地使用iris %>% opts("Petal.Width")进行管道操作。

library(tidyverse)
library(microbenchmark)

opt1 <- function(df, ignore) {
  ignore = enquo(ignore)
  x <- df %>% select(-!!ignore)
  dups <- x[x %>% duplicated(), ]
  answer <- iris %>% semi_join(dups)
}

opt2 <- function(df, ignore) {
  index <-  which(colnames(df) == ignore)
  df[duplicated(df[-index]) | duplicated(df[-index], fromLast = TRUE), ]
}

opt3 <- function(df, ignore){
  ignore <-  enquo(ignore)
  df %>%
    group_by_at(vars(-!!ignore)) %>%
    filter(n() > 1)
}


microbenchmark(
  opt1 = suppressMessages(opt1(iris, Petal.Width)),
  opt2 = opt2(iris, "Petal.Width"),
  opt3 = opt3(iris, Petal.Width)
)
#> Unit: milliseconds
#>  expr      min       lq     mean   median       uq       max neval cld
#>  opt1 3.427753 4.024185 4.851445 4.464072 5.069216 12.800890   100  b 
#>  opt2 1.712975 1.908130 2.403859 2.133632 2.542871  7.557102   100 a  
#>  opt3 6.604614 7.334304 8.461424 7.920369 8.919128 24.255678   100   c

这段代码是由reprex package (v0.2.0)在2018年7月12日创建的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接