Delete/overwrite 行部分匹配

Delete/overwrite rows by partial matching

我需要检查行是否部分重复以及 delete/overwrite 那些 2 列与 3 的不同行匹配的行值存在。一个问题是,“真正的”数据框包含几个列表列,这使得某些操作不可行。最好的情况是,如果可以找到匹配项的任何行都将独立于列号进行检查 - 这意味着只保留具有非 NA 值的列最多的行(在所有包含匹配列值的列中)。

   o1 o2 o3
1   1 NA NA
2   2 NA NA
3   3 NA NA
4   4 NA NA
5   6 NA NA
6   7 NA NA
7   5  9 NA # this row has only 2 values which match values from row 11 but the last value is na
8  10 NA NA
9  12 NA NA
10 13 NA NA
11  5  9 14 # this row has values in all 3 columns 
12 14 NA NA
13  8 11 15 # so does this row
14 16 NA NA
15 17 NA NA
16 18 NA NA
17 19 NA NA
18 20 NA NA

结果应该是相同的数据框 - 只是没有第 7 行或第 7 行被第 11 行覆盖。

这应该很容易做到,但出于某种原因我没有做到(除非有一个复杂的 for 循环,如果以后添加更多列,则很难概括)。有没有直接的方法来做到这一点?

以上 df 的输入:

structure(list(o1 = c(1L, 2L, 3L, 4L, 6L, 7L, 5L, 10L, 12L, 13L, 
5L, 14L, 8L, 16L, 17L, 18L, 19L, 20L), o2 = c(NA, NA, NA, NA, 
NA, NA, 9L, NA, NA, NA, 9L, NA, 11L, NA, NA, NA, NA, NA), o3 = c(NA, 
NA, NA, NA, NA, NA, NA, NA, NA, NA, 14L, NA, 15L, NA, NA, NA, 
NA, NA)), row.names = c(NA, -18L), class = "data.frame")

如果已经有类似的答案,请告诉我。

检测重复的部分解决方案,剩下的就是指定要删除哪些行,运行超时了。我继续“复制”了几行。

df=read.table(text="
   o1 o2 o3
1   1 NA NA
2   2 NA NA
3   3 NA NA
4   4 NA NA
5   6 NA NA
6   7 NA NA
7   5  9 NA
8  10 NA NA
9  12 NA NA
10 13 NA NA
11  5  9 14
12 14 NA NA
13  8 11 15
14 16 NA NA
15 7 1 2
16 18 NA NA
17 7 1 3
18 20 NA NA",h=T)

主要技巧是计算距离矩阵并检查哪些行的距离为零,因为 dist 会自动估计成对距离,删除缺失值。

tmp=as.matrix(dist(df))
diag(tmp)=NA
tmp[lower.tri(tmp)]=NA

tod=data.frame(which(tmp==0,arr.ind=T))

导致

     row col
X7     7  11
X6     6  15
X6.1   6  17

我想到了使用 dplyr:

library(dplyr)

df %>% 
  mutate(rn = row_number(),
         count_na = rowSums(across(o1:o3, is.na))) %>% 
  group_by(o1, o2) %>% 
  slice_min(count_na) %>% 
  arrange(rn) %>% 
  ungroup() %>% 
  select(o1:o3)

这个returns

# A tibble: 17 x 3
      o1    o2    o3
   <int> <int> <int>
 1     1    NA    NA
 2     2    NA    NA
 3     3    NA    NA
 4     4    NA    NA
 5     6    NA    NA
 6     7    NA    NA
 7    10    NA    NA
 8    12    NA    NA
 9    13    NA    NA
10     5     9    14
11    14    NA    NA
12     8    11    15
13    16    NA    NA
14    17    NA    NA
15    18    NA    NA
16    19    NA    NA
17    20    NA    NA

此解决方案基于以下想法:

  • 对于每一行,我们计算该行中 NA 的数量。
  • 我们对 o1o2 进行分组以创建属于同一组的数据。这是一个可能的缺陷:也许只按 o1 分组或进行其他分组可能是更好的方法。这取决于您的数据结构:1, <NA>, <NA> 应该被 1, 2, <NA> 覆盖吗?
  • 分组后,我们select行数最少的NA行。
  • 最后我们做一些清理:删除辅助列,排列数据并取消分组。

这是另一种考虑所有列的方法,应该适用于任意数量的列,无论它们的名称或位置如何

library(dplyr)
mydf <- structure(list(o1 = c(1L, 2L, 3L, 4L, 6L, 7L, 5L, 10L, 12L, 13L, 
                              5L, 14L, 8L, 16L, 17L, 18L, 19L, 20L), 
                       o2 = c(NA, NA, NA, NA, 
                              NA, NA, 9L, NA, NA, NA, 9L, NA, 11L, NA, NA, NA, NA, NA), 
                       o3 = c(NA, 
                              NA, NA, NA, NA, NA, NA, NA, NA, NA, 14L, NA, 15L, NA, NA, NA, 
                              NA, NA)), 
                  row.names = c(NA, -18L), 
                  class = "data.frame")

columns <- names(mydf)
dummy_cols <- paste0(columns, "_dummy")
mydf %>% 
  # duplicate the dataframe
  cbind(mydf %>% `names<-`(dummy_cols)) %>% 
  # arrange across all columns
  arrange(across(columns)) %>% 
  # fill NAs downwards
  tidyr::fill(dummy_cols, .direction = "down") %>% 
  # create a dummy ID 
  tidyr::unite(id_dummy, dummy_cols, sep = "") %>% 
  # group by the id 
  group_by(id_dummy) %>% 
  # get the first row of each
  filter(row_number()==1) %>% 
  ungroup() %>% 
  select(columns)

P.S。还将 1 - NA - NA 替换为 1 - 2 - NA 并将 1 - NA - NA 替换为 1 - NA - 3