在R中,从一个数据框中删除另一个数据框中完全相同的行和重复的行的频率。

6

考虑以下两个数据框:

a1 <- data.frame(A = c(1:5, 2, 4, 2), B = letters[c(1:5, 2, 4, 2)])
a2 <- data.frame(A = c(1:3,2), B = letters[c(1:3,2)])

我希望删除在a2中出现的a1的确切行,以便结果如下:
A  B
4  d
5  e
4  d
2  b

注意,a1单元格中带有2 b的一行将保留在最终结果中。目前,我使用循环语句,在我的数据框中有许多变量和数千行时,速度变得极慢。是否有任何内置函数可获得此结果?

你的输出是否正确并不清楚,“2b”在两个中都有,我有什么遗漏吗? - steveb
@steveb 2ba1 中出现了两次,因此只有一个被取消,另一个保留在输出中。 - Ronak Shah
啊,这就是我匆忙阅读的后果。 - steveb
2
令人惊讶的难以简化,好问题。 - Tunn
我认为我的答案符合您的要求。同意,这很难简化。 - steveb
4个回答

3

这个想法是为每个文件添加一个重复计数器,这样你就可以为每个行的出现获得唯一匹配。数据表很好用,因为它很容易计算重复项(使用.N),并且还提供了必要的函数(fsetdiff)进行集合操作。

library(data.table)

a1 <- data.table(A = c(1:5, 2, 4, 2), B = letters[c(1:5, 2, 4, 2)])
a2 <- data.table(A = c(1:3,2), B = letters[c(1:3,2)])

# add counter for duplicates
a1[, i := 1:.N, .(A,B)]
a2[, i := 1:.N, .(A,B)]

# setdiff gets the exception
# "all = T" allows duplicate rows to be returned
fsetdiff(a1, a2, all = T)

#    A B i
# 1: 4 d 1
# 2: 5 e 1
# 3: 4 d 2
# 4: 2 b 3

2
您可以使用dplyr来实现这一点。我设置了stringsAsFactors = FALSE,以消除有关因子不匹配的警告。
library(dplyr)

a1 <- data.frame(A = c(1:5, 2, 4, 2), B = letters[c(1:5, 2, 4, 2)], stringsAsFactors = FALSE)
a2 <- data.frame(A = c(1:3,2), B = letters[c(1:3,2)], stringsAsFactors = FALSE)

## Make temp variables to join on then delete later.
# Create a row number
a1_tmp <- 
    a1 %>%
    group_by(A, B) %>%
    mutate(tmp_id = row_number()) %>%
    ungroup()
# Create a count
a2_tmp <-
    a2 %>%
     group_by(A, B) %>%
     summarise(count = n()) %>%
     ungroup()

## Keep all that have no entry int a2 or the id > the count (i.e. used up a2 entries).
left_join(a1_tmp, a2_tmp, by = c('A', 'B')) %>%
    ungroup() %>% filter(is.na(count) | tmp_id > count) %>%
    select(-tmp_id, -count)

## # A tibble: 4 x 2
##       A     B
##   <dbl> <chr>
## 1     4     d
## 2     5     e
## 3     4     d
## 4     2     b

编辑

这里有一个更短的类似解决方案。它做了以下几件事:(1)添加一列行号以连接两个data.frame项目;(2)在a2(第二个data.frame)中添加一个临时列,该列将在与a1的连接中显示为空(即表示它是唯一的a1)。

library(dplyr)

left_join(a1 %>% group_by(A,B) %>% mutate(rn = row_number())             %>% ungroup(),
          a2 %>% group_by(A,B) %>% mutate(rn = row_number(), tmpcol = 0) %>% ungroup(),
          by = c('A', 'B', 'rn')) %>%
filter(is.na(tmpcol)) %>%
select(-tmpcol, -rn)

## # A tibble: 4 x 2
##       A     B
##   <dbl> <chr>
## 1     4     d
## 2     5     e
## 3     4     d
## 4     2     b

我认为这个解决方案比第一个简单一些(也许是非常少)。

是的,Steveb,谢谢您。 - RBL
太棒了!非常简洁!感谢您! - RBL

1

我猜这与DWal's solution类似,但使用的是基本R语言。

a1_temp = Reduce(paste, a1)
a1_temp = paste(a1_temp, ave(seq_along(a1_temp), a1_temp, FUN = seq_along))

a2_temp = Reduce(paste, a2)
a2_temp = paste(a2_temp, ave(seq_along(a2_temp), a2_temp, FUN = seq_along))

a1[!a1_temp %in% a2_temp,]
#  A B
#4 4 d
#5 5 e
#7 4 d
#8 2 b

1
这是另一种使用dplyr的解决方案:
library(dplyr)
a1 %>%
  arrange(A) %>%
  group_by(A) %>%
  filter(!(paste0(1:n(), A, B) %in% with(arrange(a2, A), paste0(1:n(), A, B))))

结果:

# A tibble: 4 x 2
# Groups:   A [3]
      A      B
  <dbl> <fctr>
1     2      b
2     4      d
3     4      d
4     5      e

这种过滤方式避免了在最终输出中需要后续删除的额外无用列。此方法还会对输出进行排序。不确定是否符合您的要求。

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