【问题标题】:Delete/overwrite rows by partial matching通过部分匹配删除/覆盖行
【发布时间】:2021-12-12 16:34:16
【问题描述】:

我需要检查行是否部分重复并删除/覆盖 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")

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

【问题讨论】:

    标签: r matching


    【解决方案1】:

    我想过用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)
    

    返回

    # 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
    

    本方案基于以下思路:

    • 对于每一行,我们都会计算该行中NAs 的数量。
    • 我们将o1o2 分组以创建属于同一组的数据组。这是一个可能的缺陷:也许它是仅按o1 分组或进行其他分组的更好方法。这取决于您的数据结构:1, &lt;NA&gt;, &lt;NA&gt; 是否应该被1, 2, &lt;NA&gt; 覆盖?
    • 分组后,我们选择NAs数量最少的行。
    • 最后我们做一些清理工作:删除辅助列、排列数据和取消分组。

    【讨论】:

    • 天才!这很好用;是的 - 1, &lt;NA&gt;, &lt;NA&gt; 应该被 1, 2, &lt;NA&gt; 覆盖原来的第 12 行(现在的第 11 行)也应该被删除,因为它是原始 df 中第 13 行的一部分。但这很容易解决。非常感谢!
    • 请注意这种情况。我不确定1, &lt;NA&gt;, &lt;NA&gt; 的情况是否处理得当。
    【解决方案2】:

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

    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
    

    【讨论】:

    • 这其实是一个非常有趣的想法!但结果不是我需要的。
    【解决方案3】:

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

    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)
    

    附:还将1 - NA - NA 替换为1 - 2 - NA 并将1 - NA - NA 替换为1 - NA - 3

    【讨论】:

    • 感谢您的回答!很酷,你们实际上回答得如此之快且如此多样化:) 仍然 - 据我所知,它并没有像我想要的那样工作,原因有两个 - a) 它没有返回 df 的清理版本(5,9 行仍然存在); b)它不允许我使用简单的特征几何列(因此需要列表列)-您的答案确实适用于常规列表列,所以这个 nitpick 不太公平-这就是为什么我无论如何都赞成它
    猜你喜欢
    • 1970-01-01
    • 2013-11-14
    • 2020-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多