使用dplyr / tidyverse根据多列删除重复行?

6

我想使用dplyr/tidyverse基于>1列删除重复行。

示例

library(dplyr)

df <- data.frame(a=c(1,1,1,2,2,2), b=c(1,2,1,2,1,2), stringsAsFactors = F)

我原本认为这会返回第3行和第6行,但实际上它并没有返回任何行。

df %>% filter(duplicated(a, b))
# [1] a b
# <0 rows> (or 0-length row.names)

相反,我认为这将返回第1、2、4和5行,但它返回所有行。
df %>% filter(!duplicated(a, b))
#   a b
# 1 1 1
# 2 1 2
# 3 1 1
# 4 2 2
# 5 2 1
# 6 2 2

我漏掉了什么?

2个回答

9

duplicated 期望操作 "向量、数据框或数组" (但不是两个向量 … 它仅在第一个参数中查找重复项)。

df %>%
  filter(duplicated(.))
#   a b
# 1 1 1
# 2 2 2

df %>%
  filter(!duplicated(.))
#   a b
# 1 1 1
# 2 1 2
# 3 2 2
# 4 2 1

如果你想引用特定的列子集,那么请使用cbind:
df %>%
  filter(duplicated(cbind(a, b)))

作为一个附注,dplyr 的动词可以用 distinct
df %>%
  distinct(a, b, .keep_all = TRUE)
#   a b
# 1 1 1
# 2 1 2
# 3 2 2
# 4 2 1

尽管我不知道这个函数是否有反函数。

1
非常好。我也认为cbind方法在一些罕见的情况下会失败,比如df <- data.frame(a=c(11, 1), b=c(1,12), stringsAsFactors = F); df %>% filter(duplicated(cbind(a, b))),但它并没有失败。看起来是一个很好的解决方案。 - stevec
自从您的编辑之后,我将使用distinct,因为这是最“本地”的整洁方法。很想知道是否有它的反向操作,只是为了学习和了解。 - stevec
1
也许是 janitor::get_dupes - Ian Campbell
我还没有使用那个包的需求。 - r2evans
1
不要问我为什么现在才想起来(两年后),但 cbind(a, b) 可能会导致意外匹配(例如 data.frame(a=c("xy", "x"), b=c("x", "yx")))。因此,我认为 distinct 是更好的选择。顺便说一句,恭喜你达到了10万个。 - stevec
我不明白在你的新样本框架中cbind(a,b)怎么会出错,因为“xy”和“x”(第1行)从未真正合并。也就是说,我认为你是在说“xyx”可能是模棱两可的;但这种错误不应该发生。它对你来说表现不正确吗?(别太早恭喜我,我还差6分;-) - r2evans

4
使用unique在数据框中可以得到唯一的行。
unique(df)

#  a b
#1 1 1
#2 1 2
#4 2 2
#5 2 1

另一种整洁的方法是选择每个组的第一行。

library(dplyr)

df %>% group_by(a, b) %>% slice(1L) 
#If only 2 columns
#df %>% group_by_all() %>%slice(1L)

#      a     b
#  <dbl> <dbl>
#1     1     1
#2     1     2
#3     2     1
#4     2     2

逆操作将是:
df %>%  group_by(a, b) %>% filter(n() > 1) %>%  distinct()
#If only 2 columns
#df %>%  group_by_all() %>% filter(n() > 1) %>%  distinct()

#     a     b
#  <dbl> <dbl>
#1     1     1
#2     2     2

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